Compare commits

...
17 Commits
Author SHA1 Message Date
Alexdoru 9c295a1e22 Fix cached item in RenderItemFrame leaking both world client and server (#1659) 2026-04-13 15:04:36 -07:00
DarkShadow44 9fd02900ef Load modern block properties (#1626) 2026-04-13 15:03:23 -07:00
Jason Mitchell 27e9a4d4fd Fix class circularity error with early redirector (#1670) 2026-04-13 15:02:26 -07:00
Darren Eberly dfc08053d5 Don't advance buffer positions for Material shininess in GLSM (#1668) 2026-04-12 22:25:25 -07:00
VortexSo4 1af03fa369 add CTM guide (#1667) 2026-04-11 17:49:38 -07:00
NealDeal34 3ae3b2527d Update zh_CN.lang (#1623)
* Update zh_CN.lang

* update zh_CN.lang for dynamic lights
2026-04-07 08:21:34 +02:00
Jason Mitchell fd2a14a000 Various fixes (#1652) 2026-04-06 17:35:15 -07:00
DarkShadow44 9f0f6a6232 Always render clouds late for shaders (#1649) 2026-04-06 09:52:34 -07:00
DarkShadow44 2942c7eb84 Disable clouds when shaders are active (#1648) 2026-04-06 09:52:21 -07:00
Alexdoru 47762f921e fix two world client memory leaks (#1650) 2026-04-06 16:38:03 +00:00
Jason Mitchell bdc4431760 Fix some issues someone was having with nixos (#1647) 2026-04-05 16:21:58 -07:00
DarkShadow44 7e1beadfb5 DH: Fix mismatch from upstream for solas shaders (#1645) 2026-04-04 17:27:19 -07:00
Jason Mitchell 59a4b84023 Standalone GLSM mod - Umbra (#1642) 2026-04-03 21:51:35 -07:00
Jason Mitchell 8d53e25e0a Compact CTM Fix (#1640) 2026-04-03 16:48:08 -07:00
VortexSo4 bbf83db379 Implement compact ctm (#1636) 2026-04-02 22:32:28 +00:00
Jason Mitchell ec82280b9b Match OpenGL Buffer Behavior for glGet (#1637) 2026-04-02 14:20:06 -07:00
DarkShadow44andJason Mitchell d7588e4c14 Adapt shader block id override for Angelica 2.x (#1601)
Co-authored-by: Jason Mitchell <[email protected]>
2026-04-02 14:18:55 -07:00
92 changed files with 3243 additions and 329 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
/.idea/
/.vs/
/.vscode/
/run/
run/
build/
/eclipse/
.classpath
+302
View File
@@ -0,0 +1,302 @@
# CTM Guide<img src="img/logo-readme.png" align="right">
###### by Vortex [@vortex_so in discord, any suggestions are accepted]<br>
This file describes how to use CTM, structure of `.properties` file and some useful (or not) techniques
### Note:
- All textures for CTM textures are in `[modname]/mcpatcher/ctm` folder<br>
the overrides apply globally so it doesn't matter in which directory you put them<br>
- in the `ctm` folder you must have a folder with your block name (or any name actually)<br>
in that folder you will have all texture files (animations allowed via mcmeta file for every texture) and `.properties` file<br>
multiple `.properties` files are allowed for a single folder and set of textures for more complex behaviour (see examples)
## Quick Links
- **[Method](#method)**
- **[MatchBlocks](#matchblocks)**
- **[MatchTiles](#matchtiles)**
- **[Tiles](#tiles)**
- **[Connect](#connect)**
- **[Faces](#faces)**
- **[Biomes](#biomes)**
- **[Heights](#heights)**
- **[RenderPass](#renderpass)**
- **[InnerSeams](#innerseams)**
- **[Weights](#weights)**<br>
- **[Examples](#examples)**
## Structure of `.properties` files
---
## `matchBlocks`
Override for a specific block, you can get the value from the tooltip if you have this feature enabled.<br>
Can accept several values which allows to apply one CTM for different blocks.<br>
if no value for this property was provided, Angelica will fallback to name of the `.property` file
examples:<br>
`matchBlocks=gregtech:gt.blockcasings4`<br>
`matchBlocks=minecraft:glass minecraft:glass_pane`
---
## `matchTiles`
Override for a specific texture, main difference from matchBlocks is that this will only match a certain texture, not the block itself<br>
example:<br>
`matchTiles:minecraft:blocks/stone`
---
## `tiles`
The list of texture names for CTM. Supports ranges and explicit names.<br>
You probably would want to use texture names like `0.png`, `1.png` as it is easier, however this allows for any names
examples:<br>
`tiles=0-46` - classic for full CTM, expects files from `0.png` to `46.png`<br>
`tiles=0-3,5, 8` - expects files `0.png`, `1.png`, `2.png`, `3.png`, `5.png`, `8.png`<br>
`tiles=sometext,somemoretext` - expects files `sometext.png`, `somemoretext.png`
---
## `connect`
Sets how the blocks are connected, can accept values: `block`, `tile`, `material`<br>
`block` connects based on block (who would guess)<br>
`tile` connects based on texture<br>
`material` connects based on material (didn't really understand this part, ig it is for stone, iron, wood etc)<br>
Fallbacks to `block` if no value for `matchTiles` is set, otherwise fallbacks to `tile`<br>
Most of the time you don't need to set this value because of that fallback rule
example:<br>
`connect=block`
---
## `faces`
Limit to specific faces of a block, can accept several values<br>
Fallbacks to `all` if value not present in the `.properties` file
examples:<br>
`faces=top`<br>
`faces=all`<br>
`faces=bottom`<br>
`faces=sides`<br>
`faces=north south east west`<br>
`faces=top bottom sides`
---
## `biomes`
Limit to specific biomes, can accept several values<br>
Fallbacks to all if value not present in the `.properties` file
examples:<br>
`biomes=plains`<br>
`biomes=desert forest`<br>
`biomes=taiga jungle savanna`
---
## `heights`
Limit to certain Y levels, can only accept several values<br>
can be set through either `minHeight` and `maxHeight` or `heights`
examples:<br>
`heights=0-64`<br>
`heights=64-255`<br>
example of maxHeight and minHeight:<br>
`minHeight=64`<br>
`maxHeight=128`
---
## `renderPass`
Controls which rendering layer a CTM rule is applied in<br>
In most cases you only need it to avoid rendering issues with transparency
`renderPass=solid` used for fully opaque blocks<br>
`renderPass=cutout` used for fences, glass CTM, textured blocks with hard transparency<br>
`renderPass=translucent` used for transparent blocks like glass or water<br>
`renderPass=cutout_mipped` no idea for what<br>
`renderPass=overlay` used for blocks with an overlay, for example GT hatches or damaged anvils<br>
`renderPass=backface` used for blocks with a backface, however it is very rare to use this one
---
## `innerSeams`
Adds internal seams, fallback to `innerSeams=false` if not set<br>
example:<br>
`innerSeams=true`
---
## `weights`
Priority, used for `Random` type to determine which textures have certain chances to be used for render<br>
accepts several values, assigns values correspondingly to `tiles` list values
examples:<br>
`weight=10`<br>
`weights=1 3 3 2 1`
---
# `Method`
Selects CTM mode:
- [LINK](#full-ctm-ctm) `ctm` / `glass` / `default`
- [LINK](#compact-ctm) `compact` / `ctm_compact`
- [LINK](#horizontal)`horizontal` / `bookshelf`
- [LINK](#vertical)`vertical`
- [LINK](#horizontalvertical-hv)`horizontal+vertical` / `h+v`
- [LINK](#verticalhorizontal-vh)`vertical+horizontal` / `v+h`
- [LINK](#top)`top` / `sandstone`
- [LINK](#repeat)`repeat` / `pattern`
- [LINK](#random)`random`
- [LINK](#fixed)`fixed` / `static`
example:<br>
`method=ctm`
### Full CTM (ctm)
Requires exactly 47 tiles, the most versatile but the most size-heavy method
Here's a visual explanation of how the textures should be numbered:
<img src="img/CTM/CTM-pattern.png" width="624" style="image-rendering: pixelated;">
---
### Compact CTM
Requires exactly 5 tiles.
<img src="img/CTM/CCTM-pattern.png" width="284" style="image-rendering: pixelated;">
Splits face into 4 quadrants and builds final texture per quadrant.
Here's a visual explanation of how it works on an example of 3x3 blocks pattern with a cutout:
<img src="img/CTM/CCTM-principle.png" width="400" style="image-rendering: pixelated;">
as you can see, compact CTM has a single limitation:<br>
the size of any part of texture, such as an edge or a corner, can't be larger than `8x8` pixel<br>
this is the main case where only full CTM can be used.
---
### Horizontal
Requires exactly 4 tiles. connects only horizontally, used for bookshelves for example
<img src="img/CTM/HCTM-pattern.png" width="284" style="image-rendering: pixelated;">
---
### Vertical
Requires exactly 4 tiles.
<img src="img/CTM/VCTM-pattern.png" height="284" style="image-rendering: pixelated;">
---
### Horizontal+Vertical (h+v)
Requires exactly 7 tiles.
Two-stage: horizontal first, then vertical if none horizontal variations are found.
<img src="img/CTM/HVCTM-pattern.png" height="284" style="image-rendering: pixelated;">
---
### Vertical+Horizontal (v+h)
Requires exactly 7 tiles.
Two-stage: vertical first, then horizontal if none vertical variations are found.
<img src="img/CTM/VHCTM-pattern.png" height="284" style="image-rendering: pixelated;">
---
### Top
Requires exactly 1 tile.
Applies tile on side faces when block above matches, for example used for smooth sandstone
---
### Repeat
Requires width * height tiles.
Properties:
- `width` - defines the number of tiles in width
- `height` - defines the number of tiles in height
- `symmetry` - defines the symmetry of the pattern, can be either `none` or `opposite`
Just uses N*M grid, for example<br>
`0 1 2 3 4`<br>
`5 6 7 8 9`
---
### Random
Any number of tiles.
Properties:
- `weights` - described [here](#weights)
- `symmetry` - accepts `all`, `opposite`, `none`, groups opposite faces together, otherwise keeps them independent
- `linked` - makes the textures consistent between render states, not needed much
---
### Fixed
Requires exactly 1 tile. Always uses the same tile.<br>
useful only if combined with height or other properties (see examples)
---
# Examples
## Glass via full CTM
```properties
matchBlocks=minecraft:glass minecraft:glass_pane
metadata=0
method=ctm
faces=all
tiles=0-46
connect=block
```
## Multiple properties files usage
### Use multiple .properties files with different heights for more detailed worldgen
this one will use one texture for lower part of the world and another for the rest of the world
```properties
matchBlocks=minecraft:dirt
metadata=0
method=fixed
faces=all
tiles=0
connect=block
maxHeight=40
```
```properties
matchBlocks=minecraft:dirt
metadata=0
method=fixed
faces=all
tiles=0
connect=block
minHeight=41
```
---
+2
View File
@@ -97,6 +97,8 @@ dependencies {
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
//devOnlyNonPublishable('com.github.GTNewHorizons:worldedit-gtnh:v0.0.9')
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
@@ -949,10 +949,11 @@ public class GLStateManager {
switch (pname) {
case GL11.GL_COLOR_WRITEMASK -> {
params.put((byte) (colorMask.red ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put((byte) (colorMask.green ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put((byte) (colorMask.blue ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put((byte) (colorMask.alpha ? GL11.GL_TRUE : GL11.GL_FALSE));
final int pos = params.position();
params.put(pos, (byte) (colorMask.red ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put(pos + 1, (byte) (colorMask.green ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put(pos + 2, (byte) (colorMask.blue ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put(pos + 3, (byte) (colorMask.alpha ? GL11.GL_TRUE : GL11.GL_FALSE));
}
default -> {
if (!HAS_MULTIPLE_SET.contains(pname)) {
@@ -1063,7 +1064,7 @@ public class GLStateManager {
case GL11.GL_DIFFUSE -> state.diffuse.get(0, params);
case GL11.GL_SPECULAR -> state.specular.get(0, params);
case GL11.GL_EMISSION -> state.emission.get(0, params);
case GL11.GL_SHININESS -> params.put(state.shininess);
case GL11.GL_SHININESS -> params.put(params.position(), state.shininess);
case GL11.GL_COLOR_INDEXES -> state.colorIndexes.get(0, params);
default -> {
}
@@ -1078,11 +1079,11 @@ public class GLStateManager {
case GL11.GL_SPECULAR -> state.specular.get(0, params);
case GL11.GL_POSITION -> state.position.get(0, params);
case GL11.GL_SPOT_DIRECTION -> state.spotDirection.get(0, params);
case GL11.GL_SPOT_EXPONENT -> params.put(state.spotExponent);
case GL11.GL_SPOT_CUTOFF -> params.put(state.spotCutoff);
case GL11.GL_CONSTANT_ATTENUATION -> params.put(state.constantAttenuation);
case GL11.GL_LINEAR_ATTENUATION -> params.put(state.linearAttenuation);
case GL11.GL_QUADRATIC_ATTENUATION -> params.put(state.quadraticAttenuation);
case GL11.GL_SPOT_EXPONENT -> params.put(params.position(), state.spotExponent);
case GL11.GL_SPOT_CUTOFF -> params.put(params.position(), state.spotCutoff);
case GL11.GL_CONSTANT_ATTENUATION -> params.put(params.position(), state.constantAttenuation);
case GL11.GL_LINEAR_ATTENUATION -> params.put(params.position(), state.linearAttenuation);
case GL11.GL_QUADRATIC_ATTENUATION -> params.put(params.position(), state.quadraticAttenuation);
default -> {
}
}
@@ -1101,21 +1102,39 @@ public class GLStateManager {
case GL11.GL_COLOR_CLEAR_VALUE -> clearColor.get(params);
case GL11.GL_CURRENT_COLOR -> color.get(params);
case GL11.GL_DEPTH_RANGE -> {
params.put((float) viewportState.depthRangeNear);
params.put((float) viewportState.depthRangeFar);
final int pos = params.position();
params.put(pos, (float) viewportState.depthRangeNear);
params.put(pos + 1, (float) viewportState.depthRangeFar);
}
case GL14.GL_BLEND_COLOR -> {
final int pos = params.position();
params.put(pos, blendState.getBlendColorR());
params.put(pos + 1, blendState.getBlendColorG());
params.put(pos + 2, blendState.getBlendColorB());
params.put(pos + 3, blendState.getBlendColorA());
}
case GL14.GL_BLEND_COLOR -> params.put(blendState.getBlendColorR()).put(blendState.getBlendColorG()).put(blendState.getBlendColorB()).put(blendState.getBlendColorA());
case GL11.GL_CURRENT_NORMAL -> {
final Vector3f normal = ShaderManager.getCurrentNormal();
params.put(normal.x).put(normal.y).put(normal.z);
final int pos = params.position();
params.put(pos, normal.x);
params.put(pos + 1, normal.y);
params.put(pos + 2, normal.z);
}
case GL11.GL_CURRENT_TEXTURE_COORDS -> {
final Vector4f tc = ShaderManager.getCurrentTexCoord();
params.put(tc.x).put(tc.y).put(tc.z).put(tc.w);
final int pos = params.position();
params.put(pos, tc.x);
params.put(pos + 1, tc.y);
params.put(pos + 2, tc.z);
params.put(pos + 3, tc.w);
}
case GL11.GL_FOG_COLOR -> {
final FloatBuffer fogBuf = fogState.getFogColorBuffer();
params.put(fogBuf.get(0)).put(fogBuf.get(1)).put(fogBuf.get(2)).put(fogBuf.get(3));
final int pos = params.position();
params.put(pos, fogBuf.get(0));
params.put(pos + 1, fogBuf.get(1));
params.put(pos + 2, fogBuf.get(2));
params.put(pos + 3, fogBuf.get(3));
}
case GL11.GL_LIGHT_MODEL_AMBIENT -> lightModel.ambient.get(0, params);
default -> {
@@ -36,9 +36,10 @@ public class GlslTransformUtils {
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
private record ReservedWordRename(Pattern pattern, String replacement) {}
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
// Always rename sample, glsl-transformation-lib doesn't like it
// Some reserved words to always rename
0, List.of(
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample")
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"),
new ReservedWordRename(Pattern.compile("\\bnew\\b"), RENAMED_PREFIX + "new")
),
400, List.of(
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.glsm;
/**
* Tessellator data needed by {@link com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer}.
*/
public interface ITessellatorData {
boolean isDrawing();
void setDrawing(boolean drawing);
int getVertexCount();
int[] getRawBuffer();
int getRawBufferIndex();
int getRawBufferSize();
void setRawBufferSize(int size);
void setRawBuffer(int[] buffer);
int getDrawMode();
boolean hasTexture();
boolean hasColor();
boolean hasNormals();
boolean hasBrightness();
void angelica$reset();
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.glsm.loading;
import java.util.List;
/**
* Verifies required classes/resources are present on the classpath at load time.
* Fails fast with a descriptive error message if a dependency is missing.
*/
public final class DependencyVerifier {
/**
* A single dependency check: a classpath resource that must exist.
* @param resourcePath Resource path to check (e.g. "/com/example/SomeClass.class")
* @param errorMessage Human-readable error message if missing (include download URL)
*/
public record Check(String resourcePath, String errorMessage) {}
/**
* Verify all checks pass. Throws RuntimeException on first failure.
* @param anchor Class whose classloader to use for resource lookups
* @param checks Dependency checks to run
*/
public static void verify(Class<?> anchor, List<Check> checks) {
for (Check check : checks) {
if (anchor.getResource(check.resourcePath()) == null) {
throw new RuntimeException(check.errorMessage());
}
}
}
private DependencyVerifier() {}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class EarlyRedirectorCore {
private final GLSMRedirector core;
private final String[] targets;
private final String[] exclusions;
public EarlyRedirectorCore(String... extraExclusions) {
this.core = new GLSMRedirector();
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
if (extraExclusions != null && extraExclusions.length > 0) {
Collections.addAll(excl, extraExclusions);
}
this.exclusions = excl.toArray(new String[0]);
}
public byte[] transform(String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
if (!startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, exclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (core.transformClassNode(transformedName, cn)) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String p : prefixes) {
if (name.startsWith(p)) return true;
}
return false;
}
}
@@ -0,0 +1,78 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower.NarrowRule;
import java.util.List;
public final class EcosystemNarrowRules {
public static final List<NarrowRule> ALL = List.of(
new NarrowRule("DragonAPI", "Reika.DragonAPI.ASM", List.of(
"Reika.DragonAPI.ASM.Patchers",
"Reika.DragonAPI.ASM.Profiling",
"Reika.DragonAPI.ASM.APIStripper",
"Reika.DragonAPI.ASM.ClassReparenter",
"Reika.DragonAPI.ASM.DependentMethodStripper",
"Reika.DragonAPI.ASM.DragonAPIClassTransformer",
"Reika.DragonAPI.ASM.FMLItemBlockPatch",
"Reika.DragonAPI.ASM.FluidNamePatch",
"Reika.DragonAPI.ASM.InterfaceInjector",
"Reika.DragonAPI.ASM.SpecialBiomePlacement",
"Reika.DragonAPI.ASM.StructureLootHooks"
)),
new NarrowRule("Xaeros_common", "xaero.common.core", List.of(
"xaero.common.core.transformer"
)),
new NarrowRule("Xaeros_map", "xaero.map.core", List.of(
"xaero.map.core.transformer"
)),
new NarrowRule("AdvancedLightsabers", "com.fiskmods.lightsabers.asm", List.of(
"com.fiskmods.lightsabers.asm.ALLoadingPlugin",
"com.fiskmods.lightsabers.asm.ASMHooks",
"com.fiskmods.lightsabers.asm.transformers"
)),
new NarrowRule("Ears", "com.unascribed.ears", List.of(
"com.unascribed.ears.asm",
"com.unascribed.ears.common.agent"
)),
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
"alfheim.common.core.asm.hook.AlfheimHPHooks",
"alfheim.common.core.asm.hook.Botania18AndUpBackport",
"alfheim.common.core.asm.hook.ElementalDamageAdapter",
"alfheim.common.core.asm.hook.integration",
"alfheim.common.core.asm.hook.extender.FurnaceExtender",
"alfheim.common.core.asm.hook.extender.ItemAuraRingExtender",
"alfheim.common.core.asm.hook.extender.ItemLensExtender",
"alfheim.common.core.asm.hook.extender.ItemTwigWandExtender",
"alfheim.common.core.asm.hook.extender.LightRelayExtender",
"alfheim.common.core.asm.hook.extender.PureDaisyExtender",
"alfheim.common.core.asm.hook.extender.QuartzExtender",
"alfheim.common.core.asm.hook.extender.RelicHooks",
"alfheim.common.core.asm.hook.extender.SparkExtender",
"alfheim.common.core.asm.hook.fixes.BotaniaGlowingRenderFixes",
"alfheim.common.core.asm.hook.fixes.CorporeaInputFix",
"alfheim.common.core.asm.hook.fixes.GodAttributesHooks",
"alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix"
))
);
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
"org.embeddedt.embeddium",
"org.taumc.celeritas",
"com.mitchej123.lwjgl.lwjgl3",
"com.mitchej123.glsm",
"com.gtnewhorizons.angelica.lwjgl3",
};
public static final String[] EARLY_REDIRECTOR_TARGETS = {
"cn.tesseract.mycelium.",
};
private EcosystemNarrowRules() {}
}
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.glsm.loading;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Narrows overly-broad transformer exclusions that prevent the GLSM redirector from
* transforming GL calls in mod code. Some coremods register exclusions covering their
* entire package tree, which inadvertently shields rendering code from redirection.
* <p>
* This utility removes the broad exclusion and re-adds narrower ones that cover only
* the coremod's own transformer/ASM classes, leaving rendering code exposed.
* <p>
* Operates on a {@code Set<String>} (the transformer exceptions set from LaunchClassLoader)
* and a {@code Map<String, Object>} (the Launch blackboard). Callers extract these from
* the Forge/launchwrapper infrastructure.
*/
public final class TransformerNarrower {
private static final Logger LOGGER = LogManager.getLogger("GLSM");
/**
* A rule that replaces a broad transformer exclusion with narrower ones.
* @param name Human-readable name for logging (e.g. "DragonAPI")
* @param broad The broad exclusion to remove (e.g. "Reika.DragonAPI.ASM")
* @param narrow Narrower exclusions to add in its place
*/
public record NarrowRule(String name, String broad, List<String> narrow) {}
/**
* Apply narrowing rules to the transformer exceptions set.
*
* @param exceptions The mutable Set from LaunchClassLoader.transformerExceptions
* @param blackboard The Launch.blackboard map (checked for disable flags)
* @param keyPrefix Blackboard key prefix for per-rule opt-out (e.g. "angelica" or "umbra").
* Rule "DragonAPI" checks key "{prefix}.narrow.DragonAPI". If explicitly
* set to Boolean.FALSE, the rule is skipped.
* @param callerName Name for log messages (e.g. "Angelica" or "Umbra")
* @param rules The narrowing rules to apply
* @return number of rules that were applied
*/
public static int narrow(Set<String> exceptions, Map<String, Object> blackboard,
String keyPrefix, String callerName, List<NarrowRule> rules) {
int applied = 0;
for (NarrowRule rule : rules) {
final String key = keyPrefix + ".narrow." + rule.name();
if (Boolean.FALSE.equals(blackboard.get(key))) {
continue;
}
if (exceptions.remove(rule.broad())) {
for (String narrow : rule.narrow()) {
exceptions.add(narrow);
}
LOGGER.info("[{}] Narrowed {} transformer exclusion to allow GL redirection", callerName, rule.broad());
applied++;
}
}
return applied;
}
private TransformerNarrower() {}
}
@@ -103,9 +103,12 @@ public class MaterialState implements ISettableState<MaterialState> {
}
}
public void setShininess(FloatBuffer newBuffer) { setShininess(newBuffer.get()); }
// On the buffer versions of shininess, we need to call `get(0)` explicitly specifying the index
// otherwise calling `get()` advances the buffer position, and some mods may re-use the same buffer
// in subsequent calls, resulting in a BufferUnderflowException.
public void setShininess(FloatBuffer newBuffer) { setShininess(newBuffer.get(0)); }
public void setShininess(IntBuffer newBuffer) { setShininess((float) newBuffer.get());}
public void setShininess(IntBuffer newBuffer) { setShininess((float) newBuffer.get(0)); }
public void setShininess(int val) { setShininess((float) val);}
@@ -1,16 +1,14 @@
package com.gtnewhorizons.angelica.client.rendering;
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ITessellatorData;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
@@ -71,42 +69,42 @@ public class TessellatorStreamingDrawer {
/**
* Draw the vanilla Tessellator's data using streaming VBO+VAO instead of FFP client arrays.
*/
public static int draw(Tessellator tess) {
if (!tess.isDrawing) {
public static int draw(ITessellatorData tess) {
if (!tess.isDrawing()) {
throw new IllegalStateException("Not tesselating!");
}
tess.isDrawing = false;
tess.setDrawing(false);
final int vertexCount = tess.vertexCount;
final int vertexCount = tess.getVertexCount();
if (vertexCount == 0) {
final int result = tess.rawBufferIndex * 4;
tess.reset();
final int result = tess.getRawBufferIndex() * 4;
tess.angelica$reset();
return result;
}
// Determine the optimal vertex format from the tessellator's flags
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
final int flags = VertexFlags.convertToFlags(tess.hasTexture(), tess.hasColor(), tess.hasNormals(), tess.hasBrightness());
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
final int requiredBytes = vertexCount * vertexSize;
ensureRepackCapacity(requiredBytes);
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
final long writePtr = format.writeToBuffer0(repackAddress, tess.getRawBuffer(), tess.getRawBufferIndex());
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.getDrawMode(), vertexCount);
// Shrink rawBuffer if oversized
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
tess.rawBufferSize = 0x10000;
tess.rawBuffer = new int[tess.rawBufferSize];
if (tess.getRawBufferSize() > 0x20000 && tess.getRawBufferIndex() < (tess.getRawBufferSize() << 3)) {
tess.setRawBufferSize(0x10000);
tess.setRawBuffer(new int[tess.getRawBufferSize()]);
}
final int result = tess.rawBufferIndex * 4;
tess.reset();
final int result = tess.getRawBufferIndex() * 4;
tess.angelica$reset();
return result;
}
@@ -128,6 +126,19 @@ public class TessellatorStreamingDrawer {
uploadAndDraw(buffer, flags, format, vertexSize, drawMode, vertexCount);
}
/**
* Upload pre-packed vertex data and draw. Public API for external batch systems.
* @param packedData buffer positioned at 0 with limit set to total bytes
* @param drawMode GL draw mode (GL_QUADS, GL_TRIANGLES, etc.)
* @param flags vertex format flags (from VertexFlags)
* @param vertexCount number of vertices
*/
public static void drawPacked(ByteBuffer packedData, int drawMode, int flags, int vertexCount) {
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
uploadAndDraw(packedData, flags, format, vertexSize, drawMode, vertexCount);
}
private static String cachedDebugInfo = "Stream: not initialized";
private static long lastDebugUpdateNanos;
private static final long DEBUG_UPDATE_INTERVAL_NS = 500_000_000L; // 500ms
@@ -172,34 +183,6 @@ public class TessellatorStreamingDrawer {
}
}
/**
* Draw a merged batch of pre-packed vertex data from a DeferredBatchTessellator.
* Copies byte ranges into repackBuffer, then uploads and draws.
*/
static void drawPackedBatch(DeferredBatchTessellator source,
java.util.List<DeferredBatchTessellator.DrawRange> ranges,
int from, int to,
int totalBytes, int totalVertices,
int drawMode, int flags) {
if (totalVertices == 0) return;
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
ensureRepackCapacity(totalBytes);
long writePos = repackAddress;
for (int j = from; j < to; j++) {
final DeferredBatchTessellator.DrawRange r = ranges.get(j);
source.copyRange(r.byteOffset(), r.byteLength(), writePos);
writePos += r.byteLength();
}
repackBuffer.position(0);
repackBuffer.limit(totalBytes);
uploadAndDraw(repackBuffer, flags, format, vertexSize, drawMode, totalVertices);
}
/**
* Upload packed vertex data to a streaming buffer and issue the draw call.
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
@@ -239,7 +222,11 @@ public class TessellatorStreamingDrawer {
}
}
private static void ensureRepackCapacity(int requiredBytes) {
/**
* Ensure the repack buffer is large enough for the given byte count.
* Public for use by external batch systems that need to pack data before calling {@link #drawPacked}.
*/
public static void ensureRepackCapacity(int requiredBytes) {
if (requiredBytes <= repackCapacity) return;
int newCapacity = repackCapacity;
@@ -253,6 +240,16 @@ public class TessellatorStreamingDrawer {
repackCapacity = newCapacity;
}
/** Get the repack buffer's native address. Valid until next {@link #ensureRepackCapacity} call. */
public static long getRepackAddress() {
return repackAddress;
}
/** Get the repack ByteBuffer. Caller must set position/limit before passing to {@link #drawPacked}. */
public static ByteBuffer getRepackBuffer() {
return repackBuffer;
}
private static void ensureVAO(int flags, VertexFormat format) {
init();
@@ -596,4 +596,21 @@ class CompatShaderTransformerTest {
assertFalse(fragResult.contains("varying"), "no 'varying' keyword in fragment");
assertTrue(fragResult.contains("in vec2 v_TexCoord"), "varying → in in fragment");
}
@Test
void testReservedWordNewIsRenamed() {
String src = """
#version 120
uniform sampler2D tex;
void main() {
vec4 color = texture2D(tex, vec2(0.5));
float new = color.r > 0.5 ? 1.0 : 0.0;
gl_FragColor = vec4(new, new, new, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertFalse(result.matches("(?s).*\\bnew\\b(?!\\s*\\().*"), "'new' as identifier must be renamed in core profile output\n\n" + result);
assertTrue(result.contains("angelica_renamed_new"), "'new' should be renamed to angelica_renamed_new\n\n" + result);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.client.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import lombok.Getter;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.opengl.GL11;
@@ -108,12 +109,33 @@ public class DeferredDrawBatcher {
break;
}
TessellatorStreamingDrawer.drawPackedBatch(batchTessellator, ranges, i, subEnd, totalBytes, totalVertices, drawMode, flags);
drawPackedBatch(batchTessellator, ranges, i, subEnd, totalBytes, totalVertices, drawMode, flags);
i = subEnd;
}
}
/**
* Pack ranges from a DeferredBatchTessellator into the streaming drawer's repack buffer
* and issue a single draw call for the merged batch.
*/
private static void drawPackedBatch(DeferredBatchTessellator source, List<DeferredBatchTessellator.DrawRange> ranges, int from, int to, int totalBytes, int totalVertices, int drawMode, int flags) {
if (totalVertices == 0) return;
TessellatorStreamingDrawer.ensureRepackCapacity(totalBytes);
long writePos = TessellatorStreamingDrawer.getRepackAddress();
for (int j = from; j < to; j++) {
final DeferredBatchTessellator.DrawRange r = ranges.get(j);
source.copyRange(r.byteOffset(), r.byteLength(), writePos);
writePos += r.byteLength();
}
final java.nio.ByteBuffer buf = TessellatorStreamingDrawer.getRepackBuffer();
buf.position(0);
buf.limit(totalBytes);
TessellatorStreamingDrawer.drawPacked(buf, drawMode, flags, totalVertices);
}
/**
* Pack current GLSM state into a long key for grouping.
* Layout: [tex1En:1][tex0En:1][textureId:20][srcRgb:12][dstRgb:12][blendEnabled:1][depthMask:1] = 48 bits
@@ -26,12 +26,11 @@ public class Camera {
private float pitch;
private float yaw;
private EntityLivingBase entity;
private boolean isSleeping;
private boolean thirdPerson;
private float partialTicks;
private Camera() {
}
private Camera() {}
/**
* Updates the camera position and orientation based on the current entity and partial ticks.
@@ -41,7 +40,7 @@ public class Camera {
*/
public void update(EntityLivingBase entity, float partialTicks) {
this.partialTicks = partialTicks;
this.entity = entity;
this.isSleeping = entity.isPlayerSleeping();
// Entity position (interpolated, at eye level)
final double entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
@@ -235,6 +235,11 @@ public class AngelicaConfig {
@Config.DefaultBoolean(false)
public static boolean enableHardcodedCustomUniforms;
@Config.Comment("Modern MC_VERSION to try if shader pack has no 1.7.10 section. 0 = default (260101)")
@Config.DefaultInt(0)
@Config.RangeInt(min = 0)
public static int modernFallbackMcVersion;
@Config.Comment("Define IS_IRIS in shader macros.")
@Config.DefaultBoolean(true)
public static boolean defineIsIris;
@@ -8,6 +8,8 @@ import com.gtnewhorizon.gtnhmixins.builders.ITransformers;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import com.gtnewhorizons.angelica.loading.fml.compat.CompatHandlers;
import com.gtnewhorizons.angelica.mixins.Mixins;
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
@@ -91,30 +93,25 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
.findFirst()
.orElse(null);
if (handle != null) {
handle.exclusions().add("org.embeddedt.embeddium");
handle.exclusions().add("org.taumc.celeritas");
handle.exclusions().add("com.mitchej123.lwjgl.lwjgl3");
handle.exclusions().add("com.mitchej123.glsm");
handle.exclusions().add("com.gtnewhorizons.angelica.lwjgl3");
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
handle.exclusions().add(exclusion);
}
handle.exclusions().add("com.gtnewhorizons.angelica.vulkan");
}
}
private static void verifyDependencies() {
// Check for fastutil (bundled with GTNHLib since 0.2.1)
if (AngelicaTweaker.class.getResource("/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class") == null) {
throw new RuntimeException("Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib");
}
// Check for PrimitiveExtractor/cel.model classes (added in GTNHLib 0.8.21)
if (AngelicaTweaker.class.getResource("/com/gtnewhorizon/gtnhlib/client/renderer/PrimitiveExtractor.class") == null) {
throw new RuntimeException("GTNHLib is outdated: Angelica requires GTNHLib 0.8.21 or newer! Download: https://modrinth.com/mod/gtnhlib");
}
// Check for jvmdowngrader stubs (bundled with GTNHLib since 0.9.0)
if (AngelicaTweaker.class.getResource("/xyz/wagyourtail/jvmdg/exc/MissingStubError.class") == null) {
throw new RuntimeException("GTNHLib is outdated: Angelica requires GTNHLib 0.9.0 or newer! Download: https://modrinth.com/mod/gtnhlib");
}
DependencyVerifier.verify(AngelicaTweaker.class, List.of(
new DependencyVerifier.Check(
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
"Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/com/gtnewhorizon/gtnhlib/client/renderer/PrimitiveExtractor.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.8.21 or newer! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/xyz/wagyourtail/jvmdg/exc/MissingStubError.class",
"GTNHLib is outdated: Angelica requires GTNHLib 0.9.0 or newer! Download: https://modrinth.com/mod/gtnhlib")
));
}
@Override
@@ -1,10 +1,8 @@
package com.gtnewhorizons.angelica.loading.fml.transformers;
import com.gtnewhorizons.angelica.loading.shared.transformers.AngelicaRedirector;
import com.gtnewhorizons.angelica.glsm.loading.EarlyRedirectorCore;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
/**
* A scoped redirector that only transforms classes from known-misbehaving (core)mod packages.
@@ -13,52 +11,20 @@ import org.objectweb.asm.tree.ClassNode;
* Those classes get missed by the late-registered {@link AngelicaRedirectorTransformer}, so their
* GL calls permanently bypass GLSM.
* <p>
* This transformer is registered in the {@code AngelicaTweaker} constructor (before any
* coremod's {@code injectData}) and is removed by {@code AngelicaLateTweaker} once the
* full redirector is in its proper post-mixin position.
* <p>
* The mixin service will add this to its delegation list, but it no-ops on game classes
* since it only targets specific mod packages.
* Registered in {@code AngelicaClientTweaker} constructor. Removed by {@code AngelicaLateTweaker}
* once the full redirector is in its proper post-mixin position.
*
* @see EcosystemNarrowRules#EARLY_REDIRECTOR_TARGETS
*/
public class EarlyRedirectorTransformer implements IClassTransformer {
private static final String[] TARGET_PACKAGES = {
"cn.tesseract.mycelium.",
};
private final AngelicaRedirector inner = new AngelicaRedirector();
private final String[] exclusions = inner.getTransformerExclusions();
private final EarlyRedirectorCore impl = new EarlyRedirectorCore(
"com.gtnewhorizons.angelica.lwjgl3.",
"com.gtnewhorizons.angelica.transform"
);
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
boolean targeted = false;
for (String pkg : TARGET_PACKAGES) {
if (transformedName.startsWith(pkg)) {
targeted = true;
break;
}
}
if (!targeted) return basicClass;
for (String exclusion : exclusions) {
if (transformedName.startsWith(exclusion)) return basicClass;
}
if (!inner.shouldTransform(basicClass)) {
return basicClass;
}
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
final boolean changed = inner.transformClassNode(transformedName, cn);
if (changed) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
return impl.transform(transformedName, basicClass);
}
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.loading.fml.tweakers;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.ITweaker;
@@ -27,8 +29,6 @@ public class AngelicaLateTweaker implements ITweaker {
/**
* Reduces overly broad transformer exclusions that prevent AngelicaRedirector from doing its job.
*
* Looking at you, DragonAPI.
*/
@SuppressWarnings("unchecked")
private static void narrowTransformerExclusions(LaunchClassLoader classLoader) {
@@ -37,79 +37,14 @@ public class AngelicaLateTweaker implements ITweaker {
exceptionsField.setAccessible(true);
final Set<String> exceptions = (Set<String>) exceptionsField.get(classLoader);
if (narrowEnabled("DragonAPI") && exceptions.remove("Reika.DragonAPI.ASM")) {
// Re-add non-GL classes; ASMCallsClient and utility classes with GL calls are left exposed
exceptions.add("Reika.DragonAPI.ASM.Patchers");
exceptions.add("Reika.DragonAPI.ASM.Profiling");
exceptions.add("Reika.DragonAPI.ASM.APIStripper");
exceptions.add("Reika.DragonAPI.ASM.ClassReparenter");
exceptions.add("Reika.DragonAPI.ASM.DependentMethodStripper");
exceptions.add("Reika.DragonAPI.ASM.DragonAPIClassTransformer");
exceptions.add("Reika.DragonAPI.ASM.FMLItemBlockPatch");
exceptions.add("Reika.DragonAPI.ASM.FluidNamePatch");
exceptions.add("Reika.DragonAPI.ASM.InterfaceInjector");
exceptions.add("Reika.DragonAPI.ASM.SpecialBiomePlacement");
exceptions.add("Reika.DragonAPI.ASM.StructureLootHooks");
FMLRelaunchLog.info("[Angelica] Narrowed Reika.DragonAPI.ASM transformer exclusion to allow GL redirection");
}
TransformerNarrower.narrow(exceptions, Launch.blackboard, "angelica", "Angelica",
EcosystemNarrowRules.ALL);
if (narrowEnabled("Xaeros")) {
if (exceptions.remove("xaero.common.core")) {
exceptions.add("xaero.common.core.transformer");
FMLRelaunchLog.info("[Angelica] Narrowed xaero.common.core transformer exclusion to allow GL redirection");
}
if (exceptions.remove("xaero.map.core")) {
exceptions.add("xaero.map.core.transformer");
FMLRelaunchLog.info("[Angelica] Narrowed xaero.map.core transformer exclusion to allow GL redirection");
}
}
if (narrowEnabled("AdvancedLightsabers") && exceptions.remove("com.fiskmods.lightsabers.asm")) {
exceptions.add("com.fiskmods.lightsabers.asm.ALLoadingPlugin");
exceptions.add("com.fiskmods.lightsabers.asm.ASMHooks");
exceptions.add("com.fiskmods.lightsabers.asm.transformers");
FMLRelaunchLog.info("[Angelica] Narrowed com.fiskmods.lightsabers.asm transformer exclusion to allow GL redirection");
}
if (narrowEnabled("Ears") && exceptions.remove("com.unascribed.ears")) {
exceptions.add("com.unascribed.ears.asm");
exceptions.add("com.unascribed.ears.common.agent");
FMLRelaunchLog.info("[Angelica] Narrowed com.unascribed.ears transformer exclusion to allow GL redirection");
}
if (narrowEnabled("Alfheim") && exceptions.remove("alfheim.common.core.asm.hook")) {
exceptions.add("alfheim.common.core.asm.hook.AlfheimFieldHookHandler");
exceptions.add("alfheim.common.core.asm.hook.AlfheimHPHooks");
exceptions.add("alfheim.common.core.asm.hook.Botania18AndUpBackport");
exceptions.add("alfheim.common.core.asm.hook.ElementalDamageAdapter");
exceptions.add("alfheim.common.core.asm.hook.integration");
exceptions.add("alfheim.common.core.asm.hook.extender.FurnaceExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.ItemAuraRingExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.ItemLensExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.ItemTwigWandExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.LightRelayExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.PureDaisyExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.QuartzExtender");
exceptions.add("alfheim.common.core.asm.hook.extender.RelicHooks");
exceptions.add("alfheim.common.core.asm.hook.extender.SparkExtender");
exceptions.add("alfheim.common.core.asm.hook.fixes.BotaniaGlowingRenderFixes");
exceptions.add("alfheim.common.core.asm.hook.fixes.CorporeaInputFix");
exceptions.add("alfheim.common.core.asm.hook.fixes.GodAttributesHooks");
exceptions.add("alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix");
FMLRelaunchLog.info("[Angelica] Narrowed alfheim.common.core.asm.hook transformer exclusion to allow GL redirection");
}
} catch (Exception e) {
FMLRelaunchLog.warning("[Angelica] Failed to narrow transformer exclusions: %s", e.getMessage());
}
}
private static boolean narrowEnabled(String mod) {
return !Boolean.FALSE.equals(Launch.blackboard.get("angelica.narrow." + mod));
}
@Override
public String getLaunchTarget() {
return null;
@@ -16,4 +16,8 @@ public interface StateAwareTessellator {
void angelica$setCeleritasMeshing(boolean active);
int[] angelica$getVertexStates();
int[] angelica$getShaderOverrideBlockIds();
void angelica$setShaderOverrideBlockId(short blockId);
}
@@ -115,6 +115,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
@SuppressWarnings("unchecked")
public void copyRawBuffer(int[] rawBuffer, int vertexCount, int[] vertexStates,
int[] shaderOverrideBlockIds,
ChunkBuildBuffers buffers, Material material, boolean isShaderPackOverride,
boolean blockAllowsSmoothLighting) {
if (vertexCount == 0) {
@@ -145,6 +146,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
final int worldY = originY + blockY;
final int worldZ = originZ + blockZ;
final short originalBlockId = blockRenderContext.blockId;
int stateIdx = 0;
int facesAtBaseMaterial = 0;
@@ -239,6 +242,9 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
}
final var builder = buffers.get(correctMaterial);
final int shaderOverrideBlockId = shaderOverrideBlockIds[quadIdx * 4];
blockRenderContext.blockId = shaderOverrideBlockId != -1 ? (short) shaderOverrideBlockId : originalBlockId;
if (correctMaterial != material && builder.getEncoder() instanceof IrisExtendedChunkVertexEncoder iris) {
iris.setContext(blockRenderContext);
}
@@ -270,6 +270,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
&& block.getLightValue() == 0; // does not emit real block light
buildContext.copyRawBuffer(tessellator.rawBuffer, tessellator.vertexCount,
((StateAwareTessellator)tessellator).angelica$getVertexStates(),
((StateAwareTessellator)tessellator).angelica$getShaderOverrideBlockIds(),
buffers, passMaterial, isShaderPackOverride, blockAllowsSmoothLighting);
tessellator.reset();
tessellator.isDrawing = false;
@@ -6,7 +6,7 @@ import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.client.rendering.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
@@ -52,10 +52,10 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
private static final double MAX_ENTITY_CHECK_VOLUME = 16 * 16 * 16 * 15;
// For sorting transparent TESRs
private RenderSectionOrderer renderSectionOrderer = new RenderSectionOrderer();
private TileEntityOrderer tileEntityOrderer = new TileEntityOrderer();
private ArrayList<RenderSection> sortedRenderSections = new ArrayList<>();
private ArrayList<TileEntity> sortedTileEntities = new ArrayList<>();
private final RenderSectionOrderer renderSectionOrderer = new RenderSectionOrderer();
private final TileEntityOrderer tileEntityOrderer = new TileEntityOrderer();
private final ArrayList<RenderSection> sortedRenderSections = new ArrayList<>();
private final ArrayList<TileEntity> sortedTileEntities = new ArrayList<>();
private CeleritasWorldRenderer(Minecraft mc) {
// Private constructor for singleton
@@ -92,6 +92,10 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
@Override
protected void unloadWorld() {
DynamicLights.setActiveRenderer(null);
this.sortedRenderSections.clear();
this.sortedTileEntities.clear();
ShadowRenderer.visibleTileEntities.clear();
ShadowRenderer.globalTileEntities.clear();
super.unloadWorld();
}
@@ -43,6 +43,20 @@ public class CTMUtils {
private static Overrides newOverrides = null;
private static TileLoader tileLoader;
private static final ThreadLocal<TileOverrideImpl.CTMCompact> CURRENT_COMPACT = new ThreadLocal<>();
public static void setCurrentCompact(TileOverrideImpl.CTMCompact compact) {
CURRENT_COMPACT.set(compact);
}
public static TileOverrideImpl.CTMCompact getCurrentCompact() {
return CURRENT_COMPACT.get();
}
public static void clearCurrentCompact() {
CURRENT_COMPACT.remove();
}
private static final StampedLock lock = new StampedLock();
private static class StateAndIterator {
@@ -0,0 +1,39 @@
package com.prupe.mcpatcher.ctm;
import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
public class CompactConnectingCtmProperties {
private final boolean innerSeams;
private final String orientation;
private final Int2IntMap tileReplacementMap = new Int2IntOpenHashMap();
public CompactConnectingCtmProperties(PropertiesFile properties) {
this.innerSeams = properties.getBoolean("innerSeams", false);
this.orientation = properties.getString("orientation", "none").toLowerCase();
for (int i = 0; i < 47; i++) {
String val = properties.getString("ctm." + i, "");
if (!val.isEmpty()) {
try {
int tileIdx = Integer.parseInt(val.trim());
tileReplacementMap.put(i, tileIdx);
} catch (NumberFormatException ignored) {}
}
}
}
public boolean getInnerSeams() {
return innerSeams;
}
public String getOrientation() {
return orientation;
}
public Int2IntMap getTileReplacementMap() {
return tileReplacementMap;
}
}
@@ -0,0 +1,322 @@
package com.prupe.mcpatcher.ctm;
import com.github.bsideup.jabel.Desugar;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
public final class CompactCtmQuadProcessor {
private static final int LEFT = 0;
private static final int UP_LEFT = 1;
private static final int UP = 2;
private static final int UP_RIGHT = 3;
private static final int RIGHT = 4;
private static final int DOWN_RIGHT = 5;
private static final int DOWN = 6;
private static final int DOWN_LEFT = 7;
private static final int SPRITE_DEFAULT = 0;
private static final int SPRITE_ALL = 1;
private static final int SPRITE_HORIZONTAL = 2;
private static final int SPRITE_VERTICAL = 3;
private static final int SPRITE_MIXED = 4;
private static final int[] CTM_47_BY_CONNECTIONS = {
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
36,17,36,17,24,19,24,43,36,17,36,17,24,19,24,43,
16,18,16,18,6,46,6,21,16,18,16,18,28,9,28,22,
36,17,36,17,24,19,24,43,36,17,36,17,24,19,24,43,
37,40,37,40,30,8,30,34,37,40,37,40,25,23,25,45,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
36,39,36,39,24,41,24,27,36,39,36,39,24,41,24,27,
16,42,16,42,6,20,6,10,16,42,16,42,28,35,28,44,
36,39,36,39,24,41,24,27,36,39,36,39,24,41,24,27,
37,38,37,38,30,11,30,32,37,38,37,38,25,33,25,26
};
/**
* Face-local basis:
* right = increasing U on the face
* down = increasing V on the face
*/
private static final FaceBasis[] FACE_BASIS = {
new FaceBasis( 1, 0, 0, 0, 0, 1), // 0: Y-
new FaceBasis( 1, 0, 0, 0, 0, 1), // 1: Y+
new FaceBasis(-1, 0, 0, 0, -1, 0), // 2: Z-
new FaceBasis( 1, 0, 0, 0, -1, 0), // 3: Z+
new FaceBasis( 0, 0, 1, 0, -1, 0), // 4: X-
new FaceBasis( 0, 0, -1, 0, -1, 0) // 5: X+
};
/**
* Neighbor offsets in face-local coordinates:
* 0 = left, 1 = up-left, 2 = up, 3 = up-right,
* 4 = right, 5 = down-right, 6 = down, 7 = down-left
*/
private static final int[] RIGHT_FACTOR = {-1, -1, 0, 1, 1, 1, 0, -1};
private static final int[] DOWN_FACTOR = { 0, -1, -1, -1, 0, 1, 1, 1};
/**
* Quadrant side references:
* 0 = top-left
* 1 = top-right
* 2 = bottom-right
* 3 = bottom-left
*/
private static final int[] QUAD_A = {LEFT, UP, RIGHT, DOWN};
private static final int[] QUAD_B = {UP, RIGHT, DOWN, LEFT};
private static final int[] QUAD_DIAG = {UP_LEFT, UP_RIGHT, DOWN_RIGHT, DOWN_LEFT};
private final IIcon[] sprites;
private final CompactConnectingCtmProperties props;
CompactCtmQuadProcessor(IIcon[] sprites, CompactConnectingCtmProperties props) {
this.sprites = new IIcon[5];
if (sprites != null) {
System.arraycopy(sprites, 0, this.sprites, 0, Math.min(sprites.length, this.sprites.length));
}
this.props = props;
}
public boolean processFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, IIcon origIcon) {
int connections = CtmConnectionHelper.getConnections(world, x, y, z, face, props.getInnerSeams());
int ctmIndex = CTM_47_BY_CONNECTIONS[connections & 0xFF];
IIcon replacement = getReplacementIcon(ctmIndex);
if (replacement != null) {
renderWholeFace(rb, world, x, y, z, face, replacement);
return true;
}
if (connections == 0 || connections == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(connections)];
if (sprite != null) {
renderWholeFace(rb, world, x, y, z, face, sprite);
return true;
}
return false;
}
renderSplitFace(rb, world, x, y, z, face, connections);
return true;
}
private IIcon getReplacementIcon(int ctmIndex) {
Int2IntMap map = props.getTileReplacementMap();
if (map == null || !map.containsKey(ctmIndex)) {
return null;
}
int spriteIndex = map.get(ctmIndex);
if (spriteIndex < 0 || spriteIndex >= sprites.length) {
return null;
}
return sprites[spriteIndex];
}
/**
* Compact 5-sprite variant:
* 0 = default / isolated / mixed
* 1 = all neighbors
* 2 = horizontal line
* 3 = vertical line
*/
private int getCompactSpriteIndex(int connections) {
if (connections == 0xFF) {
return SPRITE_ALL;
}
if ((connections & ((1 << LEFT) | (1 << RIGHT))) == ((1 << LEFT) | (1 << RIGHT))) {
return SPRITE_HORIZONTAL;
}
if ((connections & ((1 << UP) | (1 << DOWN))) == ((1 << UP) | (1 << DOWN))) {
return SPRITE_VERTICAL;
}
return SPRITE_DEFAULT;
}
private void renderSplitFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, int connections) {
Block block = world.getBlock(x, y, z);
double minX = rb.renderMinX;
double minY = rb.renderMinY;
double minZ = rb.renderMinZ;
double maxX = rb.renderMaxX;
double maxY = rb.renderMaxY;
double maxZ = rb.renderMaxZ;
for (int q = 0; q < 4; q++) {
IIcon icon = sprites[getQuadrantSprite(q, connections)];
if (icon == null) {
continue;
}
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
rb.overrideBlockTexture = icon;
renderFace(rb, block, x, y, z, face, icon);
}
rb.overrideBlockTexture = null;
rb.setRenderBounds(minX, minY, minZ, maxX, maxY, maxZ);
}
private void renderWholeFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, IIcon icon) {
Block block = world.getBlock(x, y, z);
IIcon saved = rb.overrideBlockTexture;
rb.overrideBlockTexture = icon;
try {
renderFace(rb, block, x, y, z, face, icon);
} finally {
rb.overrideBlockTexture = saved;
}
}
private void renderFace(RenderBlocks rb, Block block, int x, int y, int z, int face, IIcon icon) {
boolean originalFlip = rb.field_152631_f;
rb.field_152631_f = (face == 2 || face == 5);
try {
switch (face) {
case 0 -> rb.renderFaceYNeg(block, x, y, z, icon);
case 1 -> rb.renderFaceYPos(block, x, y, z, icon);
case 2 -> rb.renderFaceZNeg(block, x, y, z, icon);
case 3 -> rb.renderFaceZPos(block, x, y, z, icon);
case 4 -> rb.renderFaceXNeg(block, x, y, z, icon);
case 5 -> rb.renderFaceXPos(block, x, y, z, icon);
}
} finally {
rb.field_152631_f = originalFlip;
}
}
/**
* Quadrants are always interpreted as:
* 0 = top-left, 1 = top-right, 2 = bottom-right, 3 = bottom-left
* in the face-local view.
*/
private void setQuadrantBounds(RenderBlocks rb, int face, int quadrant,
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ) {
double midX = (minX + maxX) * 0.5;
double midY = (minY + maxY) * 0.5;
double midZ = (minZ + maxZ) * 0.5;
switch (face) {
case 0:
case 1:
if (quadrant == 0) rb.setRenderBounds(minX, minY, minZ, midX, maxY, midZ);
if (quadrant == 1) rb.setRenderBounds(midX, minY, minZ, maxX, maxY, midZ);
if (quadrant == 2) rb.setRenderBounds(midX, minY, midZ, maxX, maxY, maxZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, midZ, midX, maxY, maxZ);
break;
case 2:
if (quadrant == 0) rb.setRenderBounds(midX, midY, minZ, maxX, maxY, maxZ);
if (quadrant == 1) rb.setRenderBounds(minX, midY, minZ, midX, maxY, maxZ);
if (quadrant == 2) rb.setRenderBounds(minX, minY, minZ, midX, midY, maxZ);
if (quadrant == 3) rb.setRenderBounds(midX, minY, minZ, maxX, midY, maxZ);
break;
case 3:
if (quadrant == 0) rb.setRenderBounds(minX, midY, minZ, midX, maxY, maxZ);
if (quadrant == 1) rb.setRenderBounds(midX, midY, minZ, maxX, maxY, maxZ);
if (quadrant == 2) rb.setRenderBounds(midX, minY, minZ, maxX, midY, maxZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, minZ, midX, midY, maxZ);
break;
case 4:
if (quadrant == 0) rb.setRenderBounds(minX, midY, minZ, maxX, maxY, midZ);
if (quadrant == 1) rb.setRenderBounds(minX, midY, midZ, maxX, maxY, maxZ);
if (quadrant == 2) rb.setRenderBounds(minX, minY, midZ, maxX, midY, maxZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, minZ, maxX, midY, midZ);
break;
case 5:
if (quadrant == 0) rb.setRenderBounds(minX, midY, midZ, maxX, maxY, maxZ);
if (quadrant == 1) rb.setRenderBounds(minX, midY, minZ, maxX, maxY, midZ);
if (quadrant == 2) rb.setRenderBounds(minX, minY, minZ, maxX, midY, midZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, midZ, maxX, midY, maxZ);
break;
default:
break;
}
}
/**
* A quadrant is built from two cardinal sides and the diagonal between them.
*/
private int getQuadrantSprite(int quadrant, int connections) {
int sideA = QUAD_A[quadrant];
int sideB = QUAD_B[quadrant];
int diag = QUAD_DIAG[quadrant];
boolean connA = (connections & (1 << sideA)) != 0;
boolean connB = (connections & (1 << sideB)) != 0;
if (connA && connB) {
return (connections & (1 << diag)) != 0 ? SPRITE_ALL : SPRITE_MIXED;
}
if (connA ^ connB) {
int connectedBit = connA ? sideA : sideB;
return (connectedBit == LEFT || connectedBit == RIGHT) ? SPRITE_VERTICAL : SPRITE_HORIZONTAL;
}
return SPRITE_DEFAULT;
}
@Desugar
private record FaceBasis(int rightX, int rightY, int rightZ, int downX, int downY, int downZ) { }
private static final class CtmConnectionHelper {
public static int getConnections(IBlockAccess world, int x, int y, int z, int face, boolean innerSeams) {
Block self = world.getBlock(x, y, z);
FaceBasis basis = FACE_BASIS[face];
int bits = 0;
for (int i = 0; i < 8; i++) {
int dx = RIGHT_FACTOR[i] * basis.rightX + DOWN_FACTOR[i] * basis.downX;
int dy = RIGHT_FACTOR[i] * basis.rightY + DOWN_FACTOR[i] * basis.downY;
int dz = RIGHT_FACTOR[i] * basis.rightZ + DOWN_FACTOR[i] * basis.downZ;
int nx = x + dx;
int ny = y + dy;
int nz = z + dz;
if (world.getBlock(nx, ny, nz) != self) {
continue;
}
if (innerSeams && !shouldRenderInnerSeam(world, nx, ny, nz, face)) {
continue;
}
bits |= (1 << i);
}
return bits;
}
private static boolean shouldRenderInnerSeam(IBlockAccess world, int x, int y, int z, int face) {
int[] normal = RenderBlockState.NORMALS[face];
int sx = x + normal[0];
int sy = y + normal[1];
int sz = z + normal[2];
return world.getBlock(sx, sy, sz).shouldSideBeRendered(world, sx, sy, sz, face);
}
}
}
@@ -28,7 +28,7 @@ import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import com.prupe.mcpatcher.mal.resource.TexturePackAPI;
import com.prupe.mcpatcher.mal.tile.TileLoader;
abstract class TileOverride implements Comparable<TileOverride> {
public abstract class TileOverride implements Comparable<TileOverride> {
private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
@@ -80,6 +80,7 @@ abstract class TileOverride implements Comparable<TileOverride> {
tileLoader);
case "sandstone", "top" -> override = new TileOverrideImpl.Top(properties, tileLoader);
case "repeat", "pattern" -> override = new TileOverrideImpl.Repeat(properties, tileLoader);
case "ctm_compact", "compact" -> override = new TileOverrideImpl.CTMCompact(properties, tileLoader);
default -> properties.error("unknown method \"%s\"", method);
}
@@ -18,7 +18,7 @@ import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import com.prupe.mcpatcher.mal.tile.TileLoader;
import com.prupe.mcpatcher.mal.util.WeightedIndex;
class TileOverrideImpl {
public class TileOverrideImpl {
final static class CTM extends TileOverride {
@@ -481,4 +481,49 @@ class TileOverrideImpl {
return icons[0];
}
}
final public static class CTMCompact extends TileOverride {
private final CompactConnectingCtmProperties properties;
private volatile CompactCtmQuadProcessor processor;
CTMCompact(PropertiesFile propertiesFile, TileLoader tileLoader) {
super(propertiesFile, tileLoader);
this.properties = new CompactConnectingCtmProperties(propertiesFile);
}
@Override
String getMethod() {
return "compact";
}
@Override
String checkTileMap() {
return getNumberOfTiles() == 5 ? null : "requires exactly 5 tiles";
}
@Override
boolean requiresFace() {
return true;
}
@Override
IIcon getTileWorld_Impl(RenderBlockState renderBlockState, IIcon origIcon) {
return origIcon;
}
@Override
IIcon getTileHeld_Impl(RenderBlockState renderBlockState, IIcon origIcon) {
return icons.length > 0 ? icons[0] : origIcon;
}
public CompactCtmQuadProcessor getProcessor() {
CompactCtmQuadProcessor p = this.processor;
if (p == null) {
p = new CompactCtmQuadProcessor(icons, properties);
this.processor = p;
}
return p;
}
}
}
@@ -104,6 +104,8 @@ abstract public class TileOverrideIterator implements Iterator<TileOverride> {
}
public TileOverride go(RenderBlockState renderBlockState, IIcon origIcon) {
CTMUtils.clearCurrentCompact();
this.renderBlockState = renderBlockState;
renderBlockState.setFilter(null);
currentIcon = origIcon;
@@ -121,6 +123,9 @@ abstract public class TileOverrideIterator implements Iterator<TileOverride> {
TileOverride override = next();
IIcon newIcon = getTile(override, renderBlockState, origIcon);
if (newIcon != null) {
if (override instanceof TileOverrideImpl.CTMCompact compact) {
CTMUtils.setCurrentCompact(compact);
}
lastMatchedOverride = override;
skipOverrides.add(override);
currentIcon = newIcon;
+11 -22
View File
@@ -1,11 +1,11 @@
package net.coderbot.iris;
import com.google.common.base.Throwables;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.Tags;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
@@ -13,7 +13,6 @@ import cpw.mods.fml.common.gameevent.InputEvent;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Getter;
import net.coderbot.iris.block_context.BlockContextHolder;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.celeritas.IrisCeleritasShaderProvider;
import net.coderbot.iris.compat.dh.DHCompat;
@@ -915,35 +914,25 @@ public class Iris {
ClientRegistry.registerKeyBinding(shaderpackScreenKeybind);
}
static BlockContextHolder contextHolder;
private static int getShaderMaterialOverrideId(Block block, int meta) {
if (contextHolder == null) {
final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches();
if (blockMetaMatches == null) {
return -1;
}
contextHolder = new BlockContextHolder(blockMetaMatches);
}
contextHolder.set(block, meta, (short) block.getRenderType());
return contextHolder.blockId;
}
public static void setShaderMaterialOverride(Block block, int meta) {
if (!enabled)
return;
int blockId = getShaderMaterialOverrideId(block, meta);
final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches();
if (blockMetaMatches == null)
return;
if (TessellatorManager.get() instanceof CapturingTessellator tess)
tess.setShaderBlockId(blockId);
final Int2IntMap metaMap = blockMetaMatches.get(block);
final int blockId = metaMap != null ? metaMap.get(meta) : -1;
if (TessellatorManager.get() instanceof StateAwareTessellator tess)
tess.angelica$setShaderOverrideBlockId((short) blockId);
}
public static void resetShaderMaterialOverride() {
if (!enabled)
return;
if (TessellatorManager.get() instanceof CapturingTessellator tess)
tess.setShaderBlockId(-1);
if (TessellatorManager.get() instanceof StateAwareTessellator tess)
tess.angelica$setShaderOverrideBlockId((short) -1);
}
}
@@ -1,39 +0,0 @@
package net.coderbot.iris.block_context;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import net.minecraft.block.Block;
/**
* Holds block context for shader material ID lookups.
*/
public class BlockContextHolder {
private final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches;
public int blockId;
public short renderType;
public BlockContextHolder(Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches) {
this.blockMetaMatches = blockMetaMatches;
this.blockId = -1;
this.renderType = -1;
}
/**
* Set the material ID for a block.
* @param block The block
* @param meta The block metadata
* @param renderType The render type
*/
public void set(Block block, int meta, short renderType) {
Int2IntMap metaMap = this.blockMetaMatches != null ? this.blockMetaMatches.get(block) : null;
int id = metaMap != null ? metaMap.get(meta) : -1;
this.blockId = id;
this.renderType = renderType;
}
public void reset() {
this.blockId = -1;
this.renderType = -1;
}
}
@@ -131,7 +131,7 @@ public class IrisGenericRenderProgram implements IDhApiGenericObjectShaderProgra
ProgramImages.Builder builder = ProgramImages.builder(id);
pipeline.addGbufferOrShadowSamplers(samplerBuilder, builder,
isShadowPass ? pipeline::getFlippedBeforeShadow : () -> translucent ? pipeline.getFlippedAfterTranslucent() : pipeline.getFlippedAfterPrepare(),
isShadowPass, true, true, false);
isShadowPass, false, true, false);
customUniforms.mapholderToPass(uniformBuilder, this);
this.uniforms = uniformBuilder.buildUniforms();
this.customUniforms = customUniforms;
@@ -122,7 +122,7 @@ public class IrisLodRenderProgram {
ProgramImages.Builder builder = ProgramImages.builder(id);
pipeline.addGbufferOrShadowSamplers(samplerBuilder, builder,
isShadowPass ? pipeline::getFlippedBeforeShadow : () -> translucent ? pipeline.getFlippedAfterTranslucent() : pipeline.getFlippedAfterPrepare(),
isShadowPass, true, true, false);
isShadowPass, false, true, false);
customUniforms.mapholderToPass(uniformBuilder, this);
this.uniforms = uniformBuilder.buildUniforms();
this.customUniforms = customUniforms;
@@ -75,7 +75,7 @@ public class HandRenderer {
return mc.entityRenderer.debugViewDirection <= 0 &&
mc.gameSettings.thirdPersonView == 0 &&
!camera.getEntity().isPlayerSleeping() &&
!camera.isSleeping() &&
!mc.gameSettings.hideGUI &&
!GameModeUtil.isSpectator() &&
!mc.playerController.enableEverythingIsScrewedUpMode();
@@ -1,5 +1,6 @@
package net.coderbot.iris.shaderpack;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -58,14 +59,42 @@ public class IdMap {
private Map<NamespacedId, BlockRenderType> blockRenderTypeMap;
IdMap(Path shaderPath, ShaderPackOptions shaderPackOptions, Iterable<StringPair> environmentDefines) {
itemIdMap = loadProperties(shaderPath, "item.properties", shaderPackOptions, environmentDefines).map(IdMap::parseItemIdMap).orElse(Object2IntMaps.emptyMap());
// Check if block.properties has a dedicated 1.7.10 section
String rawBlockProperties = readProperties(shaderPath, "block.properties");
boolean hasLegacySection = rawBlockProperties != null
&& rawBlockProperties.contains("MC_VERSION") && rawBlockProperties.contains("10710");
entityIdMap = loadProperties(shaderPath, "entity.properties", shaderPackOptions, environmentDefines).map(IdMap::parseEntityIdMap).orElse(Object2IntMaps.emptyMap());
Iterable<StringPair> resolvedDefines;
if (hasLegacySection) {
// Pack has a 1.7.10 section
resolvedDefines = environmentDefines;
loadProperties(shaderPath, "block.properties", shaderPackOptions, environmentDefines).ifPresent(blockProperties -> {
blockPropertiesMap = parseBlockMap(blockProperties, "block.", "block.properties");
blockRenderTypeMap = parseRenderTypeMap(blockProperties, "layer.", "block.properties");
});
} else {
// No 1.7.10 section, use modern MC_VERSION and convert entries
ArrayList<StringPair> modernDefines = new ArrayList<>();
for (StringPair define : environmentDefines) {
if (!"MC_VERSION".equals(define.getKey())) {
modernDefines.add(define);
}
}
loadProperties(shaderPath, "block.properties", shaderPackOptions, environmentDefines).ifPresent(blockProperties -> {
blockPropertiesMap = parseBlockMap(blockProperties, "block.", "block.properties");
blockRenderTypeMap = parseRenderTypeMap(blockProperties, "layer.", "block.properties");
});
String modernVersion = AngelicaConfig.modernFallbackMcVersion > 0
? String.valueOf(AngelicaConfig.modernFallbackMcVersion) : "260101";
modernDefines.add(new StringPair("MC_VERSION", modernVersion));
resolvedDefines = modernDefines;
loadProperties(shaderPath, "block.properties", shaderPackOptions, modernDefines).ifPresent(blockProperties -> {
blockPropertiesMap = parseBlockMap(blockProperties, "block.", "block.properties");
blockRenderTypeMap = parseRenderTypeMap(blockProperties, "layer.", "block.properties");
blockPropertiesMap = LegacyIdMap.convertModernBlockEntries(blockPropertiesMap);
});
}
itemIdMap = loadProperties(shaderPath, "item.properties", shaderPackOptions, resolvedDefines).map(IdMap::parseItemIdMap).orElse(Object2IntMaps.emptyMap());
entityIdMap = loadProperties(shaderPath, "entity.properties", shaderPackOptions, resolvedDefines).map(IdMap::parseEntityIdMap).orElse(Object2IntMaps.emptyMap());
// TODO: Properly override block render layers
@@ -3,6 +3,7 @@ package net.coderbot.iris.shaderpack;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.Function;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
@@ -10,6 +11,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class LegacyIdMap {
private static final ImmutableList<String> COLORS =
@@ -19,6 +21,11 @@ public class LegacyIdMap {
private static final ImmutableList<String> WOOD_TYPES =
ImmutableList.of("oak", "birch", "jungle", "spruce", "acacia", "dark_oak");
private static final Map<String, String> MODERN_TO_LEGACY_BLOCK_IDS = Map.of(
"grass", "tallgrass",
"grass_block", "grass"
);
public static void addLegacyValues(Int2ObjectMap<List<BlockEntry>> blockIdMap) {
add(blockIdMap, 1, block("stone"), block("granite"), block("diorite"), block("andesite"));
add(blockIdMap, 2, block("grass_block"));
@@ -78,6 +85,32 @@ public class LegacyIdMap {
// TODO: 76 -> redstone_torch (on)
}
public static Int2ObjectMap<List<BlockEntry>> convertModernBlockEntries(Int2ObjectMap<List<BlockEntry>> blockIdMap) {
Int2ObjectMap<List<BlockEntry>> converted = new Int2ObjectOpenHashMap<>();
blockIdMap.forEach((intId, entries) -> {
List<BlockEntry> convertedEntries = new ArrayList<>(entries.size());
for (BlockEntry entry : entries) {
NamespacedId id = entry.getId();
if (!"minecraft".equals(id.getNamespace())) {
convertedEntries.add(entry);
continue;
}
String convertedName = MODERN_TO_LEGACY_BLOCK_IDS.get(id.getName());
if (convertedName == null) {
convertedEntries.add(entry);
continue;
}
convertedEntries.add(new BlockEntry(new NamespacedId(id.getNamespace(), convertedName), entry.getMetas()));
}
converted.put(intId, Collections.unmodifiableList(convertedEntries));
});
return converted;
}
private static BlockEntry block(String name) {
return new BlockEntry(new NamespacedId("minecraft", name), Collections.emptySet());
}
@@ -4,6 +4,7 @@ options.chunks=%s 区块
options.entityShadows=实体阴影
options.droppedItems=%s 个物品
options.droppedItems.max=无限制
sodium.option_impact.low=低
sodium.option_impact.medium=中
sodium.option_impact.high=高
@@ -12,19 +13,20 @@ sodium.option_impact.varies=视情况而定
sodium.options.pages.quality=质量
sodium.options.pages.performance=性能
sodium.options.pages.advanced=高级
sodium.options.pages.appearance=外观
sodium.options.view_distance.tooltip=可视距离 - 控制地形的渲染距离。距离越短,地形渲染越少,帧率(FPS)提升越明显。
sodium.options.simulation_distance.tooltip=模拟距离 - 控制地形和实体加载和更新的距离。更短的距离可以减少内部服务器的负载并提高帧率(FPS)。
sodium.options.brightness.tooltip=控制游戏的亮度(伽玛值)。
sodium.options.clouds.name=云
sodium.options.clouds.tooltip=控制云是否可见。
sodium.options.gui_scale.tooltip=设置要用于用户界面的最大缩放倍率。如果设为“自动(0)”,则始终使用最大的缩放倍率。
sodium.options.gui_scale.tooltip=设置要用于用户界面的最大缩放倍率。如果设为"自动",则始终使用最大的缩放倍率。
sodium.options.fullscreen.tooltip=如果启用,游戏将以全屏显示(如果支持)。
sodium.options.v_sync.tooltip=如果启用,游戏的帧率(FPS)将与显示器的刷新率同步,从而以整体输入延迟为代价获得更流畅的体验。如果你的系统性能较差,此设置反而可能会降低帧率。
sodium.options.fps_limit.tooltip=限制帧率(FPS)。这有助于降低多任务处理时的功耗和一般系统负载。如果启用了垂直同步,除非它低于显示器的刷新率,否则此设置将被忽略。
sodium.options.fps_limit.tooltip=限制最大帧率(FPS)。这有助于降低多任务处理时的功耗和一般系统负载。如果启用了垂直同步,除非它低于显示器的刷新率,否则此设置将被忽略。
sodium.options.view_bobbing.tooltip=如果启用,玩家的视角将在移动时不断摇晃。在游戏时感到晕眩的玩家可以禁用此项以提升游戏体验。
sodium.options.attack_indicator.tooltip=控制攻击指示器在屏幕上的显示位置。
sodium.options.autosave_indicator.tooltip=如果启用,当游戏将存档保存到硬盘时将显示一个提示。
sodium.options.graphics_quality.tooltip=默认图形质量控制一些传统选项,并且是模组兼容性所必需的。如果下面的选项保留为“默认”,则它们将使用此设置。
sodium.options.graphics_quality.tooltip=默认图形质量控制一些传统选项,并且是模组兼容性所必需的。如果下面的选项保留为"默认",则它们将使用此设置。
sodium.options.clouds_quality.tooltip=控制天空中云渲染的质量。
sodium.options.weather_quality.tooltip=控制雨雪效果的质量。
sodium.options.leaves_quality.name=树叶质量
@@ -38,7 +40,7 @@ sodium.options.particle_quality.tooltip=控制可同时在画面上渲染的最
sodium.options.smooth_lighting.tooltip=控制方块是否平滑光照和渲染。这会略微增加重新加载区块所需的时间,但不会影响帧率(FPS)。
sodium.options.biome_blend.value=%s 个方块
sodium.options.biome_blend.tooltip=生物群系颜色互相平滑混合的距离,单位为方块。使用更高的值会极大增加区块加载或更新的用时,而对画质的提升会逐渐递减。
sodium.options.entity_distance.tooltip=实体渲染的距离修正乘数。更小的值会减少能被渲染的实体离玩家的最大距离,更大的值则会增加这个距离。
sodium.options.entity_distance.tooltip=实体渲染距离的修正系数。更小的值会减少可被渲染的实体离玩家的最大距离,更大的值则会增加这个距离。
sodium.options.entity_shadows.tooltip=如果启用,将在生物和其他实体下方渲染简单的阴影。
sodium.options.vignette.name=晕影
sodium.options.vignette.tooltip=如果启用,将在玩家的画面上显示晕影效果。除非你的显卡像素填充率有限,否则这不太可能对帧率产生影响。
@@ -46,11 +48,11 @@ sodium.options.mipmap_levels.tooltip=控制多级纹理的生成等级。较高
sodium.options.use_chunk_multidraw.name=使用区块多重绘制
sodium.options.use_chunk_multidraw.tooltip=多重绘制允许通过更少的绘制请求来进行多区块渲染,在大大减少了渲染世界时的CPU负载的同时也可能提高了GPU利用率。此选项可能会让一些图形驱动程序出现问题,因此如果遇到相关问题,应尝试禁用它。
sodium.options.use_vertex_objects.name=使用顶点数组对象
sodium.options.use_vertex_objects.tooltip=通过将如何渲染有关顶点数据的信息移动到驱动程序中,让游戏可以优化相同对象的重复渲染,有助于提高性能。通常不应该禁此选项,除非你使用了不兼容的模组。
sodium.options.use_vertex_objects.tooltip=通过将与顶点数据渲染方式相关的信息存储到驱动程序中,以优化对相同对象的重复渲染并提高性能。通常应保持开启,除非你使用了不兼容的模组
sodium.options.use_block_face_culling.name=使用方块面剔除
sodium.options.use_block_face_culling.tooltip=如果启用,则只会渲染面向相机一侧的方块面。这可以在早期忽略大量方块面的渲染,从而节省GPU上的内存带宽和时间。某些资源包可能会在此选项上出现问题,因此如果你在方块中看到空洞,请尝试禁用它。
sodium.options.use_compact_vertex_format.name=使用压缩顶点格式
sodium.options.use_compact_vertex_format.tooltip=如果启用,将使用更紧凑的顶点格式来渲染方块。这可以显著降低显存使用量和带宽要求,尤其是集显,但由于降低了位置和纹理坐标属性的精度,可能会与一些资源包发生冲突。
sodium.options.use_compact_vertex_format.tooltip=如果启用,将使用更紧凑的顶点格式来渲染方块。这能显著降低显存占用和带宽需求,对集成显卡尤其有效。但由于降低了顶点位置和纹理坐标的精度,可能与部分纹理包不兼容
sodium.options.use_fog_occlusion.name=使用雾遮挡剔除
sodium.options.use_fog_occlusion.tooltip=如果启用,确定被雾效果完全遮挡的块将不会被渲染,有助于提升性能。当雾效果较重时(例如在水下时),提升可能会更加显著,但在某些情况下可能会导致天空和雾之间出现不良的视觉瑕疵。
sodium.options.use_entity_culling.name=使用实体剔除
@@ -67,7 +69,7 @@ sodium.options.cpu_render_ahead_limit.value=%s 帧
sodium.options.allow_direct_memory_access.name=允许直接内存访问
sodium.options.allow_direct_memory_access.tooltip=如果启用,一些关键代码将被允许通过直接读写内存来提高性能。这通常会大大减少区块和实体渲染的CPU开销,但会使诊断某些错误和崩溃变得更加困难。只有您被要求这么做或知道在做什么时,才应该禁用它。
sodium.options.ignore_driver_blacklist.name=忽略驱动程序冲突
sodium.options.ignore_driver_blacklist.tooltip=如果启用,已知的硬件/驱动程序配置不兼容性将被忽略,以允许您启用那些可能导致游戏问题的配置.请勿更改此项除非您确切知道您在做什么.更改此选项后,必须保存、关闭,然后重新打开设置菜单。
sodium.options.ignore_driver_blacklist.tooltip=如果启用,已知的硬件/驱动程序配置不兼容性将被忽略,以允许您启用那些可能导致游戏问题的配置。除非您确切知道您在做什么,否则请勿更改此项。更改此选项后,必须保存、关闭,然后重新打开设置菜单。
sodium.options.enable_memory_tracing.name=启用内存跟踪
sodium.options.enable_memory_tracing.tooltip=调试功能。如果启用,堆栈跟踪将与内存分配一起收集,有助于在检测到内存泄漏时改进诊断信息。
sodium.options.performance_impact_string=性能影响:%s
@@ -83,16 +85,23 @@ sodium.options.always_defer_chunk_updates.name=始终推迟区块更新
sodium.options.always_defer_chunk_updates.tooltip=如果启用,渲染将永远不会等待区块更新完成,即使它们很重要。这在某些情况下可以极大地提高帧率(FPS),但可能会在游戏时产生明显的视觉滞后。
sodium.options.use_no_error_context.name=使用禁用错误检查的上下文
sodium.options.use_no_error_context.tooltip=如果启用,OpenGL日志将不包含详细错误信息。这可能略微提高性能,但也可能因为无法精确处理OpenGL错误而导致游戏崩溃。如遇到不明原因的突然崩溃,则应禁用此选项。
sodium.options.async_occlusion_mode.name=异步遮挡剔除
sodium.options.async_occlusion_mode.tooltip=控制哪些渲染通道使用异步的遮挡剔除。ONLY_SHADOW(默认)仅对阴影通道进行异步遮挡,保持主渲染列表同步。EVERYTHING 对所有通道进行异步,在快速移动视角时可能导致1帧的可见性延迟。NONE 完全禁用异步遮挡
sodium.options.use_occlusion_culling.name=遮挡剔除
sodium.options.use_occlusion_culling.tooltip=如果启用,被其他区块遮挡的区块将不会被渲染。禁用此选项会显著降低性能,但可用于调试。
sodium.options.threads.value=%s 线程
sodium.options.default=默认
sodium.options.buttons.undo=撤销
sodium.options.buttons.apply=应用
sodium.options.buttons.donate=给JellySquid买杯咖啡!
options.dynamic_fov=动态视场
options.dynamic_fov.tooltip=如果启用,游戏视场将根据玩家速度发生变化.
options.dynamic_fov.tooltip=如果启用,游戏视场将根据玩家速度发生变化。
options.framerate=%s FPS
iris.shaders.toggled=光影包已切换为 %s!
iris.shaders.disabled=光影包已禁用!
options.iris.apply=应用
options.iris.refresh=刷新
options.iris.openShaderPackFolder=打开光影包文件夹……
@@ -101,15 +110,15 @@ options.iris.shaderPackList=光影包列表……
options.iris.refreshShaderPacks=刷新光影包
options.iris.shaderPackSelection=光影包……
options.iris.shaderPackSelection.title=光影
options.iris.shaderPackSelection.addedPack=已添加光影包“%s”
options.iris.shaderPackSelection.addedPack=已添加光影包"%s"
options.iris.shaderPackSelection.addedPacks=已添加 %s 个光影包
options.iris.shaderPackSelection.failedAdd=文件不是有效的光影包
options.iris.shaderPackSelection.failedAddSingle=“%s”不是有效的光影包
options.iris.shaderPackSelection.copyError=无法添加光影包“%s”
options.iris.shaderPackSelection.copyErrorAlreadyExists=“%s”已经在光影包文件夹里了!
options.iris.shaderPackSelection.failedAddSingle="%s"不是有效的光影包
options.iris.shaderPackSelection.copyError=无法添加光影包"%s"
options.iris.shaderPackSelection.copyErrorAlreadyExists="%s"已经在光影包文件夹里了!
options.iris.shaderPackOptions.tooManyFiles=无法导入多个光影设置文件!
options.iris.shaderPackOptions.failedImport=导入光影设置文件“%s”失败
options.iris.shaderPackOptions.importedSettings=已从“%s”导入光影设置
options.iris.shaderPackOptions.failedImport=导入光影设置文件"%s"失败
options.iris.shaderPackOptions.importedSettings=已从"%s"导入光影设置
options.iris.shaders.disabled=光影:已禁用
options.iris.shaders.enabled=光影:已启用
options.iris.shaders.nonePresent=光影:无光影包
@@ -129,19 +138,46 @@ options.iris.shadowDistance.enabled=更改渲染阴影的最远距离。超出
options.iris.shadowDistance.disabled=当前光影包已重设阴影渲染距离,无法更改
options.iris.gui.hide=隐藏 GUI
options.iris.gui.show=显示 GUI
options.dynamic_lights=动态光源
options.dynamic_lights.tooltip=启用动态光源,模式选择:OFF-关闭,FASTEST-500ms延迟,FAST-250ms延迟,FANCY-无延迟
options.dynamic_lights.tooltip=启用动态光源,模式选择:OFF-关闭,FASTEST-500ms延迟,FAST-250ms延迟,FANCY-50ms延迟,REALTIME-无延迟
options.dynamic_lights_shader_force=强制启用动态光源
options.dynamic_lights_shader_force.tooltip=启用-启用光影时仍显示动态光源;禁用-启用光影时禁用动态光源。
options.dynamic_lights_frustum_culling=视锥剔除
options.dynamic_lights_frustum_culling.tooltip=仅为屏幕上可见的光源重建区块。减少相机后方光源的GPU工作负载。
options.dynamic_lights_adaptive_ticking=自适应更新
options.dynamic_lights_adaptive_ticking.tooltip=降低远处和屏幕外光源的更新频率。与全局模式设置协同工作。
options.dynamic_lights_slow_dist=低频更新距离
options.dynamic_lights_slow_dist.tooltip=此距离外的光源更新频率降为每0.25秒一次。推荐:32格(约2个区块)
options.dynamic_lights_slower_dist=更低频更新距离
options.dynamic_lights_slower_dist.tooltip=此距离外的光源更新频率降为每0.5秒一次。推荐:64格(约4个区块)
options.dynamic_lights_background_dist=视野外更新距离
options.dynamic_lights_background_dist.tooltip=超出此距离后,光源更新频率降为每秒一次。同时适用于玩家镜头后方的光源。
options.dynamic_lights_cull_timeout=剔除超时(刻)
options.dynamic_lights_cull_timeout.tooltip=不可见的区块在强制重建前可等待的最大刻数。值越低响应越快但GPU负载越高
options.dynamiclights.page=动态光源
options.dynamiclights.entities=配置实体……
options.dynamiclights.entities.title=动态光源 - 实体
options.dynamiclights.entities.info=切换哪些实体类型发射动态光。部分条目用于模组兼容性。
options.dynamiclights.entities.search=搜索(@模组, $开启, $关闭)
options.dynamiclights.entity.tooltip=切换 %s 实体的动态光照。如果此类型实体产生不必要光照,禁用以提升性能。
options.angelica.search.no_results=无匹配选项
options.angelica.fontconfig=字体配置...
options.angelica.fontconfig.title=字体配置
options.angelica.fontconfig.currentfonts=主字体:§e%s§r 备用字体:§b%s
options.angelica.fontconfig.searchprompt=§7§o搜索字体...
options.angelica.fontconfig.testareaprompt=§7§o测试区域...
options.angelica.fontconfig.testareainfo=在下方输入文本以预览渲染效果。可使用&进行格式化(如&l代表粗体)
options.angelica.fontconfig.testareaexample=&d&lHerobrine&r was &7&m&oNOT&r &nhere
options.angelica.fontconfig.disable_custom_font=禁用自定义字体
options.angelica.fontconfig.enable_custom_font=启用自定义字体
options.angelica.fontconfig.reset_values=恢复默认配置
options.angelica.fontconfig.no_fonts_warning=自定义字体在您的系统上不可用,查看日志以获取详情
options.angelica.fontconfig.font_quality=字体渲染质量:%s
options.angelica.fontconfig.shadow_offset=阴影偏移:%s
options.angelica.fontconfig.shadow_copies=阴影副本数:%s
options.angelica.fontconfig.bold_copies=粗体副本数:%s
options.angelica.fontconfig.glyph_scale=字体缩放:%s
options.angelica.fontconfig.glyph_aspect=字体宽高比:%s
options.angelica.fontconfig.whitespace_scale=空白字符大小:%s
@@ -150,21 +186,53 @@ options.angelica.fontconfig.font_aa_strength=字体抗锯齿强度:%s
options.angelica.fontconfig.aamode.aa_none=抗锯齿:关闭
options.angelica.fontconfig.aamode.aa_4x=抗锯齿:4x MSAA
options.angelica.fontconfig.aamode.aa_16x=抗锯齿:16x MSAA
options.angelica.fontconfig.custom_font_scale=自定义字体缩放:%s
options.angelica.fontconfig.shadow_offset_unicode=Unicode阴影偏移:%s
options.angelica.fontconfig.font_quality.tooltip=自定义字体渲染质量,使其不那么像素化,但会牺牲一些性能与内存占用。
options.angelica.fontconfig.shadow_offset.tooltip=字体衬底阴影偏移的距离。同时影响§l粗体§r文本的绘制方式;如果看到重影的文本,请调低此值。
options.angelica.fontconfig.glyph_scale.tooltip=字体大小的缩放比例。极端值可能使游戏难以游玩。
options.angelica.fontconfig.glyph_aspect.tooltip=字体的的宽高比例
options.angelica.fontconfig.shadow_offset.tooltip=字体衬底阴影偏移的距离。同时影响§l粗体§r文本的绘制方式。
options.angelica.fontconfig.shadow_copies.tooltip=在文本后方绘制的阴影数量,逐层偏移以营造伪3D效果
options.angelica.fontconfig.bold_copies.tooltip=渲染粗体文本时绘制的文本副本数量。使用较细的自定义字体时可改善粗体文本的外观
options.angelica.fontconfig.glyph_scale.tooltip=每个字符的缩放比例。极端值可能使游戏难以游玩
options.angelica.fontconfig.glyph_aspect.tooltip=每个字符的宽高比例
options.angelica.fontconfig.whitespace_scale.tooltip=空白字符(如空格)的缩放比例
options.angelica.fontconfig.glyph_spacing.tooltip=字符之间的间距
options.angelica.fontconfig.font_aa_strength.tooltip=抗锯齿所使用的采样间距。过大可能导致字体看起来模糊,过小可能导致锯齿。请根据您的实际情况选择最佳值。
options.angelica.fontconfig.aamode.tooltip=抗锯齿模式。可以显著改善缩放或斜角观看文本的外观和可读性,但会牺牲一些性能。
options.angelica.fontconfig.custom_font_scale.tooltip=影响自定义字体缩放的系数。影响自定义字体缩放的系数。如果使用自定义字体后,文本相较于默认字体看起来过小或过大,请调整此项
options.angelica.fontconfig.shadow_offset_unicode.tooltip=仅应用于Unicode字体的阴影偏移系数。例如,如果您了使用替换Unicode字体为仿原版风格的纹理包,请将此项设为1.0x
options.angelica.hudCaching=HUD 缓存
options.angelica.hudCaching.tooltip=将HUD缓存到帧缓冲区并在帧间复用以减少渲染开销。需要在启动时启用HUD缓存。[实验性]
options.angelica.droppedItemLimit=掉落物渲染数量上限
options.angelica.droppedItemLimit.tooltip=将被渲染的掉落物的最大数量。较小的值可以提高性能。
options.angelica.mobSpawnerRenderDistance=刷怪笼渲染距离
options.angelica.mobSpawnerRenderDistance.tooltip=刷怪笼内旋转实体的渲染距离
options.angelica.itemdisplaylistcount=物品渲染器缓存限制
options.angelica.itemdisplaylistcount.tooltip=优化物品渲染器中的缓存数量上限。较高的值将显著提高性能,但可能导致显存占用增加
options.angelica.itemdisplaylistcount.tooltip=优化物品渲染器中的VBO缓存数量上限。较高的值将显著提高性能,但可能导致显存占用增加
options.angelica.threadedChunkBuilding=多线程区块构建
options.angelica.threadedChunkBuilding.tooltip=如果启用,区块网格化将多线程并行执行,以显著提高区块构建速度。如果您遇到使用自定义方块渲染的模组出现渲染问题,请禁用此项。需要重新加载渲染器才能生效
pack.iris.select.title=选择
pack.iris.configure.title=配置
label.iris.true=开启
label.iris.false=关闭
sodium.options.use_render_pass_optimization.name=渲染通道优化
sodium.options.use_render_pass_optimization.tooltip=分析贴图透明度,当贴图无需复杂渲染时自动降级渲染通道(例如:半透明降级为镂空,镂空降级为实体)。减少绘制调用和过度绘制开销
options.angelica.aggressiveChunkLoading=更激进地区块加载
options.angelica.aggressiveChunkLoading.tooltip=区块加载后立即渲染,即使相邻区块尚未加载完毕。将提升最大渲染距离处区块的加载速度。在相邻区块加载后会自动重建。适用于渲染距离较低的服务器。
options.angelica.disablef3=禁用F3附加信息显示
options.angelica.disablef3.tooltip=禁用Angelica在F3调试界面的附加信息。
sodium.options.multidraw_mode.name=批量绘制模式
sodium.options.multidraw_mode.tooltip=控制地形渲染的绘制命令的调度方式。§l直接§r使用glMultiDrawElementsBaseVertex(默认,GL 3.2+)。§l间接§r使用GPU间接渲染(GL 4.3+),可能在某些硬件上减少CPU开销。§l单独§r发出单独的绘制调用。
sodium.options.multidraw_mode.direct=直接
sodium.options.multidraw_mode.indirect=间接
sodium.options.multidraw_mode.individual=单独
sodium.options.upload_method.name=流式上传方法
sodium.options.upload_method.tooltip=更改数据流式传输到GPU的方式。某些选项可能对某些驱动程序带来更好的性能
sodium.options.upload_method.buffer_data=完整更新 (BufferData)
sodium.options.upload_method.buffer_subdata=部分更新 (BufferSubData)
sodium.options.upload_method.map_buffer=内存映射 (MapBufferRange)
sodium.options.enable_deferred_batching.name=延迟粒子渲染
sodium.options.enable_deferred_batching.tooltip=按层合并粒子绘制调用,减少绘制次数。如果粒子渲染不正确请禁用此项
@@ -142,8 +142,12 @@ public abstract class MixinForgeHooksClient_CoreProfile {
private static void angelica$reportContextFailure(Exception e) {
LOGGER.error("FATAL: Failed to create OpenGL core profile context.");
LOGGER.error("Error: {}", e != null ? e.getMessage() : "unknown");
try {
LOGGER.error("GPU: {}, Driver: {}", GLStateManager.glGetString(GL11.GL_RENDERER), GLStateManager.glGetString(GL11.GL_VERSION));
} catch (Exception ignored) {}
if (Display.isCreated()) {
try {
LOGGER.error("GPU: {}, Driver: {}", GLStateManager.glGetString(GL11.GL_RENDERER), GLStateManager.glGetString(GL11.GL_VERSION));
} catch (Exception ignored) {}
} else {
LOGGER.error("GPU info: not available (no GL context)");
}
}
}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.mixins.early.angelica;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.client.rendering.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
@@ -1,9 +1,12 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.client.rendering.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.ITessellatorData;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import net.minecraft.client.renderer.Tessellator;
import org.spongepowered.asm.mixin.Intrinsic;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -13,12 +16,43 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
* except when GTNHLib needs to intercept (display list compilation, DirectTessellator capture).
*/
@Mixin(Tessellator.class)
public class MixinTessellator_CoreProfile {
public class MixinTessellator_CoreProfile implements ITessellatorData {
@Shadow public boolean isDrawing;
@Shadow public int vertexCount;
@Shadow public int[] rawBuffer;
@Shadow public int rawBufferIndex;
@Shadow public int rawBufferSize;
@Shadow public int drawMode;
@Shadow public boolean hasTexture;
@Shadow public boolean hasColor;
@Shadow public boolean hasNormals;
@Shadow public boolean hasBrightness;
@Shadow public void reset() {}
public void angelica$reset() {
reset();
}
@Inject(method = "draw", at = @At("HEAD"), cancellable = true)
private void angelica$coreProfileDraw(CallbackInfoReturnable<Integer> cir) {
// Let GTNHLib handle display list compilation and DirectTessellator capture
if (TessellatorManager.shouldInterceptDraw((Tessellator)(Object)this)) return;
cir.setReturnValue(TessellatorStreamingDrawer.draw((Tessellator)(Object)this));
cir.setReturnValue(TessellatorStreamingDrawer.draw((ITessellatorData) this));
}
@Override public boolean isDrawing() { return isDrawing; }
@Override public void setDrawing(boolean drawing) { isDrawing = drawing; }
@Override public int getVertexCount() { return vertexCount; }
@Override public int[] getRawBuffer() { return rawBuffer; }
@Override public int getRawBufferIndex() { return rawBufferIndex; }
@Override public int getRawBufferSize() { return rawBufferSize; }
@Override public void setRawBufferSize(int size) { rawBufferSize = size; }
@Override public void setRawBuffer(int[] buffer) { rawBuffer = buffer; }
@Override public int getDrawMode() { return drawMode; }
@Override public boolean hasTexture() { return hasTexture; }
@Override public boolean hasColor() { return hasColor; }
@Override public boolean hasNormals() { return hasNormals; }
@Override public boolean hasBrightness() { return hasBrightness; }
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.startup;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.client.rendering.TextureTracker;
import com.gtnewhorizons.angelica.client.rendering.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.compat.DriverCompatabilityCheck;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
@@ -10,12 +10,6 @@ import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(value = EntityRenderer.class, priority = 990)
public abstract class MixinEntityRenderer {
@ModifyConstant(method = "renderWorld", constant = @Constant(doubleValue = 128.0D), expect = 2)
double notFine$modifyCloudHeightCheck(double original) {
return SettingsManager.cloudTranslucencyCheck;
}
@ModifyArg(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false), index = 3)
private float notFine$modifyFarPlane(float original) {
return Math.max(original, SettingsManager.minimumFarPlaneDistance);
@@ -1,34 +1,48 @@
package com.gtnewhorizons.angelica.mixins.early.notfine.optimization;
import net.minecraft.client.renderer.entity.Render;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.client.renderer.tileentity.RenderItemFrame;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = RenderItemFrame.class)
public abstract class MixinRenderItemFrame extends Render {
@Mixin(RenderItemFrame.class)
public class MixinRenderItemFrame {
private EntityItem cachedEntityItem;
@Unique
private EntityItem notfine$cachedEntityItem;
@Redirect(
method = "func_82402_b(Lnet/minecraft/entity/item/EntityItemFrame;)V",
at = @At(
value = "NEW",
target = "(Lnet/minecraft/world/World;DDDLnet/minecraft/item/ItemStack;)Lnet/minecraft/entity/item/EntityItem;",
ordinal = 0
)
)
public EntityItem cacheEntityItem(World world, double x, double y, double z, ItemStack itemstack) {
if(cachedEntityItem == null) {
cachedEntityItem = new EntityItem(world, 0.0D, 0.0D, 0.0D, itemstack);
ordinal = 0))
private EntityItem cacheEntityItem(World world, double x, double y, double z, ItemStack itemstack) {
if (notfine$cachedEntityItem == null) {
notfine$cachedEntityItem = new EntityItem(world, x, y, z, itemstack);
} else {
cachedEntityItem.setEntityItemStack(itemstack);
notfine$cachedEntityItem.setWorld(world);
notfine$cachedEntityItem.setEntityItemStack(itemstack);
}
return cachedEntityItem;
return notfine$cachedEntityItem;
}
@Inject(
method = "func_82402_b(Lnet/minecraft/entity/item/EntityItemFrame;)V",
at = @At(
value = "INVOKE",
target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V",
remap = false,
shift = At.Shift.AFTER))
private void clearWorldRef(CallbackInfo ci, @Local(name = "entityitem") EntityItem entityitem) {
entityitem.setWorld(null);
entityitem.setEntityItemStack(null);
}
}
@@ -6,6 +6,9 @@ import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.prupe.mcpatcher.ctm.CTMUtils;
import com.prupe.mcpatcher.ctm.CompactCtmQuadProcessor;
import com.prupe.mcpatcher.ctm.TileOverrideImpl;
import cpw.mods.fml.client.registry.RenderingRegistry;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -17,6 +20,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraft.world.IBlockAccess;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
@@ -177,4 +181,89 @@ public abstract class MixinRenderBlocks {
@Local(ordinal = 0) Tessellator tessellator) {
((StateAwareTessellator)tessellator).angelica$setAppliedAo(false);
}
@Shadow
public IBlockAccess blockAccess;
@Unique
private boolean compactCtmRendering = false;
@Unique
private CompactCtmQuadProcessor getCompactProcessor() {
TileOverrideImpl.CTMCompact compact = CTMUtils.getCurrentCompact();
if (compact == null) {
return null;
}
return compact.getProcessor();
}
private void handleCompactCtmFace(int face, Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
// recursion protection
if (compactCtmRendering) {
return;
}
if (this.blockAccess == null) {
return;
}
CompactCtmQuadProcessor processor = getCompactProcessor();
if (processor == null) {
return;
}
compactCtmRendering = true;
try {
boolean handled = processor.processFace(
(RenderBlocks)(Object)this,
this.blockAccess,
MathHelper.floor_double(x),
MathHelper.floor_double(y),
MathHelper.floor_double(z),
face,
icon
);
if (handled) {
ci.cancel();
}
} finally {
compactCtmRendering = false;
}
}
@Inject(method = "renderFaceYNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(0, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceYPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(1, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceZNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(2, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceZPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(3, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceXNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(4, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceXPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(5, block, x, y, z, icon, ci);
}
@Inject(method = "renderStandardBlock(Lnet/minecraft/block/Block;III)Z", at = @At("RETURN"))
private void compactCtm_resetAfterBlock(Block block, int x, int y, int z, CallbackInfoReturnable<Boolean> cir) {
CTMUtils.clearCurrentCompact();
}
}
@@ -14,6 +14,12 @@ public class MixinTessellator implements StateAwareTessellator {
@Unique
private final IntArrayList vertexStates = new IntArrayList();
@Unique
private final IntArrayList shaderOverrideBlockIds = new IntArrayList();
@Unique
private short currentShaderOverrideBlockId = -1;
@Unique
private boolean appliedAo;
@@ -33,11 +39,14 @@ public class MixinTessellator implements StateAwareTessellator {
state |= StateAwareTessellator.RENDERED_WITH_VANILLA_AO;
}
this.vertexStates.add(state);
this.shaderOverrideBlockIds.add(currentShaderOverrideBlockId);
}
@Inject(method = "reset", at = @At("RETURN"))
private void resetVertexStates(CallbackInfo ci) {
this.vertexStates.clear();
this.shaderOverrideBlockIds.clear();
this.currentShaderOverrideBlockId = -1;
}
@Override
@@ -49,4 +58,14 @@ public class MixinTessellator implements StateAwareTessellator {
public int[] angelica$getVertexStates() {
return this.vertexStates.elements();
}
@Override
public int[] angelica$getShaderOverrideBlockIds() {
return this.shaderOverrideBlockIds.elements();
}
@Override
public void angelica$setShaderOverrideBlockId(short blockId) {
this.currentShaderOverrideBlockId = blockId;
}
}
@@ -6,7 +6,9 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import jss.notfine.core.SettingsManager;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.CloudSetting;
import net.coderbot.iris.compat.dh.DHCompat;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.pipeline.HandRenderer;
@@ -26,7 +28,9 @@ import net.minecraft.entity.EntityLivingBase;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -79,6 +83,12 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe
private int iris$alwaysRenderSky(GameSettings instance) {
return Math.max(instance.renderDistanceChunks, 4);
}
@ModifyConstant(method = "renderWorld(FJ)V", constant = @Constant(doubleValue = 128.0D), expect = 2)
private double iris$alwaysRenderCloudsLate(double cloudHeightCheck) {
return IrisApi.getInstance().isShaderPackInUse() ? Double.NEGATIVE_INFINITY : SettingsManager.cloudTranslucencyCheck;
}
@Inject(method = "renderWorld(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderSky(F)V"))
private void iris$beginSky(float partialTicks, long startTime, CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
// Use CUSTOM_SKY until levelFogColor is called as a heuristic to catch FabricSkyboxes.
@@ -94,7 +104,9 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe
@WrapOperation(method = "renderWorld(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;renderCloudsCheck(Lnet/minecraft/client/renderer/RenderGlobal;F)V"))
private void iris$clouds(EntityRenderer instance, RenderGlobal rg, float partialTicks, Operation<Void> original, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
pipeline.get().setPhase(WorldRenderingPhase.CLOUDS);
original.call(instance, rg, partialTicks);
if (pipeline.get().getCloudSetting() != CloudSetting.OFF) {
original.call(instance, rg, partialTicks);
}
pipeline.get().setPhase(WorldRenderingPhase.NONE);
}
+37
View File
@@ -0,0 +1,37 @@
plugins {
id("com.gtnewhorizons.gtnhconvention")
}
minecraft {
extraRunJvmArguments.add("-Dumbra.dumpClass=true")
}
tasks.processResources {
val projectVersion = project.version.toString()
inputs.property("version", projectVersion)
filesMatching("META-INF/rfb-plugin/*") {
expand("version" to projectVersion)
}
}
val embedOnly: Configuration by configurations
val shadowImplementation: Configuration by configurations
tasks.shadowJar {
dependsOn(embedOnly)
from(embedOnly.map(::zipTree))
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mergeServiceFiles()
minimize {
exclude(project(":glsm"))
exclude(project(":lwjgl3-backend"))
}
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
}
+40
View File
@@ -0,0 +1,40 @@
configurations {
// Embed in JAR without relocation (compile only, shadowed into JAR)
embedOnly
embedOnly.canBeConsumed = false
embedOnly.canBeResolved = true
compileClasspath.extendsFrom(embedOnly)
runtimeClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
testRuntimeClasspath.extendsFrom(embedOnly)
}
// Exclude self-reference from all configurations
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
}
dependencies {
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
compileOnly('org.jetbrains:annotations:26.0.2')
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.42")
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-32.g7dd88a4-GTNH") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
}
+47
View File
@@ -0,0 +1,47 @@
gtnh.settings.blowdryerTag = 0.2.2
modName = Umbra
modId = umbra
modGroup = com.gtnewhorizons.umbra
useModGroupForPublishing = false
autoUpdateBuildScript = false
minecraftVersion = 1.7.10
forgeVersion = 10.13.4.1614
channel = stable
mappingsVersion = 12
remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/
developmentEnvironmentUserName = Developer
enableModernJavaSyntax = jvmDowngrader
jvmDowngraderStubsProvider = gtnhlib
jvmDowngraderMultiReleaseVersions = 17, 21, 25
enableGenericInjection = true
generateGradleTokenClass = com.gtnewhorizons.umbra.Tags
gradleTokenVersion = VERSION
coreModClass = loading.UmbraTweaker
containsMixinsAndOrCoreModOnly = false
usesMixins = true
separateMixinSourceSet = mixin
mixinsPackage = mixins
usesShadowedDependencies = true
minimizeShadowedDependencies = true
relocateShadowedDependencies = true
includeWellKnownRepositories = true
usesMavenPublishing = true
disableSpotless = true
org.gradle.configuration-cache = true
org.gradle.jvmargs = -Xmx2g
org.gradle.parallel = true
+12
View File
@@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
toolchainVersion=25
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+93
View File
@@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+26
View File
@@ -0,0 +1,26 @@
pluginManagement {
repositories {
maven {
// RetroFuturaGradle
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
mavenContent {
includeGroup("com.gtnewhorizons")
includeGroupByRegex("com\\.gtnewhorizons\\..+")
}
}
gradlePluginPortal()
mavenCentral()
mavenLocal()
}
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.24")
}
include("glsm")
project(":glsm").projectDir = file("../glsm")
include("lwjgl3-backend")
project(":lwjgl3-backend").projectDir = file("../lwjgl3-backend")
@@ -0,0 +1,50 @@
package com.gtnewhorizons.umbra;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import com.gtnewhorizons.umbra.proxy.CommonProxy;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod(modid = UmbraMod.MOD_ID, name = "Umbra", version = Tags.VERSION,
dependencies = "required-after:gtnhlib@[0.8.21,);before:lwjgl3ify@[1.5.3,)",
acceptableRemoteVersions = "*")
public class UmbraMod {
public static final String MOD_ID = "umbra";
public static final Logger LOGGER = LogManager.getLogger("Umbra");
@SidedProxy(
clientSide = "com.gtnewhorizons.umbra.proxy.ClientProxy",
serverSide = "com.gtnewhorizons.umbra.proxy.CommonProxy")
public static CommonProxy proxy;
private static boolean isDisabled() {
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("umbra.disabled"));
}
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
if (isDisabled()) {
LOGGER.info("Umbra is disabled (Angelica detected), skipping preInit");
return;
}
proxy.preInit(event);
}
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
if (isDisabled()) return;
proxy.init(event);
}
@Mod.EventHandler
public void postInit(FMLPostInitializationEvent event) {
if (isDisabled()) return;
proxy.postInit(event);
}
}
@@ -0,0 +1,115 @@
package com.gtnewhorizons.umbra.client.rendering;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.mitchej123.glsm.GLStateManagerService;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.nio.IntBuffer;
public class UmbraGLStateManagerService implements GLStateManagerService {
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
private IntBuffer getIntArrayBuffer(int size) {
if (intArrayBuffer.capacity() < size) {
intArrayBuffer = BufferUtils.createIntBuffer(size);
}
intArrayBuffer.clear();
return intArrayBuffer;
}
@Override public int getPriority() { return 100; }
@Override public int glGetInteger(int pname) { return GLStateManager.glGetInteger(pname); }
@Override public String glGetString(int pname) { return GLStateManager.glGetString(pname); }
@Override public int glGenFramebuffers() { return GLStateManager.glGenFramebuffers(); }
@Override public void glDeleteFramebuffers(int fb) { GLStateManager.glDeleteFramebuffers(fb); }
@Override public void glBindFramebuffer(int target, int fb) { GLStateManager.glBindFramebuffer(target, fb); }
@Override public int glCheckFramebufferStatus(int target) { return GLStateManager.glCheckFramebufferStatus(target); }
@Override public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
GLStateManager.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override public int glCreateShader(int type) { return GLStateManager.glCreateShader(type); }
@Override public void glCompileShader(int shader) { GLStateManager.glCompileShader(shader); }
@Override public int glGetShaderi(int shader, int pname) { return GLStateManager.glGetShaderi(shader, pname); }
@Override public String glGetShaderInfoLog(int shader, int maxLen) { return GLStateManager.glGetShaderInfoLog(shader, maxLen); }
@Override public void glDeleteShader(int shader) { GLStateManager.glDeleteShader(shader); }
@Override public int glCreateProgram() { return GLStateManager.glCreateProgram(); }
@Override public void glAttachShader(int program, int shader) { GLStateManager.glAttachShader(program, shader); }
@Override public void glLinkProgram(int program) { GLStateManager.glLinkProgram(program); }
@Override public int glGetProgrami(int program, int pname) { return GLStateManager.glGetProgrami(program, pname); }
@Override public void glUseProgram(int program) { GLStateManager.glUseProgram(program); }
@Override public void glDeleteProgram(int program) { GLStateManager.glDeleteProgram(program); }
@Override public int glGetUniformLocation(int program, CharSequence name) { return GLStateManager.glGetUniformLocation(program, name); }
@Override public void glUniform1i(int location, int value) { GLStateManager.glUniform1i(location, value); }
@Override public int glGetAttribLocation(int program, CharSequence name) { return GLStateManager.glGetAttribLocation(program, name); }
@Override public void glBindAttribLocation(int program, int index, CharSequence name) { GLStateManager.glBindAttribLocation(program, index, name); }
@Override public int glGenVertexArrays() { return UniversalVAO.genVertexArrays(); }
@Override public void glBindVertexArray(int array) { GLStateManager.glBindVertexArray(array); }
@Override public void glDeleteVertexArrays(int array) { GLStateManager.glDeleteVertexArrays(array); }
@Override public int glGenBuffers() { return GLStateManager.glGenBuffers(); }
@Override public void glBindBuffer(int target, int buffer) { GLStateManager.glBindBuffer(target, buffer); }
@Override public void glDeleteBuffers(int buffer) { GLStateManager.glDeleteBuffers(buffer); }
@Override public int glGenTextures() { return GLStateManager.glGenTextures(); }
@Override public void glGenTextures(int[] textures) {
final IntBuffer buffer = getIntArrayBuffer(textures.length);
GLStateManager.glGenTextures(buffer);
buffer.get(textures);
}
@Override public void glDeleteTextures(int texture) { GLStateManager.glDeleteTextures(texture); }
@Override public void glDeleteTextures(int[] textures) {
final IntBuffer buffer = getIntArrayBuffer(textures.length);
buffer.put(textures);
buffer.flip();
GLStateManager.glDeleteTextures(buffer);
}
@Override public void glActiveTexture(int texture) { GLStateManager.glActiveTexture(texture); }
@Override public int glGetTexLevelParameteri(int target, int level, int pname) { return GLStateManager.glGetTexLevelParameteri(target, level, pname); }
@Override public int glGetTexLevelParameter(int target, int level, int pname) { return GLStateManager.glGetTexLevelParameteri(target, level, pname); }
@Override public void glCopyTexSubImage2D(int target, int level, int xo, int yo, int x, int y, int w, int h) {
GLStateManager.glCopyTexSubImage2D(target, level, xo, yo, x, y, w, h);
}
@Override public void glPixelStorei(int pname, int param) { GLStateManager.glPixelStorei(pname, param); }
@Override public void enableCullFace() { GLStateManager.enableCull(); }
@Override public void disableCullFace() { GLStateManager.disableCull(); }
@Override public void enableBlend() { GLStateManager.enableBlend(); }
@Override public void disableBlend() { GLStateManager.disableBlend(); }
@Override public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcA, int dstA) { GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA); }
@Override public void enableDepthTest() { GLStateManager.enableDepthTest(); }
@Override public void disableDepthTest() { GLStateManager.disableDepthTest(); }
@Override public void glDepthFunc(int func) { GLStateManager.glDepthFunc(func); }
@Override public void glDepthMask(boolean flag) { GLStateManager.glDepthMask(flag); }
@Override public void glViewport(int x, int y, int w, int h) { GLStateManager.glViewport(x, y, w, h); }
@Override public void glColorMask(boolean r, boolean g, boolean b, boolean a) { GLStateManager.glColorMask(r, g, b, a); }
@Override public void glClearColor(float r, float g, float b, float a) { GLStateManager.glClearColor(r, g, b, a); }
@Override public void glClear(int mask) { GLStateManager.glClear(mask); }
@Override public void clear(int mask, boolean checkError) { GLStateManager.glClear(mask); }
@Override public void bindTexture(int texture) { GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture); }
@Override public int getActiveTexture() { return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0; }
@Override public int getActiveTextureAccessor() { return GLStateManager.getActiveTextureUnit(); }
@Override public int getBoundTexture(int internalUnit) { return GLStateManager.getBoundTextureForServerState(internalUnit); }
@Override public int getActiveBoundTexture() { return GLStateManager.getBoundTextureForServerState(); }
@Override public int getViewportWidth() { return GLStateManager.getViewportState().width; }
@Override public int getViewportHeight() { return GLStateManager.getViewportState().height; }
@Override public boolean getDepthStateMask() { return GLStateManager.getDepthState().isEnabled(); }
@Override public boolean isBlendEnabled() { return GLStateManager.getBlendMode().isEnabled(); }
@Override public void setBoundTexture(int unit, int texture) {
final int currentUnit = GLStateManager.getActiveTextureUnit();
if (currentUnit != unit) GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
if (currentUnit != unit) GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
}
@Override public int getTextureBinding(int unit) { return GLStateManager.getBoundTextureForServerState(unit); }
}
@@ -0,0 +1,75 @@
package com.gtnewhorizons.umbra.client.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.mitchej123.glsm.RenderSystemService;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
public class UmbraRenderSystemService implements RenderSystemService {
private final int[] shaderTextures = new int[16];
private final float[] shaderColor = {1f, 1f, 1f, 1f};
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
private float shaderFogStart = 0f;
private float shaderFogEnd = 0f;
private int fogShape = 0;
private float shaderLineWidth = 1f;
private final Matrix4f projectionMatrix = new Matrix4f();
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
@Override public int getPriority() { return 100; }
@Override public void glActiveTexture(int texture) { GLStateManager.glActiveTexture(texture); }
@Override public void bindTexture(int texture) { GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture); }
@Override public void enableCullFace() { GLStateManager.enableCull(); }
@Override public void disableCullFace() { GLStateManager.disableCull(); }
@Override public void enableBlend() { GLStateManager.enableBlend(); }
@Override public void disableBlend() { GLStateManager.disableBlend(); }
@Override public void defaultBlendFunc() { GLStateManager.defaultBlendFunc(); }
@Override public void blendFuncSeparate(int srcRGB, int dstRGB, int srcA, int dstA) { GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcA, dstA); }
@Override public void setUnknownBlendState() { }
@Override public void enableDepthTest() { GLStateManager.enableDepthTest(); }
@Override public void disableDepthTest() { GLStateManager.disableDepthTest(); }
@Override public void depthFunc(int func) { GLStateManager.glDepthFunc(func); }
@Override public void depthMask(boolean flag) { GLStateManager.glDepthMask(flag); }
@Override public void glViewport(int x, int y, int w, int h) { GLStateManager.glViewport(x, y, w, h); }
@Override public void glClearColor(float r, float g, float b, float a) { GLStateManager.glClearColor(r, g, b, a); }
@Override public void clear(int mask, boolean checkError) { GLStateManager.glClear(mask); }
@Override public void glUniform1i(int loc, int val) { GLStateManager.glUniform1i(loc, val); }
@Override public void glUniformMatrix3(int loc, boolean transpose, FloatBuffer val) { GLStateManager.glUniformMatrix3(loc, transpose, val); }
@Override public void glUniformMatrix4(int loc, boolean transpose, FloatBuffer val) { GLStateManager.glUniformMatrix4(loc, transpose, val); }
@Override public void assertOnRenderThread() { }
@Override public void assertOnRenderThreadOrInit() { }
@Override public void setShaderTexture(int slot, int id) { if (slot >= 0 && slot < 16) shaderTextures[slot] = id; }
@Override public int getShaderTexture(int slot) { return (slot >= 0 && slot < 16) ? shaderTextures[slot] : 0; }
@Override public void setShaderColor(float r, float g, float b, float a) { shaderColor[0]=r; shaderColor[1]=g; shaderColor[2]=b; shaderColor[3]=a; }
@Override public float[] getShaderColor() { return shaderColor.clone(); }
@Override public void setShaderLineWidth(float w) { shaderLineWidth = w; }
@Override public float getShaderLineWidth() { return shaderLineWidth; }
@Override public void setShaderFogColor(float r, float g, float b, float a) { shaderFogColor[0]=r; shaderFogColor[1]=g; shaderFogColor[2]=b; shaderFogColor[3]=a; }
@Override public float[] getShaderFogColor() { return shaderFogColor.clone(); }
@Override public void setShaderFogStart(float start) { shaderFogStart = start; }
@Override public float getShaderFogStart() { return shaderFogStart; }
@Override public void setShaderFogEnd(float end) { shaderFogEnd = end; }
@Override public float getShaderFogEnd() { return shaderFogEnd; }
@Override public void setFogShape(int shape) { fogShape = shape; }
@Override public int getFogShape() { return fogShape; }
@Override public Matrix4f getProjectionMatrix() { return new Matrix4f(projectionMatrix); }
@Override public void setProjectionMatrixOrth(Matrix4f m) {
projectionMatrix.set(m);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
projectionMatrix.get(projectionMatrixBuffer);
projectionMatrixBuffer.rewind();
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
}
@Override public void setProjectionMatrixOrigin(Matrix4f m) { setProjectionMatrixOrth(m); }
}
@@ -0,0 +1,136 @@
package com.gtnewhorizons.umbra.loading;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
import com.gtnewhorizons.umbra.loading.shared.AngelicaDetector;
import com.gtnewhorizons.umbra.mixins.Mixins;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import net.minecraft.launchwrapper.Launch;
import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Client-side coremod logic. Handles Angelica detection, transformer exclusions,
* mixin registration, dependency verification, and late tweaker registration.
*/
public class UmbraClientTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
private static Boolean OBF_ENV;
private final boolean disabled;
@SuppressWarnings("unchecked")
public UmbraClientTweaker() {
Launch.classLoader.addTransformerExclusion("com.gtnewhorizons.umbra.loading");
Launch.classLoader.addClassLoaderExclusion("com.gtnewhorizons.angelica.glsm.redirect");
// Detect Angelica -- if present, disable Umbra entirely
if (AngelicaDetector.isPresent()) {
FMLRelaunchLog.info("[Umbra] Angelica detected, Disabling Umbra");
Launch.blackboard.put("umbra.disabled", Boolean.TRUE);
disabled = true;
return;
}
disabled = false;
if (Boolean.TRUE.equals(Launch.blackboard.get("umbra.rfbPluginLoaded"))) {
addLwjgl3ifyExclusions();
}
verifyDependencies();
// Register early redirector if RFB is not loaded (RFB handles its own registration)
if (!Boolean.TRUE.equals(Launch.blackboard.get("umbra.rfbPluginLoaded"))) {
final String earlyTransformer = "com.gtnewhorizons.umbra.loading.fml.transformers.EarlyRedirectorTransformer";
Launch.classLoader.registerTransformer(earlyTransformer);
FMLRelaunchLog.info("[Umbra] Registered early redirector transformer");
}
}
private static void addLwjgl3ifyExclusions() {
final var handle = SharedConfig.getRfbTransformers().stream()
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
.findFirst()
.orElse(null);
if (handle != null) {
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
handle.exclusions().add(exclusion);
}
}
}
private static void verifyDependencies() {
DependencyVerifier.verify(UmbraClientTweaker.class, List.of(
new DependencyVerifier.Check(
"/com/gtnewhorizon/gtnhlib/client/renderer/PrimitiveExtractor.class",
"Missing dependency: Umbra requires GTNHLib 0.8.21+! Download: https://modrinth.com/mod/gtnhlib"),
new DependencyVerifier.Check(
"/xyz/wagyourtail/jvmdg/exc/MissingStubError.class",
"GTNHLib is outdated: Umbra requires GTNHLib 0.9.0+! Download: https://modrinth.com/mod/gtnhlib")
));
}
@Override
public String[] getASMTransformerClass() {
if (disabled) return new String[0];
return new String[0]; // Redirector registered via late tweaker, not here
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
@SuppressWarnings("unchecked")
public void injectData(Map<String, Object> data) {
if (disabled) return;
OBF_ENV = (Boolean) data.get("runtimeDeobfuscationEnabled");
// Register the late tweaker (handles narrowing + full redirector registration)
if (!Boolean.TRUE.equals(Launch.blackboard.get("umbra.rfbPluginLoaded"))) {
final List<String> tweakClasses = org.spongepowered.asm.launch.GlobalProperties.get(
MixinServiceLaunchWrapper.BLACKBOARD_KEY_TWEAKCLASSES);
if (tweakClasses != null) {
tweakClasses.add("com.gtnewhorizons.umbra.loading.fml.tweakers.UmbraLateTweaker");
FMLRelaunchLog.info("[Umbra] Registered late tweaker");
}
}
}
@Override
public String getAccessTransformerClass() {
return null;
}
@Override
public String getMixinConfig() {
if (disabled) return null;
return "mixins.umbra.early.json";
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
if (disabled) return Collections.emptyList();
return IMixins.getEarlyMixins(Mixins.class, loadedCoreMods);
}
public static boolean isObfEnv() {
if (OBF_ENV == null) throw new IllegalStateException("OBF_ENV not yet initialized");
return OBF_ENV;
}
}
@@ -0,0 +1,76 @@
package com.gtnewhorizons.umbra.loading;
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Umbra coremod entry point. Detects Angelica and delegates to client tweaker.
*/
@IFMLLoadingPlugin.MCVersion("1.7.10")
public class UmbraTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader {
private final IFMLLoadingPlugin loadingPlugin;
private final IEarlyMixinLoader mixinLoader;
public UmbraTweaker() {
if (FMLLaunchHandler.side().isClient()) {
final UmbraClientTweaker clientTweaker = new UmbraClientTweaker();
loadingPlugin = clientTweaker;
mixinLoader = clientTweaker;
} else {
loadingPlugin = null;
mixinLoader = null;
FMLRelaunchLog.info("[Umbra] Server side detected, skipping client initialization");
}
}
@Override
public String[] getASMTransformerClass() {
return loadingPlugin != null ? loadingPlugin.getASMTransformerClass() : new String[0];
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {
if (loadingPlugin != null) {
loadingPlugin.injectData(data);
}
}
@Override
public String getAccessTransformerClass() {
return null;
}
@Override
public String getMixinConfig() {
if (mixinLoader != null) {
return mixinLoader.getMixinConfig();
}
return null;
}
@Override
public List<String> getMixins(Set<String> loadedCoreMods) {
if (mixinLoader != null) {
return mixinLoader.getMixins(loadedCoreMods);
}
return Collections.emptyList();
}
}
@@ -0,0 +1,29 @@
package com.gtnewhorizons.umbra.loading.fml.transformers;
import com.gtnewhorizons.angelica.glsm.loading.EarlyRedirectorCore;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import net.minecraft.launchwrapper.IClassTransformer;
/**
* A scoped redirector that only transforms classes from known-misbehaving (core)mod packages.
* <p>
* Some mods prematurely load classes that call GL functions during coremod discovery/injectData.
* Those classes get missed by the late-registered {@link UmbraRedirectorTransformer}, so their
* GL calls permanently bypass GLSM.
* <p>
* Registered in {@code UmbraClientTweaker} constructor. Removed by {@code UmbraLateTweaker}
* once the full redirector is in its proper post-mixin position.
*
* @see EcosystemNarrowRules#EARLY_REDIRECTOR_TARGETS
*/
public class EarlyRedirectorTransformer implements IClassTransformer {
private final EarlyRedirectorCore impl = new EarlyRedirectorCore(
"com.gtnewhorizons.umbra.loading"
);
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
return impl.transform(transformedName, basicClass);
}
}
@@ -0,0 +1,46 @@
package com.gtnewhorizons.umbra.loading.fml.transformers;
import com.gtnewhorizons.umbra.loading.shared.UmbraClassDump;
import com.gtnewhorizons.umbra.loading.shared.transformers.UmbraRedirector;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
/** IClassTransformer wrapper for {@link UmbraRedirector} */
public class UmbraRedirectorTransformer implements IClassTransformer {
private final UmbraRedirector inner;
private final String[] exclusions;
public UmbraRedirectorTransformer() {
inner = new UmbraRedirector();
exclusions = inner.getTransformerExclusions();
}
@Override
public byte[] transform(final String className, String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
for (String exclusion : exclusions) {
if (transformedName.startsWith(exclusion)) return basicClass;
}
if (!inner.shouldTransform(basicClass)) {
return basicClass;
}
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
final boolean changed = inner.transformClassNode(transformedName, cn);
if (changed) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
UmbraClassDump.dumpClass(transformedName, basicClass, bytes, this);
return bytes;
}
return basicClass;
}
}
@@ -0,0 +1,75 @@
package com.gtnewhorizons.umbra.loading.fml.tweakers;
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower;
import cpw.mods.fml.relauncher.FMLRelaunchLog;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import java.io.File;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Set;
/**
* Late tweaker that:
* 1. Narrows overly-broad transformer exclusions from other coremods
* 2. Removes the scoped early redirector
* 3. Registers the full redirector in its proper post-mixin position
*/
public class UmbraLateTweaker implements ITweaker {
private static final String EARLY_REDIRECTOR_CLASS = "com.gtnewhorizons.umbra.loading.fml.transformers.EarlyRedirectorTransformer";
@Override
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
}
@Override
public void injectIntoClassLoader(LaunchClassLoader classLoader) {
narrowTransformerExclusions(classLoader);
}
@SuppressWarnings("unchecked")
private static void narrowTransformerExclusions(LaunchClassLoader classLoader) {
try {
final Field exceptionsField = Launch.classLoader.getClass().getDeclaredField("transformerExceptions");
exceptionsField.setAccessible(true);
final Set<String> exceptions = (Set<String>) exceptionsField.get(classLoader);
TransformerNarrower.narrow(exceptions, Launch.blackboard, "umbra", "Umbra",
EcosystemNarrowRules.ALL);
} catch (Exception e) {
FMLRelaunchLog.warning("[Umbra] Failed to narrow transformer exclusions: %s", e.getMessage());
}
}
@Override
public String getLaunchTarget() {
return null;
}
@Override
@SuppressWarnings("unchecked")
public String[] getLaunchArguments() {
// Remove the scoped early redirector and register the full one after mixins
try {
final Field transformersField = Launch.classLoader.getClass().getDeclaredField("transformers");
transformersField.setAccessible(true);
final List<IClassTransformer> transformers =
(List<IClassTransformer>) transformersField.get(Launch.classLoader);
transformers.removeIf(t -> t.getClass().getName().equals(EARLY_REDIRECTOR_CLASS));
} catch (Exception e) {
FMLRelaunchLog.warning("[Umbra] Failed to remove EarlyRedirectorTransformer: %s", e.getMessage());
}
final String transformer =
"com.gtnewhorizons.umbra.loading.fml.transformers.UmbraRedirectorTransformer";
FMLRelaunchLog.finer("[Umbra] Registering transformer %s", transformer);
Launch.classLoader.registerTransformer(transformer);
return new String[0];
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizons.umbra.loading.rfb;
import com.gtnewhorizons.umbra.loading.rfb.transformers.RFBUmbraRedirector;
import com.gtnewhorizons.umbra.loading.shared.AngelicaDetector;
import com.gtnewhorizons.retrofuturabootstrap.api.PluginContext;
import com.gtnewhorizons.retrofuturabootstrap.api.RetroFuturaBootstrap;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbPlugin;
import net.minecraft.launchwrapper.Launch;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class UmbraRfbPlugin implements RfbPlugin {
@Override
public void onConstruction(@NotNull PluginContext ctx) {
Launch.blackboard.put("umbra.rfbPluginLoaded", Boolean.TRUE);
Launch.classLoader.addClassLoaderExclusion("com.gtnewhorizons.angelica.glsm.redirect.");
}
@Override
public @NotNull RfbClassTransformer @Nullable [] makeTransformers() {
// Detect Angelica -- if present, don't register any transformers
if (AngelicaDetector.isPresent()) {
Launch.blackboard.put("umbra.disabled", Boolean.TRUE);
return null;
}
final boolean isServer = (null == RetroFuturaBootstrap.API.launchClassLoader()
.findClassMetadata("net.minecraft.client.main.Main"));
if (isServer) {
return null;
}
return new RfbClassTransformer[] {
new RFBUmbraRedirector()
};
}
}
@@ -0,0 +1,63 @@
package com.gtnewhorizons.umbra.loading.rfb.transformers;
import com.gtnewhorizons.umbra.loading.shared.UmbraClassDump;
import com.gtnewhorizons.umbra.loading.shared.transformers.UmbraRedirector;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
import org.intellij.lang.annotations.Pattern;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.jar.Manifest;
/** RfbClassTransformer wrapper for {@link UmbraRedirector} */
public class RFBUmbraRedirector implements RfbClassTransformer {
private final UmbraRedirector inner;
public RFBUmbraRedirector() {
inner = new UmbraRedirector();
}
@Pattern("[a-z0-9-]+")
@Override
public @NotNull String id() {
return "umbra-redirector";
}
@Override
public @NotNull String @Nullable [] sortAfter() {
return new String[] {"*", "mixin:mixin"};
}
@Override
public @NotNull String @Nullable [] sortBefore() {
return new String[] {"lwjgl3ify:redirect"};
}
@Override
public @NotNull String @Nullable [] additionalExclusions() {
return inner.getTransformerExclusions();
}
@Override
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
@NotNull ClassNodeHandle classNode) {
if (!classNode.isPresent()) {
return false;
}
return inner.shouldTransform(classNode.getOriginalBytes());
}
@Override
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
final boolean changed = inner.transformClassNode(className, classNode.getNode());
if (changed) {
classNode.computeMaxs();
UmbraClassDump.dumpRFBClass(className, classNode, this);
}
}
}
@@ -0,0 +1,26 @@
package com.gtnewhorizons.umbra.loading.shared;
import net.minecraft.launchwrapper.Launch;
/**
* Detects whether Angelica is present. If so, Umbra should disable itself entirely since Angelica provides its own GLSM integration.
*/
public final class AngelicaDetector {
private static Boolean result;
public static boolean isPresent() {
if (result != null) return result;
boolean found = AngelicaDetector.class.getClassLoader().getResource("com/gtnewhorizons/angelica/loading/AngelicaTweaker.class") != null;
if (!found && Launch.blackboard != null) {
found = Boolean.TRUE.equals(Launch.blackboard.get("angelica.rfbPluginLoaded"));
}
result = found;
return found;
}
private AngelicaDetector() {}
}
@@ -0,0 +1,25 @@
package com.gtnewhorizons.umbra.loading.shared;
import com.gtnewhorizon.gtnhlib.asm.ASMUtil;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
public class UmbraClassDump {
private static final boolean DUMP_CLASS = Boolean.getBoolean("umbra.dumpClass");
public static void dumpClass(String className, byte[] originalBytes, byte[] transformedBytes, Object transformer) {
if (DUMP_CLASS) {
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
}
}
public static void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
if (DUMP_CLASS) {
final byte[] originalBytes = classNode.getOriginalBytes();
final byte[] transformedBytes = classNode.computeBytes();
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
}
}
}
@@ -0,0 +1,41 @@
package com.gtnewhorizons.umbra.loading.shared.transformers;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Mod-level wrapper around {@link GLSMRedirector}. Adds Umbra-specific transformer exclusions
* and delegates all redirect logic to GLSM.
* <p>
* THIS CLASS MIGHT BE LOADED ON A DIFFERENT CLASS LOADER,
* IT SHOULD NOT CALL ANY CODE FROM THE MAIN MOD
*/
public final class UmbraRedirector {
private final GLSMRedirector core = new GLSMRedirector();
private final String[] allExclusions;
public UmbraRedirector() {
final List<String> exclusions = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
exclusions.add("com.gtnewhorizons.umbra.loading");
exclusions.add("com.gtnewhorizons.angelica.lwjgl3.");
allExclusions = exclusions.toArray(new String[0]);
}
public String[] getTransformerExclusions() {
return allExclusions;
}
public boolean shouldTransform(byte[] basicClass) {
return core.shouldTransform(basicClass);
}
/** @return Was the class changed? */
public boolean transformClassNode(String transformedName, ClassNode cn) {
return core.transformClassNode(transformedName, cn);
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizons.umbra.mixins;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizon.gtnhmixins.builders.MixinBuilder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Mixins implements IMixins {
UMBRA_STARTUP(new MixinBuilder()
.setPhase(Phase.EARLY)
.addClientMixins(
"early.startup.MixinInitGLStateManager"
)
),
UMBRA_CORE_PROFILE(new MixinBuilder()
.setPhase(Phase.EARLY)
.addClientMixins(
"early.ffp.MixinTessellator_CoreProfile"
)
),
UMBRA_GLSM(new MixinBuilder()
.setPhase(Phase.EARLY)
.addClientMixins(
"early.glsm.MixinForgeHooksClient_CoreProfile",
"early.glsm.MixinSplashProgressCaching",
"early.glsm.MixinMinecraft_FrameHook",
"early.glsm.MixinWorldRenderer_VertexState"
)
),
;
private final MixinBuilder builder;
}
@@ -0,0 +1,23 @@
package com.gtnewhorizons.umbra.proxy;
import com.gtnewhorizons.umbra.UmbraMod;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
public class ClientProxy extends CommonProxy {
@Override
public void preInit(FMLPreInitializationEvent event) {
UmbraMod.LOGGER.info("Umbra client preInit - GLSM initialized via mixin");
}
@Override
public void init(FMLInitializationEvent event) {
UmbraMod.LOGGER.info("Umbra client init");
}
@Override
public void postInit(FMLPostInitializationEvent event) {
}
}
@@ -0,0 +1,17 @@
package com.gtnewhorizons.umbra.proxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
public class CommonProxy {
public void preInit(FMLPreInitializationEvent event) {
}
public void init(FMLInitializationEvent event) {
}
public void postInit(FMLPostInitializationEvent event) {
}
}
@@ -0,0 +1,9 @@
name=Umbra
version=${version}
additionalVersions=
className=com.gtnewhorizons.umbra.loading.rfb.UmbraRfbPlugin
transformerExclusions=com.gtnewhorizons.umbra.loading.shared;com.gtnewhorizon.gtnhlib.asm
versionConstraints=
loadBefore=
loadAfter=mixin; lwjgl3ify
loadRequires=
@@ -0,0 +1 @@
com.gtnewhorizons.umbra.client.rendering.UmbraGLStateManagerService
@@ -0,0 +1 @@
com.gtnewhorizons.umbra.client.rendering.UmbraRenderSystemService
@@ -0,0 +1,2 @@
com.mitchej123.lwjgl.lwjgl3.LWJGL3Service
com.mitchej123.lwjgl.lwjgl2.LWJGL2Service
+13
View File
@@ -0,0 +1,13 @@
[{
"modid": "umbra",
"name": "Umbra",
"description": "Umbra replaces Minecraft 1.7.10’s legacy rendering pipeline with a modern, backend-agnostic core.",
"version": "${modVersion}",
"mcversion": "${minecraftVersion}",
"url": "",
"authorList": ["GTNH"],
"credits": "",
"logoFile": "",
"screenshots": [],
"dependencies": ["gtnhlib"]
}]
@@ -0,0 +1,11 @@
{
"required": true,
"minVersion": "0.8.3-GTNH",
"refmap": "mixins.umbra.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8",
"package": "com.gtnewhorizons.umbra.mixins",
"overwrites": {
"conformVisibility": true
}
}
@@ -0,0 +1,11 @@
{
"required": true,
"minVersion": "0.8.3-GTNH",
"refmap": "mixins.umbra.refmap.json",
"target": "@env(DEFAULT)",
"compatibilityLevel": "JAVA_8",
"package": "com.gtnewhorizons.umbra.mixins",
"overwrites": {
"conformVisibility": true
}
}
@@ -0,0 +1,57 @@
package com.gtnewhorizons.umbra.mixins.early.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.ITessellatorData;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import net.minecraft.client.renderer.Tessellator;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Intercepts Tessellator.draw() to use streaming VBO+VAO for core profile rendering.
* Implements ITessellatorData so TessellatorStreamingDrawer can access Tessellator fields
* without depending on Minecraft classes.
*/
@Mixin(Tessellator.class)
public class MixinTessellator_CoreProfile implements ITessellatorData {
@Shadow public boolean isDrawing;
@Shadow public int vertexCount;
@Shadow public int[] rawBuffer;
@Shadow public int rawBufferIndex;
@Shadow public int rawBufferSize;
@Shadow public int drawMode;
@Shadow public boolean hasTexture;
@Shadow public boolean hasColor;
@Shadow public boolean hasNormals;
@Shadow public boolean hasBrightness;
@Shadow public void reset() {}
public void angelica$reset() {
reset();
}
@Inject(method = "draw", at = @At("HEAD"), cancellable = true)
private void umbra$coreProfileDraw(CallbackInfoReturnable<Integer> cir) {
if (TessellatorManager.shouldInterceptDraw((Tessellator)(Object)this)) return;
cir.setReturnValue(TessellatorStreamingDrawer.draw((ITessellatorData) this));
}
@Override public boolean isDrawing() { return isDrawing; }
@Override public void setDrawing(boolean drawing) { isDrawing = drawing; }
@Override public int getVertexCount() { return vertexCount; }
@Override public int[] getRawBuffer() { return rawBuffer; }
@Override public int getRawBufferIndex() { return rawBufferIndex; }
@Override public int getRawBufferSize() { return rawBufferSize; }
@Override public void setRawBufferSize(int size) { rawBufferSize = size; }
@Override public void setRawBuffer(int[] buffer) { rawBuffer = buffer; }
@Override public int getDrawMode() { return drawMode; }
@Override public boolean hasTexture() { return hasTexture; }
@Override public boolean hasColor() { return hasColor; }
@Override public boolean hasNormals() { return hasNormals; }
@Override public boolean hasBrightness() { return hasBrightness; }
}
@@ -0,0 +1,101 @@
package com.gtnewhorizons.umbra.mixins.early.glsm;
import net.minecraftforge.client.ForgeHooksClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.LWJGLUtil;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.PixelFormat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import javax.imageio.ImageIO;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
/**
* Requests highest GL version with core profile context.
* Required for GLSM's FFP emulation to work correctly.
*/
@Mixin(value = ForgeHooksClient.class, remap = false)
public abstract class MixinForgeHooksClient_CoreProfile {
@Unique private static final Logger LOGGER = LogManager.getLogger("Umbra");
@Shadow static int stencilBits;
/**
* @author Umbra
* @reason Request highest GL version with core profile context for GLSM
*/
@Overwrite
public static void createDisplay() throws LWJGLException {
ImageIO.setUseCache(false);
stencilBits = 8;
final PixelFormat format = new PixelFormat().withDepthBits(24).withStencilBits(stencilBits);
final boolean lwjglDebug = Boolean.getBoolean("org.lwjgl.util.Debug");
final ContextAttribs attribs = new ContextAttribs(3, 3)
.withProfileCore(true).withForwardCompatible(true).withDebug(lwjglDebug);
final MethodHandle setMajor, setMinor;
try {
final Field majorField = ContextAttribs.class.getDeclaredField("majorVersion");
final Field minorField = ContextAttribs.class.getDeclaredField("minorVersion");
majorField.setAccessible(true);
minorField.setAccessible(true);
final MethodHandles.Lookup lookup = MethodHandles.lookup();
setMajor = lookup.unreflectSetter(majorField);
setMinor = lookup.unreflectSetter(minorField);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to obtain ContextAttribs version setters", e);
}
final int platformMaxMajor = 4;
final int platformMaxMinor = (LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_MACOSX) ? 1 : 6;
// Probe from highest to lowest
Exception lastException = null;
for (int major = platformMaxMajor; major >= 3; --major) {
final int maxMinor = (major == 4) ? platformMaxMinor : 3;
final int minMinor = (major == 3) ? 3 : 0;
for (int minor = maxMinor; minor >= minMinor; --minor) {
final Exception e = umbra$tryCreate(attribs, format, setMajor, setMinor, major, minor);
if (e == null) {
LOGGER.info("Created GL {}.{} core profile context", major, minor);
return;
}
lastException = e;
}
}
LOGGER.error("FATAL: Failed to create OpenGL core profile context. Error: {}", lastException != null ? lastException.getMessage() : "unknown");
if (lastException instanceof LWJGLException lwjgl) {
throw lwjgl;
}
throw new LWJGLException("Failed to create OpenGL core profile context", lastException);
}
@Unique
private static Exception umbra$tryCreate(ContextAttribs attribs, PixelFormat format, MethodHandle setMajor, MethodHandle setMinor, int major, int minor) {
try {
setMajor.invokeExact(attribs, major);
setMinor.invokeExact(attribs, minor);
} catch (Throwable t) {
throw new RuntimeException("Failed to set ContextAttribs version", t);
}
try {
Display.create(format, attribs);
return null;
} catch (LWJGLException | RuntimeException e) {
try { Display.destroy(); } catch (Exception ignored) {}
return e;
}
}
}
@@ -0,0 +1,20 @@
package com.gtnewhorizons.umbra.mixins.early.glsm;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Per-frame hook to flush streaming buffer fences.
*/
@Mixin(Minecraft.class)
public class MixinMinecraft_FrameHook {
@Inject(method = "runGameLoop", at = @At("HEAD"))
private void umbra$onFrameStart(CallbackInfo ci) {
TessellatorStreamingDrawer.endFrame();
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.umbra.mixins.early.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Controls GLSM state during splash screen lifecycle.
* Initializes splash tessellator on start, marks splash complete on finish.
*/
@SuppressWarnings("deprecation")
@Mixin(value = cpw.mods.fml.client.SplashProgress.class, remap = false)
public class MixinSplashProgressCaching {
private static final Logger LOGGER = LogManager.getLogger("Umbra");
@Inject(method = "start", at = @At("HEAD"))
private static void umbra$initSplashTessellator(CallbackInfo ci) {
ImmediateModeRecorder.initSplashTessellator();
}
@Inject(method = "finish", at = @At("RETURN"))
private static void umbra$enableCachingOnFinish(CallbackInfo ci) {
ImmediateModeRecorder.destroySplashTessellator();
GLStateManager.markSplashComplete();
LOGGER.info("Splash Complete");
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.umbra.mixins.early.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.shader.TesselatorVertexState;
import net.minecraft.entity.EntityLivingBase;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraftforge.client.ForgeHooksClient;
@Mixin(WorldRenderer.class)
public class MixinWorldRenderer_VertexState {
@Shadow private TesselatorVertexState vertexState;
@Inject(method = "postRenderBlocks", at = @At("HEAD"), cancellable = true)
private void umbra$skipVertexStateForDirectTessellator(int pass, EntityLivingBase entity, CallbackInfo ci) {
if (Tessellator.instance instanceof DirectTessellator) {
// Can't get vertex state from DirectTessellator, draw and return
Tessellator.instance.draw();
ForgeHooksClient.onPostRenderWorld((WorldRenderer)(Object)this, pass);
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
ci.cancel();
}
}
}
@@ -0,0 +1,35 @@
package com.gtnewhorizons.umbra.mixins.early.startup;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper;
import org.lwjgl.opengl.Display;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = OpenGlHelper.class, priority = 100)
public class MixinInitGLStateManager {
@Inject(method = "initializeTextures", at = @At("RETURN"))
private static void umbra$initializeGLStateManager(CallbackInfo ci) {
final Minecraft mc = Minecraft.getMinecraft();
GLStateManager.setDrawableGL(Display.getDrawable());
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(mc.displayWidth, mc.displayHeight)
.framebufferSupported(OpenGlHelper.framebufferSupported)
.fboEnabled(mc.gameSettings.fboEnable)
.directDrawer(TessellatorStreamingDrawer::drawDirect)
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
.build());
GLSMHooks.LIGHTMAP_COORDS.addListener(event -> {
OpenGlHelper.lastBrightnessX = event.x;
OpenGlHelper.lastBrightnessY = event.y;
});
}
}