Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f43fbd4d9f | ||
|
|
dbabc4b3dc | ||
|
|
e01097264b | ||
|
|
0a922f3129 | ||
|
|
4a2dcd0b22 | ||
|
|
0db691db0b | ||
|
|
e5cd1372e7 | ||
|
|
fbd07cc575 | ||
|
|
91f2cf9dfb | ||
|
|
34a6967c71 | ||
|
|
9c295a1e22 | ||
|
|
9fd02900ef | ||
|
|
27e9a4d4fd | ||
|
|
dfc08053d5 | ||
|
|
1af03fa369 | ||
|
|
3ae3b2527d | ||
|
|
fd2a14a000 | ||
|
|
9f0f6a6232 | ||
|
|
2942c7eb84 | ||
|
|
47762f921e | ||
|
|
bdc4431760 | ||
|
|
7e1beadfb5 | ||
|
|
59a4b84023 | ||
|
|
8d53e25e0a | ||
|
|
bbf83db379 | ||
|
|
ec82280b9b | ||
|
|
d7588e4c14 |
No files matched your search
@@ -3,7 +3,7 @@
|
||||
/.idea/
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/run/
|
||||
run/
|
||||
build/
|
||||
/eclipse/
|
||||
.classpath
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -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")
|
||||
@@ -119,7 +121,7 @@ dependencies {
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
|
||||
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5634_H261"))
|
||||
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5663_H261"))
|
||||
|
||||
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
|
||||
@@ -146,6 +146,10 @@ tasks.test {
|
||||
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
if (System.getProperty("os.name").lowercase().contains("mac")
|
||||
&& System.getProperty("os.arch") == "aarch64") {
|
||||
vendor = JvmVendorSpec.AZUL
|
||||
}
|
||||
}
|
||||
|
||||
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeDepsForTest)
|
||||
|
||||
@@ -15,6 +15,9 @@ import com.gtnewhorizons.angelica.glsm.recording.DisplayListVBO;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.DisplayListVBOBuilder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import lombok.Getter;
|
||||
@@ -566,12 +569,16 @@ public class DisplayListManager {
|
||||
currentRecorder.recordDrawBuffers(count, bufs);
|
||||
}
|
||||
|
||||
// Complex commands are texture uploads - not draw barriers
|
||||
public static void recordComplexCommand(DisplayListCommand cmd) {
|
||||
if (currentRecorder == null) return;
|
||||
currentRecorder.recordComplexCommand(cmd);
|
||||
}
|
||||
|
||||
public static void recordIndexedDrawCapture(IndexedDrawCapture capture) {
|
||||
if (currentRecorder == null) return;
|
||||
currentRecorder.recordIndexedDrawCapture(capture);
|
||||
}
|
||||
|
||||
public static void recordLoadMatrix(Matrix4f matrix) {
|
||||
if (currentRecorder != null) currentRecorder.recordLoadMatrix(matrix);
|
||||
}
|
||||
@@ -580,14 +587,6 @@ public class DisplayListManager {
|
||||
if (currentRecorder != null) currentRecorder.recordLoadIdentity();
|
||||
}
|
||||
|
||||
public static void recordDrawArrays(int mode, int start, int count) {
|
||||
if (currentRecorder != null) currentRecorder.recordDrawArrays(mode, start, count);
|
||||
}
|
||||
|
||||
public static void recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
if (currentRecorder != null) currentRecorder.recordDrawElements(mode, indices_count, type, indices_buffer_offset);
|
||||
}
|
||||
|
||||
public static void recordBindVBO(int vbo) { if (currentRecorder != null) currentRecorder.recordBindVBO(vbo); }
|
||||
public static void recordBindVAO(int vao) { if (currentRecorder != null) currentRecorder.recordBindVAO(vao); }
|
||||
|
||||
@@ -822,17 +821,34 @@ public class DisplayListManager {
|
||||
final boolean hasDraws = accumulatedDraws != null && !accumulatedDraws.isEmpty();
|
||||
|
||||
if (hasCommands || hasDraws) {
|
||||
// Phase 1: Compile format-based VBOs (shared by both optimized and unoptimized paths)
|
||||
final DisplayListVBO compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
|
||||
try {
|
||||
final CommandBuffer finalBuffer = new CommandBuffer();
|
||||
CommandBufferBuilder.buildFromRawBuffer(rawCommandBuffer, accumulatedDraws, finalBuffer);
|
||||
|
||||
// Build to CommandBuffer - optimize raw buffer to final buffer
|
||||
final CommandBuffer finalBuffer = new CommandBuffer();
|
||||
CommandBufferBuilder.buildFromRawBuffer(rawCommandBuffer, accumulatedDraws, finalBuffer);
|
||||
final IndexedDrawBatchBuilder indexedBuilder = currentRecorder.getIndexedDraws();
|
||||
final List<IndexedDrawBatch> indexedBatches;
|
||||
if (indexedBuilder.isEmpty()) {
|
||||
indexedBatches = Collections.emptyList();
|
||||
} else {
|
||||
final CommandRecorder paused = pauseRecording();
|
||||
try {
|
||||
indexedBatches = indexedBuilder.build();
|
||||
} finally {
|
||||
try {
|
||||
for (IndexedDrawCapture c : indexedBuilder.getCaptures()) {
|
||||
c.freeBuffers();
|
||||
}
|
||||
} finally {
|
||||
resumeRecording(paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the recorder (and its buffer) after optimization
|
||||
currentRecorder.free();
|
||||
|
||||
compiled = new CompiledDisplayList(finalBuffer.toBuffer(), finalBuffer.getComplexObjects(), compiledBuffers);
|
||||
compiled = new CompiledDisplayList(finalBuffer.toBuffer(), finalBuffer.getComplexObjects(), compiledBuffers, indexedBatches);
|
||||
} finally {
|
||||
currentRecorder.free();
|
||||
}
|
||||
} else {
|
||||
// Free the recorder even if empty
|
||||
if (currentRecorder != null) {
|
||||
@@ -841,8 +857,10 @@ public class DisplayListManager {
|
||||
// Empty display list - per OpenGL spec, still valid after glNewList/glEndList
|
||||
compiled = CompiledDisplayList.EMPTY;
|
||||
}
|
||||
// Store the compiled list (even if empty - an empty list is still a valid list)
|
||||
displayListCache.put(glListId, compiled);
|
||||
final CompiledDisplayList previous = displayListCache.put(glListId, compiled);
|
||||
if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) {
|
||||
previous.delete();
|
||||
}
|
||||
|
||||
// Log compilation details if enabled (before context restoration changes glListId)
|
||||
if (LOG_DISPLAY_LIST_COMPILATION) {
|
||||
@@ -895,7 +913,7 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
// Merge the previous draw call if possible
|
||||
final AccumulatedDraw previous = accumulatedDraws.get(accumulatedDraws.size() - 1);
|
||||
final AccumulatedDraw previous = accumulatedDraws.getLast();
|
||||
if (previous.format == tessellator.getVertexFormat() && previous.stateGeneration == stateGeneration) {
|
||||
previous.mergeDraw(tessellator, copyLast);
|
||||
return;
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.TexImage2DCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.TexSubImage2DCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
|
||||
@@ -43,6 +44,7 @@ import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
@@ -949,10 +951,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 +1066,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 +1081,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 +1104,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 -> {
|
||||
@@ -1617,6 +1638,9 @@ public class GLStateManager {
|
||||
}
|
||||
alphaTest.enable();
|
||||
fragmentGeneration++;
|
||||
if (GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void disableAlphaTest() {
|
||||
@@ -1634,6 +1658,9 @@ public class GLStateManager {
|
||||
}
|
||||
alphaTest.disable();
|
||||
fragmentGeneration++;
|
||||
if (GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glAlphaFunc(int function, float reference) {
|
||||
@@ -1653,6 +1680,9 @@ public class GLStateManager {
|
||||
alphaState.setFunction(function);
|
||||
alphaState.setReference(reference);
|
||||
fragmentGeneration++;
|
||||
if (GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2210,15 +2240,12 @@ public class GLStateManager {
|
||||
CommandRecorder savedRecorder = null;
|
||||
final RecordMode recordMode = DisplayListManager.getRecordMode();
|
||||
if (recordMode != RecordMode.NONE) {
|
||||
if (isVAOBound()) {
|
||||
DisplayListManager.recordDrawElements(mode, indices_count, type, indices_buffer_offset);
|
||||
} else if (isVBOBound() || VertexAttribState.hasVBOBoundAttrib()) {
|
||||
final DirectTessellator result = ImmediateModeRecorder.processDrawElementsFromVBO(mode, indices_count, type, indices_buffer_offset, boundEBO);
|
||||
if (result != null) {
|
||||
DisplayListManager.addImmediateModeDraw(result);
|
||||
}
|
||||
} else {
|
||||
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
|
||||
// Core profile: a default VAO is generated at init and glBindVertexArray(0)
|
||||
// is redirected to it, so a VAO is always bound here — no fallback branches.
|
||||
DisplayListManager.flushMatrix();
|
||||
final IndexedDrawCapture capture = IndexedDrawCapture.create(mode, indices_count, type, indices_buffer_offset, boundEBO);
|
||||
if (capture != null) {
|
||||
DisplayListManager.recordIndexedDrawCapture(capture);
|
||||
}
|
||||
if (recordMode == RecordMode.COMPILE) {
|
||||
return;
|
||||
@@ -4929,12 +4956,14 @@ public class GLStateManager {
|
||||
|
||||
private static void invalidateDeletedBuffer(int buffer) {
|
||||
if (buffer == 0) return;
|
||||
if (boundVBO == buffer) {
|
||||
boundVBO = 0;
|
||||
}
|
||||
if (boundEBO == buffer) {
|
||||
boundEBO = 0;
|
||||
vaoEboMap.put(boundVAO, 0);
|
||||
if (boundVBO == buffer) boundVBO = 0;
|
||||
if (boundEBO == buffer) boundEBO = 0;
|
||||
if (boundPixelUnpackBuffer == buffer) boundPixelUnpackBuffer = 0;
|
||||
if (boundPixelPackBuffer == buffer) boundPixelPackBuffer = 0;
|
||||
|
||||
// Sweep all VAOs: an unbound VAO may later rebind with a dangling EBO id.
|
||||
for (Int2IntMap.Entry e : vaoEboMap.int2IntEntrySet()) {
|
||||
if (e.getIntValue() == buffer) e.setValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5051,14 +5080,6 @@ public class GLStateManager {
|
||||
glBindVertexArray(array);
|
||||
}
|
||||
|
||||
public static boolean isVBOBound() {
|
||||
return boundVBO != 0;
|
||||
}
|
||||
|
||||
public static boolean isVAOBound() {
|
||||
return boundVAO != 0;
|
||||
}
|
||||
|
||||
public static boolean vendorIsAMD() {
|
||||
return VENDOR == AMD;
|
||||
}
|
||||
@@ -5642,7 +5663,10 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDeleteProgram(int program) {
|
||||
if (program == 0) return;
|
||||
CompatUniformManager.onDeleteProgram(program);
|
||||
|
||||
if (activeProgram == program) activeProgram = 0;
|
||||
RENDER_BACKEND.deleteProgram(program);
|
||||
}
|
||||
|
||||
@@ -5702,6 +5726,14 @@ public class GLStateManager {
|
||||
return RENDER_BACKEND.getProgramInfoLog(program);
|
||||
}
|
||||
|
||||
public static boolean glIsProgram(int obj) {
|
||||
return RENDER_BACKEND.isProgram(obj);
|
||||
}
|
||||
|
||||
public static boolean glIsShader(int obj) {
|
||||
return RENDER_BACKEND.isShader(obj);
|
||||
}
|
||||
|
||||
public static void glBindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) { RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format); }
|
||||
public static void glMemoryBarrier(int barriers) { RENDER_BACKEND.memoryBarrier(barriers); }
|
||||
public static void glCopyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -146,42 +146,11 @@ public final class QuadConverter {
|
||||
|
||||
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
|
||||
// Read source indices from caller's EBO
|
||||
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
|
||||
final long srcAddr = memAddress0(src);
|
||||
|
||||
// Allocate output as GL_UNSIGNED_INT
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
default /* GL_UNSIGNED_BYTE */ -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
}
|
||||
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a);
|
||||
memPutInt(dstAddr + o + 4, b);
|
||||
memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b);
|
||||
memPutInt(dstAddr + o + 16, c);
|
||||
memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
memFree(src);
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
@@ -198,16 +167,8 @@ public final class QuadConverter {
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
|
||||
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
@@ -223,16 +184,8 @@ public final class QuadConverter {
|
||||
final int triIndexCount = quadCount * 6;
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 2);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
final int basePos = indices.position();
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int base = basePos + i * 4;
|
||||
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
|
||||
final long o = (long) i * 12;
|
||||
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
|
||||
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
|
||||
}
|
||||
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
|
||||
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
|
||||
}
|
||||
@@ -248,31 +201,7 @@ public final class QuadConverter {
|
||||
final long srcAddr = memAddress0(indices) + indices.position();
|
||||
|
||||
final ByteBuffer dst = memAlloc(triIndexCount * 4);
|
||||
final long dstAddr = memAddress0(dst);
|
||||
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a, b, c, d;
|
||||
switch (type) {
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
final long p = srcAddr + (long) i * 16;
|
||||
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
final long p = srcAddr + (long) i * 8;
|
||||
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
|
||||
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
|
||||
}
|
||||
case GL11.GL_UNSIGNED_BYTE -> {
|
||||
final long p = srcAddr + (long) i * 4;
|
||||
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
|
||||
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
|
||||
}
|
||||
default -> { memFree(dst); return; }
|
||||
}
|
||||
final long o = (long) i * 24;
|
||||
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
|
||||
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
|
||||
}
|
||||
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
|
||||
|
||||
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
|
||||
}
|
||||
@@ -282,14 +211,140 @@ public final class QuadConverter {
|
||||
*/
|
||||
public static void destroy() {
|
||||
if (eboId != 0) {
|
||||
RENDER_BACKEND.deleteBuffers(eboId);
|
||||
GLStateManager.glDeleteBuffers(eboId);
|
||||
eboId = 0;
|
||||
maxQuads = 0;
|
||||
}
|
||||
if (scratchEboId != 0) {
|
||||
RENDER_BACKEND.deleteBuffers(scratchEboId);
|
||||
GLStateManager.glDeleteBuffers(scratchEboId);
|
||||
scratchEboId = 0;
|
||||
scratchEboCapacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read one index at {@code srcAddr + i * sizeof(srcType)} and return its
|
||||
* zero-extended unsigned value.
|
||||
*/
|
||||
private static int readIndex(long srcAddr, int srcType, int i) {
|
||||
return switch (srcType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> memGetByte(srcAddr + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> memGetShort(srcAddr + i * 2L) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> memGetInt(srcAddr + i * 4L);
|
||||
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(srcType));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write one index at {@code dstAddr + i * sizeof(dstType)}.
|
||||
*/
|
||||
private static void writeIndex(long dstAddr, int dstType, int i, int value) {
|
||||
switch (dstType) {
|
||||
case GL11.GL_UNSIGNED_SHORT -> memPutShort(dstAddr + i * 2L, (short) value);
|
||||
case GL11.GL_UNSIGNED_INT -> memPutInt(dstAddr + i * 4L, value);
|
||||
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(dstType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read {@code quadCount} quads from {@code srcAddr} and emit 6 triangle indices
|
||||
* per quad at {@code dstAddr} using {@code GL_LAST_VERTEX_CONVENTION}:
|
||||
* {@code (a,b,d), (b,c,d)}. v3 is the provoking vertex for both triangles;
|
||||
* diagonal runs b→d. Matches Mesa's {@code do_quad} LAST path.
|
||||
*
|
||||
* <p>Caller allocates dst of size {@code quadCount * 6 * sizeof(dstType)}.
|
||||
*
|
||||
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT}
|
||||
* / {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
|
||||
* {@code GL_UNSIGNED_INT}. Widening only.
|
||||
*/
|
||||
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount) {
|
||||
triangulateQuads(srcAddr, srcType, dstAddr, dstType, quadCount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #triangulateQuads(long, int, long, int, int)} but subtracts
|
||||
* {@code minVtx} from every emitted index, rebasing to a 0-based buffer in
|
||||
* one pass.
|
||||
*/
|
||||
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount, int minVtx) {
|
||||
for (int i = 0; i < quadCount; i++) {
|
||||
final int a = readIndex(srcAddr, srcType, i * 4) - minVtx;
|
||||
final int b = readIndex(srcAddr, srcType, i * 4 + 1) - minVtx;
|
||||
final int c = readIndex(srcAddr, srcType, i * 4 + 2) - minVtx;
|
||||
final int d = readIndex(srcAddr, srcType, i * 4 + 3) - minVtx;
|
||||
final int o = i * 6;
|
||||
writeIndex(dstAddr, dstType, o, a);
|
||||
writeIndex(dstAddr, dstType, o + 1, b);
|
||||
writeIndex(dstAddr, dstType, o + 2, d);
|
||||
writeIndex(dstAddr, dstType, o + 3, b);
|
||||
writeIndex(dstAddr, dstType, o + 4, c);
|
||||
writeIndex(dstAddr, dstType, o + 5, d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy {@code count} indices from {@code srcAddr} to {@code dstAddr}, converting
|
||||
* from {@code srcType} to {@code dstType}.
|
||||
*
|
||||
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT} /
|
||||
* {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
|
||||
* {@code GL_UNSIGNED_INT}. Widening only; narrowing is rejected by
|
||||
* {@link #writeIndex(long, int, int, int) writeIndex}.
|
||||
*/
|
||||
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count) {
|
||||
widenIndices(srcAddr, srcType, dstAddr, dstType, count, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #widenIndices(long, int, long, int, int)} but subtracts
|
||||
* {@code minVtx} from every written index in one pass.
|
||||
*/
|
||||
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count, int minVtx) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
writeIndex(dstAddr, dstType, i, readIndex(srcAddr, srcType, i) - minVtx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan {@code count} indices at {@code srcAddr} of {@code srcType}. Returns
|
||||
* {@code (max << 32) | (min & 0xFFFFFFFFL)} or {@code -1L} for an unsupported
|
||||
* type or empty range.
|
||||
*/
|
||||
public static long scanMinMaxIndex(long srcAddr, int srcType, int count) {
|
||||
if (count <= 0) return -1L;
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
switch (srcType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int v = memGetByte(srcAddr + i) & 0xFF;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
}
|
||||
case GL11.GL_UNSIGNED_SHORT -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int v = memGetShort(srcAddr + i * 2L) & 0xFFFF;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
}
|
||||
case GL11.GL_UNSIGNED_INT -> {
|
||||
for (int i = 0; i < count; i++) {
|
||||
final long v = memGetInt(srcAddr + i * 4L) & 0xFFFFFFFFL;
|
||||
if (v < min) min = v;
|
||||
if (v > max) max = v;
|
||||
}
|
||||
// readIndex / writeIndex / rebase all route UINT through signed int.
|
||||
// Reject the > 2^31 slice so the caller bails rather than silently
|
||||
// emitting negative indices. MC never hits this in practice.
|
||||
if (max > Integer.MAX_VALUE) return -1L;
|
||||
}
|
||||
default -> {
|
||||
return -1L;
|
||||
}
|
||||
}
|
||||
return (max << 32) | (min & 0xFFFFFFFFL);
|
||||
}
|
||||
}
|
||||
@@ -432,7 +432,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void deleteBuffers(int buffer) {
|
||||
RENDER_BACKEND.deleteBuffers(buffer);
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
public static long getVRAM() {
|
||||
@@ -491,7 +491,7 @@ public class RenderSystem {
|
||||
|
||||
public static void destroySampler(int sampler) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
RENDER_BACKEND.deleteSamplers(sampler);
|
||||
GLStateManager.glDeleteSamplers(sampler);
|
||||
}
|
||||
|
||||
public static void samplerParameteri(int sampler, int pname, int param) {
|
||||
|
||||
@@ -178,7 +178,7 @@ public class Program {
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
RENDER_BACKEND.deleteProgram(programId);
|
||||
GLStateManager.glDeleteProgram(programId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.BlendFuncChangeEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.FogStateChangeEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.LightmapCoordsEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.ProgramChangeEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureBindEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureDeleteEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureUnitStateEvent;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
|
||||
import net.minecraftforge.eventbus.api.bus.EventBus;
|
||||
|
||||
public final class GLSMHooks {
|
||||
@@ -23,6 +17,7 @@ public final class GLSMHooks {
|
||||
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
|
||||
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
|
||||
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
|
||||
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
|
||||
|
||||
// Reusable event instances
|
||||
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
|
||||
@@ -32,6 +27,7 @@ public final class GLSMHooks {
|
||||
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
|
||||
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
|
||||
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
|
||||
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
|
||||
|
||||
private GLSMHooks() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
/**
|
||||
* AlphaTest changed - Listeners should query GLSM alpha state directly if they care what changed.
|
||||
*/
|
||||
public final class AlphaStateChangeEvent extends MutableEvent {
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class AttribSnapshot {
|
||||
|
||||
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
|
||||
public int effectiveStride() {
|
||||
return stride != 0 ? stride : size * VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
}
|
||||
|
||||
public int typeSizeBytes() {
|
||||
return VertexAttribState.Attrib.glTypeSizeBytes(type);
|
||||
}
|
||||
}
|
||||
|
||||
private final AttribDesc[] perLocation;
|
||||
private final List<ByteBuffer> allocatedBuffers;
|
||||
private boolean freed;
|
||||
|
||||
private AttribSnapshot(AttribDesc[] perLocation, List<ByteBuffer> allocatedBuffers) {
|
||||
this.perLocation = perLocation;
|
||||
this.allocatedBuffers = allocatedBuffers;
|
||||
}
|
||||
|
||||
public AttribDesc get(int location) {
|
||||
return (location >= 0 && location < perLocation.length) ? perLocation[location] : null;
|
||||
}
|
||||
|
||||
public int maxLocations() {
|
||||
return perLocation.length;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
if (freed) return;
|
||||
freed = true;
|
||||
for (ByteBuffer buf : allocatedBuffers) {
|
||||
MemoryUtilities.memFree(buf);
|
||||
}
|
||||
allocatedBuffers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot attribs over a tight vertex window {@code [firstVertex, firstVertex+vertexCount)}.
|
||||
*
|
||||
* <p>Hot path (single VBO backing every enabled attrib — the typical draw) runs without
|
||||
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
|
||||
*/
|
||||
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
|
||||
final AttribDesc[] out = new AttribDesc[VertexAttribState.MAX_ATTRIBS];
|
||||
final List<ByteBuffer> allocated = new ArrayList<>();
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
|
||||
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
|
||||
int rangeSoleVboId = 0;
|
||||
long rangeSoleStart = Long.MAX_VALUE;
|
||||
long rangeSoleEnd = Long.MIN_VALUE;
|
||||
Int2ObjectOpenHashMap<long[]> vboRanges = null;
|
||||
|
||||
final long lastVtx = (long) firstVertex + vertexCount - 1;
|
||||
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
|
||||
final VertexAttribState.Attrib a = VertexAttribState.get(i);
|
||||
if (!a.enabled || a.vboId == 0) continue;
|
||||
final int stride = a.effectiveStride();
|
||||
final long typeBytes = (long) a.size * VertexAttribState.Attrib.glTypeSizeBytes(a.type);
|
||||
final long start = a.offset + (long) firstVertex * stride;
|
||||
final long end = a.offset + lastVtx * stride + typeBytes;
|
||||
|
||||
if (vboRanges != null) {
|
||||
long[] r = vboRanges.get(a.vboId);
|
||||
if (r == null) {
|
||||
vboRanges.put(a.vboId, new long[] { start, end });
|
||||
} else {
|
||||
if (start < r[0]) r[0] = start;
|
||||
if (end > r[1]) r[1] = end;
|
||||
}
|
||||
} else if (rangeSoleVboId == 0 || rangeSoleVboId == a.vboId) {
|
||||
rangeSoleVboId = a.vboId;
|
||||
if (start < rangeSoleStart) rangeSoleStart = start;
|
||||
if (end > rangeSoleEnd) rangeSoleEnd = end;
|
||||
} else {
|
||||
// Spill: second distinct vboId. Promote the accumulated range to the map.
|
||||
vboRanges = new Int2ObjectOpenHashMap<>();
|
||||
vboRanges.put(rangeSoleVboId, new long[] { rangeSoleStart, rangeSoleEnd });
|
||||
vboRanges.put(a.vboId, new long[] { start, end });
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2 — readback cache. Same spill pattern.
|
||||
int readSoleVboId = 0;
|
||||
ByteBuffer readSoleBuf = null;
|
||||
long readSoleOffset = 0;
|
||||
Int2ObjectOpenHashMap<ByteBuffer> vboBuffers = null;
|
||||
Int2LongOpenHashMap vboReadOffsets = null;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
|
||||
final VertexAttribState.Attrib a = VertexAttribState.get(i);
|
||||
if (!a.enabled) continue;
|
||||
|
||||
if (a.vboId != 0) {
|
||||
ByteBuffer buf;
|
||||
long readOffset;
|
||||
if (vboBuffers != null) {
|
||||
buf = vboBuffers.get(a.vboId);
|
||||
readOffset = buf != null ? vboReadOffsets.get(a.vboId) : 0;
|
||||
} else if (readSoleVboId == a.vboId) {
|
||||
buf = readSoleBuf;
|
||||
readOffset = readSoleOffset;
|
||||
} else {
|
||||
buf = null;
|
||||
readOffset = 0;
|
||||
}
|
||||
|
||||
if (buf == null) {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
|
||||
final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (bufferSize <= 0) {
|
||||
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
|
||||
continue;
|
||||
}
|
||||
|
||||
final long rStart;
|
||||
final long rEnd;
|
||||
if (vboRanges != null) {
|
||||
final long[] range = vboRanges.get(a.vboId);
|
||||
rStart = range[0];
|
||||
rEnd = range[1];
|
||||
} else {
|
||||
rStart = rangeSoleStart;
|
||||
rEnd = rangeSoleEnd;
|
||||
}
|
||||
final long ro = Math.max(0, rStart);
|
||||
final int rs = (int) (Math.min(bufferSize, rEnd) - ro);
|
||||
if (rs <= 0) continue;
|
||||
|
||||
buf = MemoryUtilities.memAlloc(rs);
|
||||
GLStateManager.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, ro, buf);
|
||||
allocated.add(buf);
|
||||
readOffset = ro;
|
||||
|
||||
if (vboBuffers != null) {
|
||||
vboBuffers.put(a.vboId, buf);
|
||||
vboReadOffsets.put(a.vboId, ro);
|
||||
} else if (readSoleVboId == 0) {
|
||||
readSoleVboId = a.vboId;
|
||||
readSoleBuf = buf;
|
||||
readSoleOffset = ro;
|
||||
} else {
|
||||
vboBuffers = new Int2ObjectOpenHashMap<>();
|
||||
vboReadOffsets = new Int2LongOpenHashMap();
|
||||
vboBuffers.put(readSoleVboId, readSoleBuf);
|
||||
vboReadOffsets.put(readSoleVboId, readSoleOffset);
|
||||
vboBuffers.put(a.vboId, buf);
|
||||
vboReadOffsets.put(a.vboId, ro);
|
||||
}
|
||||
}
|
||||
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.offset, a.vboId, buf, readOffset);
|
||||
} else if (a.clientPointer != null) {
|
||||
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.clientPointer.position(), 0, a.clientPointer, 0L);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
}
|
||||
|
||||
return new AttribSnapshot(out, allocated);
|
||||
}
|
||||
}
|
||||
@@ -600,23 +600,6 @@ public final class CommandBuffer {
|
||||
writeInt(listId);
|
||||
}
|
||||
|
||||
public void writeDrawArrays(int mode, int start, int count) {
|
||||
ensureCapacity(16);
|
||||
writeInt(GLCommand.DRAW_ARRAYS);
|
||||
writeInt(mode);
|
||||
writeInt(start);
|
||||
writeInt(count);
|
||||
}
|
||||
|
||||
public void writeDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
ensureCapacity(24);
|
||||
writeInt(GLCommand.DRAW_ELEMENTS);
|
||||
writeInt(mode);
|
||||
writeInt(indices_count);
|
||||
writeInt(type);
|
||||
writeLong(indices_buffer_offset);
|
||||
}
|
||||
|
||||
public void writeBindVBO(int vbo) {
|
||||
ensureCapacity(8);
|
||||
writeInt(GLCommand.BIND_VBO);
|
||||
|
||||
@@ -530,14 +530,6 @@ public final class CommandBufferExecutor {
|
||||
ptr += 4 * MAX_DRAW_BUFFERS;
|
||||
GLStateManager.glDrawBuffers(DRAW_BUFFERS_BUFFER);
|
||||
}
|
||||
case GLCommand.DRAW_ARRAYS -> {
|
||||
GLStateManager.glDrawArrays(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8));
|
||||
ptr += 12;
|
||||
}
|
||||
case GLCommand.DRAW_ELEMENTS -> {
|
||||
GLStateManager.glDrawElements(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8), memGetLong(ptr + 12));
|
||||
ptr += 20;
|
||||
}
|
||||
case GLCommand.BIND_VBO -> {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, memGetInt(ptr));
|
||||
ptr += 4;
|
||||
|
||||
@@ -150,8 +150,6 @@ public final class CommandBufferProcessor {
|
||||
// Draw/call commands
|
||||
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
|
||||
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
|
||||
case GLCommand.DRAW_ARRAYS -> out.writeDrawArrays(raw.readInt(), raw.readInt(), raw.readInt());
|
||||
case GLCommand.DRAW_ELEMENTS -> out.writeDrawElements(raw.readInt(), raw.readInt(), raw.readInt(), raw.readLong());
|
||||
case GLCommand.BIND_VBO -> out.writeBindVBO(raw.readInt());
|
||||
case GLCommand.BIND_VAO -> out.writeBindVAO(raw.readInt());
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
@@ -11,6 +13,7 @@ import java.nio.IntBuffer;
|
||||
*/
|
||||
public final class CommandRecorder {
|
||||
private final CommandBuffer buffer;
|
||||
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
|
||||
private int commandCount;
|
||||
|
||||
public CommandRecorder() {
|
||||
@@ -26,7 +29,19 @@ public final class CommandRecorder {
|
||||
return commandCount;
|
||||
}
|
||||
|
||||
public IndexedDrawBatchBuilder getIndexedDraws() {
|
||||
return indexedDraws;
|
||||
}
|
||||
|
||||
public void recordIndexedDrawCapture(IndexedDrawCapture capture) {
|
||||
indexedDraws.add(capture);
|
||||
recordComplexCommand(capture.placeholder);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
|
||||
c.freeBuffers();
|
||||
}
|
||||
buffer.free();
|
||||
}
|
||||
|
||||
@@ -347,16 +362,6 @@ public final class CommandRecorder {
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawArrays(int mode, int start, int count) {
|
||||
buffer.writeDrawArrays(mode, start, count);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
|
||||
buffer.writeDrawElements(mode, indices_count, type, indices_buffer_offset);
|
||||
commandCount++;
|
||||
}
|
||||
|
||||
public void recordBindVBO(int vbo) {
|
||||
buffer.writeBindVBO(vbo);
|
||||
commandCount++;
|
||||
|
||||
@@ -2,12 +2,15 @@ package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
@@ -26,16 +29,18 @@ public final class CompiledDisplayList {
|
||||
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
|
||||
* This instance does nothing when rendered and is never deleted (shared singleton).
|
||||
*/
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null);
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
|
||||
|
||||
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
|
||||
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
|
||||
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
|
||||
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
|
||||
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
|
||||
this.commandBuffer = commandBuffer;
|
||||
this.complexObjects = complexObjects;
|
||||
this.ownedVbos = ownedVbos;
|
||||
this.indexedBatches = indexedBatches;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,7 +56,6 @@ public final class CompiledDisplayList {
|
||||
* Delete all resources held by this display list.
|
||||
*/
|
||||
public void delete() {
|
||||
// Delete complex objects that hold resources
|
||||
if (complexObjects != null) {
|
||||
for (Object obj : complexObjects) {
|
||||
if (obj instanceof DisplayListCommand cmd) {
|
||||
@@ -60,11 +64,18 @@ public final class CompiledDisplayList {
|
||||
}
|
||||
}
|
||||
|
||||
// Close VBOs
|
||||
// Close VBOs from immediate-mode DisplayListVBOBuilder.
|
||||
if (ownedVbos != null) {
|
||||
ownedVbos.delete();
|
||||
}
|
||||
|
||||
// Release shared (VAO, VBO, EBO) triples from IndexedDrawBatchBuilder.
|
||||
if (indexedBatches != null) {
|
||||
for (IndexedDrawBatch batch : indexedBatches) {
|
||||
batch.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Free the command buffer (off-heap memory)
|
||||
if (commandBuffer != null) {
|
||||
memFree(commandBuffer);
|
||||
@@ -89,6 +100,10 @@ public final class CompiledDisplayList {
|
||||
return ownedVbos;
|
||||
}
|
||||
|
||||
public List<IndexedDrawBatch> getIndexedBatches() {
|
||||
return indexedBatches;
|
||||
}
|
||||
|
||||
// === Test inspection methods ===
|
||||
|
||||
/**
|
||||
|
||||
@@ -97,8 +97,6 @@ public final class GLCommand {
|
||||
public static final int CALL_LIST = 102; // [cmd:4][listId:4] = 8 bytes
|
||||
|
||||
|
||||
public static final int DRAW_ARRAYS = 110; // [cmd:4][drawMode:4][start:4][count:4] = 20 bytes
|
||||
public static final int DRAW_ELEMENTS = 111; // [cmd:4][mode:4][indices_count:4][type:4][indices_buffer_offset:8] = 28 bytes
|
||||
public static final int DRAW_BUFFER = 112; // [cmd:4][mode:4] = 8 bytes
|
||||
public static final int DRAW_BUFFERS = 113; // [cmd:4][count:4][bufs:4*8] = 40 bytes (up to 8 buffers)
|
||||
/**
|
||||
@@ -219,7 +217,7 @@ public final class GLCommand {
|
||||
// Three int commands (16 bytes)
|
||||
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
|
||||
GLCommand.LIGHTF, GLCommand.LIGHTI,
|
||||
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF, GLCommand.DRAW_ARRAYS -> 16;
|
||||
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
|
||||
|
||||
// Four int commands (20 bytes)
|
||||
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
|
||||
@@ -227,8 +225,6 @@ public final class GLCommand {
|
||||
GLCommand.SCISSOR,
|
||||
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
|
||||
|
||||
case GLCommand.DRAW_ELEMENTS -> 24;
|
||||
|
||||
// DRAW_RANGE_RESTORE: 28 bytes
|
||||
case GLCommand.DRAW_RANGE_RESTORE -> 28;
|
||||
|
||||
|
||||
@@ -5,15 +5,11 @@ import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
/**
|
||||
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
|
||||
* Used both during display list compilation and for live immediate mode emulation in core profile.
|
||||
@@ -167,19 +163,14 @@ public final class ImmediateModeRecorder {
|
||||
.mapToInt(Usage::getAttributeLocation)
|
||||
.max().orElse(0);
|
||||
|
||||
private static final ByteBuffer[] scratchAttribBuffers = new ByteBuffer[NUM_ATTRIBS];
|
||||
private static final long[] scratchAttribBaseOffsets = new long[NUM_ATTRIBS];
|
||||
private static final int[] scratchVBOIds = new int[NUM_ATTRIBS];
|
||||
private static final ByteBuffer[] scratchVBOBuffers = new ByteBuffer[NUM_ATTRIBS];
|
||||
private static final long[] scratchVBOReadOffsets = new long[NUM_ATTRIBS];
|
||||
|
||||
private static final class AttribReader {
|
||||
ByteBuffer data;
|
||||
int stride;
|
||||
int baseOffset;
|
||||
int size;
|
||||
int loc;
|
||||
VertexAttribState.Attrib attrib;
|
||||
int type;
|
||||
boolean normalized;
|
||||
}
|
||||
|
||||
private static final AttribReader[] readers = new AttribReader[NUM_ATTRIBS];
|
||||
@@ -198,167 +189,32 @@ public final class ImmediateModeRecorder {
|
||||
* Reads vertex data from VBOs or client memory referenced by VertexAttribState and converts to immediate mode geometry.
|
||||
*/
|
||||
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
|
||||
final int readVBOCount = readbackAttribVBOs(first, count);
|
||||
|
||||
final AttribSnapshot snap = AttribSnapshot.snapshot(first, count);
|
||||
try {
|
||||
prepareReaders();
|
||||
prepareReaders(snap);
|
||||
if (positionReaderIndex < 0) return null;
|
||||
return readVerticesFromBuffer(mode, first, count);
|
||||
} finally {
|
||||
freeReadbackBuffers(readVBOCount);
|
||||
snap.free();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a glDrawElements call during display list recording when a VBO is bound.
|
||||
*/
|
||||
public static DirectTessellator processDrawElementsFromVBO(int mode, int indicesCount, int indexType, long indicesOffset, int eboId) {
|
||||
if (eboId == 0) return null;
|
||||
|
||||
final int readVBOCount = readbackAttribVBOs(-1, 0);
|
||||
|
||||
try {
|
||||
prepareReaders();
|
||||
if (positionReaderIndex < 0) return null;
|
||||
|
||||
final int prevEBO = GLStateManager.getBoundEBO();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
final int eboSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (eboSize <= 0) {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
return null;
|
||||
}
|
||||
|
||||
final int indexSize = VertexAttribState.Attrib.glTypeSizeBytes(indexType);
|
||||
final int eboReadOffset = (int) indicesOffset;
|
||||
final int eboReadSize = indicesCount * indexSize;
|
||||
if (eboReadOffset + eboReadSize > eboSize) {
|
||||
GLStateManager.LOGGER.warn("[EBO] Read range {}+{} exceeds buffer size {}", eboReadOffset, eboReadSize, eboSize);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
return null;
|
||||
}
|
||||
final ByteBuffer eboData = MemoryUtilities.memAlloc(eboReadSize);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboReadOffset, eboData);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
|
||||
try {
|
||||
return readVerticesFromBufferIndexed(mode, eboData, indexType, 0, indicesCount);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(eboData);
|
||||
}
|
||||
} finally {
|
||||
freeReadbackBuffers(readVBOCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data for all enabled attribs into scratchAttribBuffers.
|
||||
* For VBO-backed attribs, reads VBO data. For client-memory attribs, uses the client pointer directly.
|
||||
*/
|
||||
private static int readbackAttribVBOs(int firstVertex, int vertexCount) {
|
||||
int readVBOCount = 0;
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final boolean partialRead = firstVertex >= 0;
|
||||
|
||||
for (int i = 0; i < NUM_ATTRIBS; i++) {
|
||||
scratchAttribBuffers[i] = null;
|
||||
scratchAttribBaseOffsets[i] = 0;
|
||||
final VertexAttribState.Attrib a = VertexAttribState.get(i);
|
||||
if (!a.enabled) continue;
|
||||
|
||||
if (a.vboId != 0) {
|
||||
// VBO-backed attrib — read from GPU
|
||||
ByteBuffer existing = null;
|
||||
int existingIdx = -1;
|
||||
for (int j = 0; j < readVBOCount; j++) {
|
||||
if (scratchVBOIds[j] == a.vboId) {
|
||||
existing = scratchVBOBuffers[j];
|
||||
existingIdx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
scratchAttribBuffers[i] = existing;
|
||||
scratchAttribBaseOffsets[i] = scratchVBOReadOffsets[existingIdx];
|
||||
} else {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
|
||||
final int bufferSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (bufferSize <= 0) {
|
||||
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
|
||||
continue;
|
||||
}
|
||||
|
||||
final long readOffset;
|
||||
final int readSize;
|
||||
if (partialRead) {
|
||||
final int stride = a.effectiveStride();
|
||||
long rangeStart = a.offset + (long) firstVertex * stride;
|
||||
long rangeEnd = a.offset + (long) (firstVertex + vertexCount - 1) * stride + (long) a.size * a.typeSizeBytes();
|
||||
|
||||
for (int k = 0; k < NUM_ATTRIBS; k++) {
|
||||
if (k == i) continue;
|
||||
final VertexAttribState.Attrib ak = VertexAttribState.get(k);
|
||||
if (!ak.enabled || ak.vboId != a.vboId) continue;
|
||||
final int ks = ak.effectiveStride();
|
||||
rangeStart = Math.min(rangeStart, ak.offset + (long) firstVertex * ks);
|
||||
rangeEnd = Math.max(rangeEnd, ak.offset + (long) (firstVertex + vertexCount - 1) * ks
|
||||
+ (long) ak.size * ak.typeSizeBytes());
|
||||
}
|
||||
rangeStart = Math.max(0, rangeStart);
|
||||
rangeEnd = Math.min(bufferSize, rangeEnd);
|
||||
readOffset = rangeStart;
|
||||
readSize = (int) (rangeEnd - rangeStart);
|
||||
} else {
|
||||
readOffset = 0;
|
||||
readSize = bufferSize;
|
||||
}
|
||||
|
||||
if (readSize <= 0) continue;
|
||||
|
||||
final ByteBuffer buf = MemoryUtilities.memAlloc(readSize);
|
||||
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, readOffset, buf);
|
||||
scratchAttribBuffers[i] = buf;
|
||||
scratchAttribBaseOffsets[i] = readOffset;
|
||||
scratchVBOIds[readVBOCount] = a.vboId;
|
||||
scratchVBOReadOffsets[readVBOCount] = readOffset;
|
||||
scratchVBOBuffers[readVBOCount] = buf;
|
||||
readVBOCount++;
|
||||
}
|
||||
} else if (a.clientPointer != null) {
|
||||
// Client-memory attrib — use pointer directly (no allocation needed)
|
||||
scratchAttribBuffers[i] = a.clientPointer;
|
||||
}
|
||||
}
|
||||
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
|
||||
return readVBOCount;
|
||||
}
|
||||
|
||||
private static void freeReadbackBuffers(int readVBOCount) {
|
||||
for (int i = 0; i < readVBOCount; i++) {
|
||||
MemoryUtilities.memFree(scratchVBOBuffers[i]);
|
||||
scratchVBOBuffers[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void prepareReaders() {
|
||||
private static void prepareReaders(AttribSnapshot snap) {
|
||||
nonPositionCount = 0;
|
||||
positionReaderIndex = -1;
|
||||
|
||||
for (int i = 0; i < NUM_ATTRIBS; i++) {
|
||||
final ByteBuffer buf = scratchAttribBuffers[i];
|
||||
if (buf == null) continue;
|
||||
final VertexAttribState.Attrib a = VertexAttribState.get(i);
|
||||
final AttribSnapshot.AttribDesc d = snap.get(i);
|
||||
if (d == null) continue;
|
||||
|
||||
final AttribReader r = readers[i];
|
||||
r.data = buf;
|
||||
r.stride = a.effectiveStride();
|
||||
r.baseOffset = (int) (a.offset - scratchAttribBaseOffsets[i]);
|
||||
r.size = a.size;
|
||||
r.data = d.readBuffer();
|
||||
r.stride = d.effectiveStride();
|
||||
r.baseOffset = (int) (d.offset() - d.readBufferBaseOffset());
|
||||
r.size = d.size();
|
||||
r.loc = i;
|
||||
r.attrib = a;
|
||||
r.type = d.type();
|
||||
r.normalized = d.normalized();
|
||||
|
||||
if (i == LOC_POSITION) {
|
||||
positionReaderIndex = i;
|
||||
@@ -379,18 +235,6 @@ public final class ImmediateModeRecorder {
|
||||
return end();
|
||||
}
|
||||
|
||||
private static DirectTessellator readVerticesFromBufferIndexed(int mode, ByteBuffer eboData, int indexType, int eboOffset, int indicesCount) {
|
||||
begin(mode);
|
||||
final DirectTessellator t = tessellator();
|
||||
|
||||
for (int i = 0; i < indicesCount; i++) {
|
||||
final int index = readIndex(eboData, indexType, eboOffset, i);
|
||||
readVertexFromBuffer(t, index);
|
||||
}
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
public static int readIndex(ByteBuffer ebo, int indexType, int eboOffset, int i) {
|
||||
return switch (indexType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> ebo.get(eboOffset + i) & 0xFF;
|
||||
@@ -411,39 +255,43 @@ public final class ImmediateModeRecorder {
|
||||
final AttribReader r = readers[positionReaderIndex];
|
||||
final int base = r.baseOffset + vertexIndex * r.stride;
|
||||
t.addVertex(
|
||||
r.attrib.readComponent(r.data, base, 0),
|
||||
r.attrib.readComponent(r.data, base, 1),
|
||||
(r.size >= 3) ? r.attrib.readComponent(r.data, base, 2) : 0.0f);
|
||||
readComponent(r, base, 0),
|
||||
readComponent(r, base, 1),
|
||||
(r.size >= 3) ? readComponent(r, base, 2) : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
|
||||
if (r.loc == LOC_NORMAL) {
|
||||
t.setNormal(
|
||||
r.attrib.readComponent(r.data, base, 0),
|
||||
r.attrib.readComponent(r.data, base, 1),
|
||||
r.attrib.readComponent(r.data, base, 2));
|
||||
readComponent(r, base, 0),
|
||||
readComponent(r, base, 1),
|
||||
readComponent(r, base, 2));
|
||||
} else if (r.loc == LOC_UV0) {
|
||||
t.setTextureUV(
|
||||
r.attrib.readComponent(r.data, base, 0),
|
||||
(r.size >= 2) ? r.attrib.readComponent(r.data, base, 1) : 0.0f);
|
||||
readComponent(r, base, 0),
|
||||
(r.size >= 2) ? readComponent(r, base, 1) : 0.0f);
|
||||
} else if (r.loc == LOC_UV1) {
|
||||
if (r.attrib.type == GL11.GL_SHORT) {
|
||||
if (r.type == GL11.GL_SHORT) {
|
||||
final int s = r.data.getShort(base) & 0xFFFF;
|
||||
final int u = (r.size >= 2) ? (r.data.getShort(base + 2) & 0xFFFF) : 0;
|
||||
t.setBrightness((u << 16) | s);
|
||||
} else if (r.attrib.type == GL11.GL_FLOAT) {
|
||||
} else if (r.type == GL11.GL_FLOAT) {
|
||||
final int s = (int) r.data.getFloat(base);
|
||||
final int u = (r.size >= 2) ? (int) r.data.getFloat(base + 4) : 0;
|
||||
t.setBrightness((u << 16) | (s & 0xFFFF));
|
||||
}
|
||||
} else if (r.loc == LOC_COLOR) {
|
||||
final float cr = r.attrib.readComponent(r.data, base, 0);
|
||||
final float cg = r.attrib.readComponent(r.data, base, 1);
|
||||
final float cb = r.attrib.readComponent(r.data, base, 2);
|
||||
final float ca = (r.size >= 4) ? r.attrib.readComponent(r.data, base, 3) : 1.0f;
|
||||
final float cr = readComponent(r, base, 0);
|
||||
final float cg = readComponent(r, base, 1);
|
||||
final float cb = readComponent(r, base, 2);
|
||||
final float ca = (r.size >= 4) ? readComponent(r, base, 3) : 1.0f;
|
||||
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
|
||||
}
|
||||
}
|
||||
|
||||
private static float readComponent(AttribReader r, int base, int component) {
|
||||
return VertexAttribState.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class AttribLayoutKey {
|
||||
final int[] locations;
|
||||
final int[] sizes;
|
||||
final int[] types;
|
||||
final boolean[] normalized;
|
||||
private final int[] offsets;
|
||||
private final int stride;
|
||||
private final int hash;
|
||||
|
||||
public AttribLayoutKey(int[] locations, int[] sizes, int[] types, boolean[] normalized) {
|
||||
this.locations = locations;
|
||||
this.sizes = sizes;
|
||||
this.types = types;
|
||||
this.normalized = normalized;
|
||||
|
||||
final int n = locations.length;
|
||||
final int[] off = new int[n];
|
||||
int base = 0;
|
||||
int maxAlign = 1;
|
||||
for (int i = 0; i < n; i++) {
|
||||
final int typeSize = VertexAttribState.Attrib.glTypeSizeBytes(types[i]);
|
||||
if (typeSize > maxAlign) maxAlign = typeSize;
|
||||
base = (base + typeSize - 1) & -typeSize;
|
||||
off[i] = base;
|
||||
base += sizes[i] * typeSize;
|
||||
}
|
||||
this.offsets = off;
|
||||
this.stride = (base + maxAlign - 1) & -maxAlign;
|
||||
|
||||
int h = Arrays.hashCode(locations);
|
||||
h = 31 * h + Arrays.hashCode(sizes);
|
||||
h = 31 * h + Arrays.hashCode(types);
|
||||
h = 31 * h + Arrays.hashCode(normalized);
|
||||
this.hash = h;
|
||||
}
|
||||
|
||||
public int offset(int i) {
|
||||
return offsets[i];
|
||||
}
|
||||
|
||||
public int stride() {
|
||||
return stride;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof AttribLayoutKey k)) return false;
|
||||
return Arrays.equals(locations, k.locations)
|
||||
&& Arrays.equals(sizes, k.sizes)
|
||||
&& Arrays.equals(types, k.types)
|
||||
&& Arrays.equals(normalized, k.normalized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
private int sharedVAO;
|
||||
private int drawMode;
|
||||
private int indexCount;
|
||||
private int indexType;
|
||||
private long indexByteOffset;
|
||||
|
||||
public void fill(int sharedVAO, int drawMode, int indexCount, int indexType, long indexByteOffset) {
|
||||
this.sharedVAO = sharedVAO;
|
||||
this.drawMode = drawMode;
|
||||
this.indexCount = indexCount;
|
||||
this.indexType = indexType;
|
||||
this.indexByteOffset = indexByteOffset;
|
||||
}
|
||||
|
||||
public int getSharedVAO() { return sharedVAO; }
|
||||
public int getDrawMode() { return drawMode; }
|
||||
public int getIndexCount() { return indexCount; }
|
||||
public int getIndexType() { return indexType; }
|
||||
public long getIndexByteOffset() { return indexByteOffset; }
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (sharedVAO == 0) return;
|
||||
GLStateManager.glBindVertexArray(sharedVAO);
|
||||
GLStateManager.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BatchedIndexedDrawCmd(vao=" + sharedVAO + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
public final class IndexedDrawBatch {
|
||||
private int sharedVAO;
|
||||
private int sharedVBO;
|
||||
private int sharedEBO;
|
||||
private boolean deleted;
|
||||
|
||||
public IndexedDrawBatch(int sharedVAO, int sharedVBO, int sharedEBO) {
|
||||
this.sharedVAO = sharedVAO;
|
||||
this.sharedVBO = sharedVBO;
|
||||
this.sharedEBO = sharedEBO;
|
||||
}
|
||||
|
||||
public int getSharedVAO() { return sharedVAO; }
|
||||
public int getSharedVBO() { return sharedVBO; }
|
||||
public int getSharedEBO() { return sharedEBO; }
|
||||
|
||||
public void delete() {
|
||||
if (deleted) return;
|
||||
deleted = true;
|
||||
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
|
||||
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
|
||||
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
|
||||
sharedVAO = sharedVBO = sharedEBO = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexedDrawBatch(vao=" + sharedVAO + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
|
||||
public final class IndexedDrawBatchBuilder {
|
||||
|
||||
private final List<IndexedDrawCapture> captures = new ArrayList<>();
|
||||
|
||||
public boolean isEmpty() {
|
||||
return captures.isEmpty();
|
||||
}
|
||||
|
||||
public void add(IndexedDrawCapture capture) {
|
||||
captures.add(capture);
|
||||
}
|
||||
|
||||
public List<IndexedDrawCapture> getCaptures() {
|
||||
return captures;
|
||||
}
|
||||
|
||||
public List<IndexedDrawBatch> build() {
|
||||
final List<IndexedDrawBatch> out = new ArrayList<>();
|
||||
if (captures.isEmpty()) return out;
|
||||
|
||||
final int prevVAO = GLStateManager.getBoundVAO();
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final int prevEBO = GLStateManager.getBoundEBO();
|
||||
|
||||
final List<GroupResult> results = new ArrayList<>();
|
||||
boolean success = false;
|
||||
try {
|
||||
final Object2ObjectLinkedOpenHashMap<AttribLayoutKey, List<IndexedDrawCapture>> groups = new Object2ObjectLinkedOpenHashMap<>();
|
||||
for (IndexedDrawCapture c : captures) {
|
||||
groups.computeIfAbsent(c.layoutKey, k -> new ArrayList<>()).add(c);
|
||||
}
|
||||
|
||||
for (List<IndexedDrawCapture> group : groups.values()) {
|
||||
results.add(buildGroup(group));
|
||||
}
|
||||
// Placeholder fill is deferred until every group's GL work has succeeded.
|
||||
for (GroupResult r : results) r.commit();
|
||||
for (GroupResult r : results) out.add(r.batch);
|
||||
success = true;
|
||||
} finally {
|
||||
if (!success) {
|
||||
for (GroupResult r : results) r.batch.delete();
|
||||
}
|
||||
GLStateManager.glBindVertexArray(prevVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private record GroupResult(IndexedDrawBatch batch, List<IndexedDrawCapture> captures,
|
||||
int[] drawModes, int[] indexCounts, long[] indexOffsets) {
|
||||
void commit() {
|
||||
final int sharedVAO = batch.getSharedVAO();
|
||||
for (int i = 0; i < captures.size(); i++) {
|
||||
captures.get(i).placeholder.fill(sharedVAO, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static GroupResult buildGroup(List<IndexedDrawCapture> group) {
|
||||
long totalVertexBytes = 0;
|
||||
long totalIndexBytes = 0;
|
||||
for (IndexedDrawCapture c : group) {
|
||||
totalVertexBytes += (long) c.vertexCount * c.layoutKey.stride();
|
||||
totalIndexBytes += (long) c.indexCount * 4; // always GL_UNSIGNED_INT
|
||||
}
|
||||
if (totalVertexBytes > Integer.MAX_VALUE || totalIndexBytes > Integer.MAX_VALUE) {
|
||||
throw new IllegalStateException("batch exceeds 2GiB — split needed");
|
||||
}
|
||||
|
||||
int sharedVAO = 0;
|
||||
int sharedVBO = 0;
|
||||
int sharedEBO = 0;
|
||||
ByteBuffer eboData = null;
|
||||
boolean success = false;
|
||||
|
||||
final int n = group.size();
|
||||
final int[] drawModes = new int[n];
|
||||
final int[] indexCounts = new int[n];
|
||||
final long[] indexOffsets = new long[n];
|
||||
|
||||
try {
|
||||
sharedVAO = GLStateManager.glGenVertexArrays();
|
||||
sharedVBO = GLStateManager.glGenBuffers();
|
||||
sharedEBO = GLStateManager.glGenBuffers();
|
||||
eboData = MemoryUtilities.memAlloc((int) totalIndexBytes);
|
||||
|
||||
GLStateManager.glBindVertexArray(sharedVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, totalVertexBytes, GL15.GL_STATIC_DRAW);
|
||||
|
||||
int vboBytePos = 0;
|
||||
int eboBytePos = 0;
|
||||
int vertexBase = 0;
|
||||
final long eboAddr = memAddress0(eboData);
|
||||
|
||||
for (int g = 0; g < n; g++) {
|
||||
final IndexedDrawCapture c = group.get(g);
|
||||
final int vBytes = c.vertexCount * c.layoutKey.stride();
|
||||
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, vboBytePos, c.vertexData);
|
||||
|
||||
final long srcIdxAddr = memAddress0(c.indexData);
|
||||
final int idxBytes = c.indexCount * 4;
|
||||
if (vertexBase == 0) {
|
||||
// First capture (or a group that starts at 0) — indices are already
|
||||
// rebased to [0..vertexCount) in IndexedDrawCapture, so a raw bulk copy
|
||||
// is correct and strictly faster than the per-index loop.
|
||||
memCopy(srcIdxAddr, eboAddr + eboBytePos, idxBytes);
|
||||
} else {
|
||||
final long dstBase = eboAddr + eboBytePos;
|
||||
for (int i = 0; i < c.indexCount; i++) {
|
||||
memPutInt(dstBase + i * 4L, memGetInt(srcIdxAddr + i * 4L) + vertexBase);
|
||||
}
|
||||
}
|
||||
|
||||
drawModes[g] = c.drawMode;
|
||||
indexCounts[g] = c.indexCount;
|
||||
indexOffsets[g] = eboBytePos;
|
||||
|
||||
vboBytePos += vBytes;
|
||||
eboBytePos += idxBytes;
|
||||
vertexBase += c.vertexCount;
|
||||
}
|
||||
|
||||
eboData.position(0).limit((int) totalIndexBytes);
|
||||
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
final AttribLayoutKey layout = group.get(0).layoutKey;
|
||||
final int stride = layout.stride();
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
final int size = layout.sizes[i];
|
||||
final int type = layout.types[i];
|
||||
GLStateManager.glVertexAttribPointer(loc, size, type, layout.normalized[i], stride, layout.offset(i));
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
}
|
||||
|
||||
success = true;
|
||||
return new GroupResult(new IndexedDrawBatch(sharedVAO, sharedVBO, sharedEBO),
|
||||
group, drawModes, indexCounts, indexOffsets);
|
||||
} finally {
|
||||
if (eboData != null) MemoryUtilities.memFree(eboData);
|
||||
if (!success) {
|
||||
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
|
||||
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
|
||||
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.QuadConverter;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
|
||||
|
||||
|
||||
/** {@link #create} returns null on any recoverable validation failure; callers must not record the placeholder. */
|
||||
public final class IndexedDrawCapture {
|
||||
public final AttribLayoutKey layoutKey;
|
||||
public final int drawMode;
|
||||
public final int vertexCount;
|
||||
public final int indexCount;
|
||||
public final ByteBuffer vertexData;
|
||||
public final ByteBuffer indexData;
|
||||
public final BatchedIndexedDrawCmd placeholder;
|
||||
private boolean freed;
|
||||
|
||||
public IndexedDrawCapture(AttribLayoutKey layoutKey, int drawMode, int vertexCount, int indexCount,
|
||||
ByteBuffer vertexData, ByteBuffer indexData,
|
||||
BatchedIndexedDrawCmd placeholder) {
|
||||
this.layoutKey = layoutKey;
|
||||
this.drawMode = drawMode;
|
||||
this.vertexCount = vertexCount;
|
||||
this.indexCount = indexCount;
|
||||
this.vertexData = vertexData;
|
||||
this.indexData = indexData;
|
||||
this.placeholder = placeholder;
|
||||
}
|
||||
|
||||
public void freeBuffers() {
|
||||
if (freed) return;
|
||||
freed = true;
|
||||
if (vertexData != null) MemoryUtilities.memFree(vertexData);
|
||||
if (indexData != null) MemoryUtilities.memFree(indexData);
|
||||
}
|
||||
|
||||
public boolean isFreed() {
|
||||
return freed;
|
||||
}
|
||||
|
||||
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private static void warnOnce(String key, String fmt, Object... args) {
|
||||
if (WARN_ONCE.add(key)) GLStateManager.LOGGER.warn(fmt, args);
|
||||
}
|
||||
|
||||
public static IndexedDrawCapture create(int mode, int indicesCount, int srcIndexType, long indicesOffset, int eboId) {
|
||||
if (indicesCount == 0) return null;
|
||||
if (eboId == 0) {
|
||||
warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
|
||||
return null;
|
||||
}
|
||||
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
|
||||
warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
|
||||
return null;
|
||||
}
|
||||
final int srcIndexSize = VertexAttribState.Attrib.glTypeSizeBytes(srcIndexType);
|
||||
if (srcIndexSize <= 0) return null;
|
||||
|
||||
final int prevEBO = GLStateManager.getBoundEBO();
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
ByteBuffer eboSrc = null;
|
||||
AttribSnapshot snap = null;
|
||||
ByteBuffer vertexData = null;
|
||||
ByteBuffer indexData = null;
|
||||
boolean success = false;
|
||||
try {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (eboBufferSize <= 0) {
|
||||
warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
|
||||
return null;
|
||||
}
|
||||
final long eboReadSize = (long) indicesCount * srcIndexSize;
|
||||
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
|
||||
warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
|
||||
return null;
|
||||
}
|
||||
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
|
||||
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
|
||||
final long eboSrcAddr = MemoryUtilities.memAddress0(eboSrc);
|
||||
|
||||
final long minMax = QuadConverter.scanMinMaxIndex(eboSrcAddr, srcIndexType, indicesCount);
|
||||
if (minMax == -1L) {
|
||||
warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
|
||||
return null;
|
||||
}
|
||||
final int minVtx = (int) (minMax & 0xFFFFFFFFL);
|
||||
final int maxVtx = (int) (minMax >>> 32);
|
||||
final int vertexCount = maxVtx - minVtx + 1;
|
||||
|
||||
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
|
||||
|
||||
int enabledCount = 0;
|
||||
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
|
||||
if (snap.get(i) != null) enabledCount++;
|
||||
}
|
||||
if (enabledCount == 0) {
|
||||
warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
|
||||
return null;
|
||||
}
|
||||
final int[] locations = new int[enabledCount];
|
||||
final int[] sizes = new int[enabledCount];
|
||||
final int[] types = new int[enabledCount];
|
||||
final boolean[] normalized = new boolean[enabledCount];
|
||||
int k = 0;
|
||||
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
|
||||
final AttribSnapshot.AttribDesc d = snap.get(i);
|
||||
if (d == null) continue;
|
||||
locations[k] = i;
|
||||
sizes[k] = d.size();
|
||||
types[k] = d.type();
|
||||
normalized[k] = d.normalized();
|
||||
k++;
|
||||
}
|
||||
final AttribLayoutKey layoutKey = new AttribLayoutKey(locations, sizes, types, normalized);
|
||||
final int tightStride = layoutKey.stride();
|
||||
|
||||
ByteBuffer sharedSrcBuffer = null;
|
||||
long sharedSrcBase = 0;
|
||||
int sharedSrcStride = -1;
|
||||
boolean tightSrc = true;
|
||||
for (int a = 0; a < enabledCount; a++) {
|
||||
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
|
||||
final int relOffset = (int) (d.offset() - d.readBufferBaseOffset());
|
||||
if (a == 0) {
|
||||
sharedSrcBuffer = d.readBuffer();
|
||||
sharedSrcStride = d.effectiveStride();
|
||||
sharedSrcBase = MemoryUtilities.memAddress0(sharedSrcBuffer) + relOffset + (long) minVtx * sharedSrcStride;
|
||||
if (relOffset != layoutKey.offset(0)) tightSrc = false;
|
||||
} else if (d.readBuffer() != sharedSrcBuffer || d.effectiveStride() != sharedSrcStride || relOffset != layoutKey.offset(a)) {
|
||||
tightSrc = false;
|
||||
}
|
||||
}
|
||||
if (tightSrc && sharedSrcStride != tightStride) tightSrc = false;
|
||||
|
||||
vertexData = MemoryUtilities.memAlloc(vertexCount * tightStride);
|
||||
final long dstAddr = MemoryUtilities.memAddress0(vertexData);
|
||||
if (tightSrc) {
|
||||
MemoryUtilities.memCopy(sharedSrcBase, dstAddr, (long) vertexCount * tightStride);
|
||||
} else {
|
||||
for (int a = 0; a < enabledCount; a++) {
|
||||
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
|
||||
final int attribBytes = d.size() * d.typeSizeBytes();
|
||||
final int srcStride = d.effectiveStride();
|
||||
final long srcStart = MemoryUtilities.memAddress0(d.readBuffer()) + (d.offset() - d.readBufferBaseOffset()) + (long) minVtx * srcStride;
|
||||
final long dstStart = dstAddr + layoutKey.offset(a);
|
||||
copyStrided(srcStart, srcStride, dstStart, tightStride, attribBytes, vertexCount);
|
||||
}
|
||||
}
|
||||
|
||||
final int bakedDrawMode;
|
||||
final int bakedIndexCount;
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
final int quadCount = indicesCount / 4;
|
||||
bakedIndexCount = quadCount * 6;
|
||||
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
|
||||
QuadConverter.triangulateQuads(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
|
||||
bakedDrawMode = GL11.GL_TRIANGLES;
|
||||
} else {
|
||||
bakedIndexCount = indicesCount;
|
||||
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
|
||||
QuadConverter.widenIndices(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
|
||||
bakedDrawMode = mode;
|
||||
}
|
||||
|
||||
final BatchedIndexedDrawCmd placeholder = new BatchedIndexedDrawCmd();
|
||||
|
||||
final IndexedDrawCapture out = new IndexedDrawCapture(layoutKey, bakedDrawMode, vertexCount, bakedIndexCount, vertexData, indexData, placeholder);
|
||||
success = true;
|
||||
return out;
|
||||
} finally {
|
||||
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
|
||||
if (snap != null) snap.free();
|
||||
if (!success) {
|
||||
if (vertexData != null) MemoryUtilities.memFree(vertexData);
|
||||
if (indexData != null) MemoryUtilities.memFree(indexData);
|
||||
}
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy {@code attribBytes} per vertex from {@code src} (stride {@code srcStride}) to
|
||||
* {@code dst} (stride {@code dstStride}) for {@code vertexCount} vertices.
|
||||
*
|
||||
* <p>Specializes on the common attribute sizes (4 / 8 / 12 / 16) so the hot path is
|
||||
* one or two {@code memPut} calls per vertex instead of a JNI-ish {@code memCopy}
|
||||
* dispatch. Falls through to {@code memCopy} for exotic sizes.
|
||||
*/
|
||||
private static void copyStrided(long src, int srcStride, long dst, int dstStride, int attribBytes, int vertexCount) {
|
||||
switch (attribBytes) {
|
||||
case 4 -> {
|
||||
for (int v = 0; v < vertexCount; v++) {
|
||||
memPutInt(dst, memGetInt(src));
|
||||
src += srcStride; dst += dstStride;
|
||||
}
|
||||
}
|
||||
case 8 -> {
|
||||
for (int v = 0; v < vertexCount; v++) {
|
||||
memPutLong(dst, memGetLong(src));
|
||||
src += srcStride; dst += dstStride;
|
||||
}
|
||||
}
|
||||
case 12 -> {
|
||||
for (int v = 0; v < vertexCount; v++) {
|
||||
memPutLong(dst, memGetLong(src));
|
||||
memPutInt(dst + 8, memGetInt(src + 8));
|
||||
src += srcStride; dst += dstStride;
|
||||
}
|
||||
}
|
||||
case 16 -> {
|
||||
for (int v = 0; v < vertexCount; v++) {
|
||||
memPutLong(dst, memGetLong(src));
|
||||
memPutLong(dst + 8, memGetLong(src + 8));
|
||||
src += srcStride; dst += dstStride;
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
for (int v = 0; v < vertexCount; v++) {
|
||||
MemoryUtilities.memCopy(src, dst, attribBytes);
|
||||
src += srcStride; dst += dstStride;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);}
|
||||
|
||||
|
||||
@@ -108,13 +108,6 @@ public class VertexAttribState {
|
||||
clientSideEnabledCount = count;
|
||||
}
|
||||
|
||||
public static boolean hasVBOBoundAttrib() {
|
||||
for (int i = 0; i < MAX_ATTRIBS; i++) {
|
||||
if (current[i].enabled && current[i].vboId != 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any currently enabled vertex attribute was registered without a VBO
|
||||
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
|
||||
@@ -167,6 +160,10 @@ public class VertexAttribState {
|
||||
}
|
||||
|
||||
public float readComponent(ByteBuffer buf, int base, int component) {
|
||||
return readComponent(type, normalized, buf, base, component);
|
||||
}
|
||||
|
||||
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
|
||||
return switch (type) {
|
||||
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
|
||||
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -40,7 +41,7 @@ public final class OrphanStreamingBuffer {
|
||||
|
||||
public void destroy() {
|
||||
if (bufferId != 0) {
|
||||
RENDER_BACKEND.deleteBuffers(bufferId);
|
||||
GLStateManager.glDeleteBuffers(bufferId);
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
|
||||
import org.embeddedt.embeddium.impl.gl.sync.GlFence;
|
||||
@@ -52,7 +53,7 @@ public class PersistentStreamingBuffer implements StreamingBuffer {
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
if (this.mappedBuffer == null) {
|
||||
RENDER_BACKEND.deleteBuffers(bufferId);
|
||||
GLStateManager.glDeleteBuffers(bufferId);
|
||||
bufferId = 0;
|
||||
throw new RuntimeException("Failed to persistently map streaming buffer");
|
||||
}
|
||||
@@ -147,7 +148,7 @@ public class PersistentStreamingBuffer implements StreamingBuffer {
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
RENDER_BACKEND.deleteBuffers(bufferId);
|
||||
GLStateManager.glDeleteBuffers(bufferId);
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class QuadConverterTest {
|
||||
|
||||
@Test
|
||||
void triangulateQuads_ubyteToUint_lastVertexConvention() {
|
||||
// 2 quads: [0,1,2,3] and [4,5,6,7]. Expected LAST-convention output:
|
||||
// quad0 → (0,1,3), (1,2,3)
|
||||
// quad1 → (4,5,7), (5,6,7)
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(8);
|
||||
for (int i = 0; i < 8; i++) src.put(i, (byte) i);
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(12 * 4);
|
||||
try {
|
||||
QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_UNSIGNED_BYTE, memAddress0(dst), GL11.GL_UNSIGNED_INT, 2);
|
||||
|
||||
assertUintIndices(dst, 0, 1, 3, 1, 2, 3, 4, 5, 7, 5, 6, 7);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void triangulateQuads_ushortToUshort_preservesType() {
|
||||
// USHORT in, USHORT out — same-size path for live draws.
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(4 * 2);
|
||||
for (int i = 0; i < 4; i++) src.putShort(i * 2, (short) (i + 100));
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(6 * 2);
|
||||
try {
|
||||
QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, 1);
|
||||
|
||||
assertUshortIndices(dst, 100, 101, 103, 101, 102, 103);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void triangulateQuads_ushortToUint_widens() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(4 * 2);
|
||||
for (int i = 0; i < 4; i++) src.putShort(i * 2, (short) (0xFF00 | i));
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(6 * 4);
|
||||
try {
|
||||
QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_INT, 1);
|
||||
|
||||
// Zero-extended USHORT→UINT, LAST winding.
|
||||
assertUintIndices(dst, 0xFF00, 0xFF01, 0xFF03, 0xFF01, 0xFF02, 0xFF03);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void triangulateQuads_uintToUint_identity() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(4 * 4);
|
||||
for (int i = 0; i < 4; i++) src.putInt(i * 4, 1_000_000 + i);
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(6 * 4);
|
||||
try {
|
||||
QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, 1);
|
||||
|
||||
assertUintIndices(dst, 1_000_000, 1_000_001, 1_000_003, 1_000_001, 1_000_002, 1_000_003);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void triangulateQuads_zeroQuads_noop() {
|
||||
QuadConverter.triangulateQuads(0L, GL11.GL_UNSIGNED_INT, 0L, GL11.GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void triangulateQuads_unsupportedTypes_throw() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(16);
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(24);
|
||||
try {
|
||||
assertThrows(IllegalArgumentException.class, () -> QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_FLOAT, memAddress0(dst), GL11.GL_UNSIGNED_INT, 1));
|
||||
assertThrows(IllegalArgumentException.class, () -> QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_BYTE, 1));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void widenIndices_ubyteToUint() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(5);
|
||||
for (int i = 0; i < 5; i++) src.put(i, (byte) (200 + i));
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(5 * 4);
|
||||
try {
|
||||
QuadConverter.widenIndices(memAddress0(src), GL11.GL_UNSIGNED_BYTE, memAddress0(dst), GL11.GL_UNSIGNED_INT, 5);
|
||||
|
||||
assertUintIndices(dst, 200, 201, 202, 203, 204);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void widenIndices_ushortToUint_zeroExtends() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(3 * 2);
|
||||
src.putShort(0, (short) 0xFFFF);
|
||||
src.putShort(2, (short) 0x8001);
|
||||
src.putShort(4, (short) 0);
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(3 * 4);
|
||||
try {
|
||||
QuadConverter.widenIndices(memAddress0(src), GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_INT, 3);
|
||||
|
||||
assertUintIndices(dst, 0xFFFF, 0x8001, 0);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void widenIndices_uintToUint_copiesBitsExactly() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(3 * 4);
|
||||
src.putInt(0, 0x01234567);
|
||||
src.putInt(4, 0xDEADBEEF);
|
||||
src.putInt(8, 0);
|
||||
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(3 * 4);
|
||||
try {
|
||||
QuadConverter.widenIndices(memAddress0(src), GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, 3);
|
||||
|
||||
assertUintIndices(dst, 0x01234567, 0xDEADBEEF, 0);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanMinMaxIndex_ushort_typical() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(6 * 2);
|
||||
final int[] data = {42, 7, 1000, 99, 500, 3};
|
||||
for (int i = 0; i < data.length; i++) src.putShort(i * 2, (short) data[i]);
|
||||
|
||||
try {
|
||||
final long packed = QuadConverter.scanMinMaxIndex(memAddress0(src), GL11.GL_UNSIGNED_SHORT, data.length);
|
||||
final int min = (int) (packed & 0xFFFFFFFFL);
|
||||
final int max = (int) (packed >>> 32);
|
||||
assertEquals(3, min);
|
||||
assertEquals(1000, max);
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanMinMaxIndex_emptyCountReturnsMinusOne() {
|
||||
assertEquals(-1L, QuadConverter.scanMinMaxIndex(0L, GL11.GL_UNSIGNED_SHORT, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanMinMaxIndex_unsupportedTypeReturnsMinusOne() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(4);
|
||||
try {
|
||||
assertEquals(-1L, QuadConverter.scanMinMaxIndex(memAddress0(src), GL11.GL_FLOAT, 1));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanMinMaxIndex_uintOverIntMaxRejected() {
|
||||
// readIndex/writeIndex/rebase route UINT through signed int; values above
|
||||
// Integer.MAX_VALUE would silently wrap. scanMinMaxIndex must bail.
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(2 * 4);
|
||||
src.putInt(0, 10);
|
||||
src.putInt(4, 0x80000000); // 2^31
|
||||
try {
|
||||
assertEquals(-1L, QuadConverter.scanMinMaxIndex(memAddress0(src), GL11.GL_UNSIGNED_INT, 2));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanMinMaxIndex_uintUpToIntMaxAccepted() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(2 * 4);
|
||||
src.putInt(0, 5);
|
||||
src.putInt(4, Integer.MAX_VALUE);
|
||||
try {
|
||||
final long packed = QuadConverter.scanMinMaxIndex(memAddress0(src), GL11.GL_UNSIGNED_INT, 2);
|
||||
assertEquals(5, (int) (packed & 0xFFFFFFFFL));
|
||||
assertEquals(Integer.MAX_VALUE, (int) (packed >>> 32));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void widenIndices_ubyteDstRejected() {
|
||||
// UBYTE is not a supported destination — widening only.
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(2);
|
||||
src.put(0, (byte) 1).put(1, (byte) 2);
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(2);
|
||||
try {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> QuadConverter.widenIndices(memAddress0(src), GL11.GL_UNSIGNED_BYTE, memAddress0(dst), GL11.GL_UNSIGNED_BYTE, 2));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void triangulateQuads_ubyteDstRejected() {
|
||||
final ByteBuffer src = MemoryUtilities.memAlloc(4);
|
||||
for (int i = 0; i < 4; i++) src.put(i, (byte) i);
|
||||
final ByteBuffer dst = MemoryUtilities.memAlloc(6);
|
||||
try {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> QuadConverter.triangulateQuads(memAddress0(src), GL11.GL_UNSIGNED_BYTE, memAddress0(dst), GL11.GL_UNSIGNED_BYTE, 1));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(src);
|
||||
MemoryUtilities.memFree(dst);
|
||||
}
|
||||
}
|
||||
|
||||
// === helpers ===
|
||||
|
||||
private static void assertUintIndices(ByteBuffer buf, int... expected) {
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertEquals(expected[i], buf.getInt(i * 4), "uint index[" + i + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertUshortIndices(ByteBuffer buf, int... expected) {
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertEquals(expected[i], buf.getShort(i * 2) & 0xFFFF, "ushort index[" + i + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.LWJGLException;
|
||||
import org.lwjgl.opengl.ContextAttribs;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.DisplayMode;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.PixelFormat;
|
||||
import org.lwjgl.opengl.SharedDrawable;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
class SharedDrawableVaoTest {
|
||||
|
||||
private static final String VERT_330 = "#version 330 core\nvoid main() { gl_Position = vec4(0.0); }\n";
|
||||
private static final String FRAG_330 = "#version 330 core\nout vec4 o;\nvoid main() { o = vec4(0.0); }\n";
|
||||
|
||||
@BeforeAll
|
||||
static void createContext() throws LWJGLException {
|
||||
assumeTrue(!Display.isCreated(), "inherited Display -- skipping to avoid profile drift");
|
||||
Display.setDisplayModeAndFullscreen(new DisplayMode(800, 600));
|
||||
Display.setResizable(false);
|
||||
Display.setFullscreen(false);
|
||||
Display.create(
|
||||
new PixelFormat().withDepthBits(24).withStencilBits(8),
|
||||
new ContextAttribs(4, 1).withProfileCore(true).withForwardCompatible(true));
|
||||
GL30.glBindVertexArray(GL30.glGenVertexArrays());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void destroyContext() {
|
||||
if (Display.isCreated()) Display.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateProgramSucceedsOnSharedDrawableWithVao() throws LWJGLException {
|
||||
SharedDrawable shared = new SharedDrawable(Display.getDrawable());
|
||||
Display.getDrawable().releaseContext();
|
||||
shared.makeCurrent();
|
||||
try {
|
||||
GL30.glBindVertexArray(GL30.glGenVertexArrays());
|
||||
int program = linkTrivialProgram();
|
||||
GL20.glValidateProgram(program);
|
||||
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(program, GL20.GL_VALIDATE_STATUS), () -> GL20.glGetProgramInfoLog(program, 4096));
|
||||
GL20.glDeleteProgram(program);
|
||||
} finally {
|
||||
shared.releaseContext();
|
||||
Display.getDrawable().makeCurrent();
|
||||
shared.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private static int linkTrivialProgram() {
|
||||
int vs = compile(GL20.GL_VERTEX_SHADER, VERT_330);
|
||||
int fs = compile(GL20.GL_FRAGMENT_SHADER, FRAG_330);
|
||||
int program = GL20.glCreateProgram();
|
||||
GL20.glAttachShader(program, vs);
|
||||
GL20.glAttachShader(program, fs);
|
||||
GL20.glLinkProgram(program);
|
||||
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(program, GL20.GL_LINK_STATUS), () -> GL20.glGetProgramInfoLog(program, 4096));
|
||||
GL20.glDeleteShader(vs);
|
||||
GL20.glDeleteShader(fs);
|
||||
return program;
|
||||
}
|
||||
|
||||
private static int compile(int type, String src) {
|
||||
int shader = GL20.glCreateShader(type);
|
||||
GL20.glShaderSource(shader, src);
|
||||
GL20.glCompileShader(shader);
|
||||
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS), () -> GL20.glGetShaderInfoLog(shader, 4096));
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.gtnewhorizons.angelica.glsm.architecture;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DeletePathConventionTest {
|
||||
|
||||
private static final Pattern FORBIDDEN = Pattern.compile("\\bRENDER_BACKEND\\s*\\.\\s*delete[A-Z][A-Za-z]*\\b");
|
||||
private static final String ALLOWLIST_MARKER = "// glsm-allow-direct-delete:";
|
||||
|
||||
/** Only this file is allowed to call RENDER_BACKEND.delete* — it is the cache-invalidation layer. */
|
||||
private static final String GATEKEEPER_FILE_NAME = "GLStateManager.java";
|
||||
|
||||
@Test
|
||||
void glsmMainSources_onlyCallBackendDeleteFromGLStateManager() throws IOException {
|
||||
final Path root = locateGlsmMainRoot();
|
||||
final List<String> violations = new ArrayList<>();
|
||||
|
||||
try (Stream<Path> paths = Files.walk(root)) {
|
||||
paths.filter(Files::isRegularFile)
|
||||
.filter(p -> p.toString().endsWith(".java"))
|
||||
.filter(p -> !p.getFileName().toString().equals(GATEKEEPER_FILE_NAME))
|
||||
.forEach(p -> scanFile(p, violations));
|
||||
}
|
||||
|
||||
assertTrue(violations.isEmpty(),
|
||||
() -> "Direct RENDER_BACKEND.delete*(...) call found outside " + GATEKEEPER_FILE_NAME + ".\n"
|
||||
+ "Use GLStateManager.glDelete*() instead\n"
|
||||
+ "Violations:\n " + String.join("\n ", violations));
|
||||
}
|
||||
|
||||
private static void scanFile(Path file, List<String> violations) {
|
||||
final List<String> lines;
|
||||
try {
|
||||
lines = Files.readAllLines(file, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
violations.add(file + ": I/O error reading file: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
final String line = lines.get(i);
|
||||
final Matcher m = FORBIDDEN.matcher(line);
|
||||
if (!m.find()) continue;
|
||||
if (line.contains(ALLOWLIST_MARKER)) continue;
|
||||
violations.add(file + ":" + (i + 1) + " " + line.trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static Path locateGlsmMainRoot() {
|
||||
final String[] candidates = {
|
||||
"src/main/java/com/gtnewhorizons/angelica/glsm",
|
||||
"glsm/src/main/java/com/gtnewhorizons/angelica/glsm",
|
||||
};
|
||||
for (String c : candidates) {
|
||||
final Path p = Paths.get(c).toAbsolutePath();
|
||||
if (Files.isDirectory(p)) return p;
|
||||
}
|
||||
throw new IllegalStateException("Could not find glsm main sources — cwd=" + Paths.get("").toAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
|
||||
@Test
|
||||
void singleDraw_bakedSurvivesVboMutation() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 2, 3, 0);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
|
||||
assertNotNull(cmd);
|
||||
assertEquals(GL11.GL_TRIANGLES, cmd.getDrawMode());
|
||||
assertEquals(6, cmd.getIndexCount());
|
||||
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "batched indices always widen to GL_UNSIGNED_INT");
|
||||
|
||||
// Overwrite source VBO with garbage — baked batch must be unaffected.
|
||||
overwriteBufferWithGarbage(GL15.GL_ARRAY_BUFFER, vbo, 4 * POS_COLOR_STRIDE);
|
||||
|
||||
final ByteBuffer read = readBufferBytes(GL15.GL_ARRAY_BUFFER, firstBatch(list).getSharedVBO(), 4 * POS_COLOR_STRIDE);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
assertEquals(DEFAULT_QUAD_POSITIONS[i * 2], read.getFloat(i * POS_COLOR_STRIDE), 0f, "pos.x v=" + i);
|
||||
assertEquals(DEFAULT_QUAD_POSITIONS[i * 2 + 1], read.getFloat(i * POS_COLOR_STRIDE + 4), 0f, "pos.y v=" + i);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
assertEquals(DEFAULT_SOLID_COLORS[i][c], read.get(i * POS_COLOR_STRIDE + 8 + c), "color v=" + i + " c=" + c);
|
||||
}
|
||||
}
|
||||
MemoryUtilities.memFree(read);
|
||||
}
|
||||
|
||||
@Test
|
||||
void compileAndExecute_firesLiveAndBakes() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
|
||||
assertNotNull(cmd);
|
||||
assertEquals(3, cmd.getIndexCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void quads_triangulateAtRecord() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
|
||||
assertNotNull(cmd);
|
||||
assertEquals(GL11.GL_TRIANGLES, cmd.getDrawMode());
|
||||
assertEquals(6, cmd.getIndexCount());
|
||||
|
||||
// EBO is widened to UNSIGNED_INT and uses GL_LAST_VERTEX_CONVENTION:
|
||||
// (a,b,d,b,c,d) — v3 is the provoking vertex for both triangles, diagonal b→d.
|
||||
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, firstBatch(list).getSharedEBO(), 6 * 4);
|
||||
assertUintIndices(ib, 0, 1, 3, 1, 2, 3);
|
||||
MemoryUtilities.memFree(ib);
|
||||
}
|
||||
|
||||
@Test
|
||||
void offsetAndMinMaxRebase() {
|
||||
// 8-vertex VBO; draw references only vertices 5..7 via a 6L byte offset into the EBO.
|
||||
final float[] positions = new float[16];
|
||||
final byte[][] colors = new byte[8][4];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
positions[i * 2] = i;
|
||||
positions[i * 2 + 1] = i * 2;
|
||||
colors[i][0] = (byte) i;
|
||||
colors[i][1] = (byte) (i + 1);
|
||||
colors[i][2] = (byte) (i + 2);
|
||||
colors[i][3] = (byte) (i + 3);
|
||||
}
|
||||
final int vbo = uploadPosColorVbo(positions, colors);
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 5, 6, 7);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 6L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertEquals(1, allBatchedCmds(list).size());
|
||||
|
||||
final IndexedDrawBatch batch = firstBatch(list);
|
||||
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, batch.getSharedEBO(), 3 * 4);
|
||||
// Indices rebased against the tight [minVtx..maxVtx] range.
|
||||
assertUintIndices(ib, 0, 1, 2);
|
||||
MemoryUtilities.memFree(ib);
|
||||
|
||||
final ByteBuffer vboRead = readBufferBytes(GL15.GL_ARRAY_BUFFER, batch.getSharedVBO(), 3 * POS_COLOR_STRIDE);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final int orig = 5 + i;
|
||||
assertEquals(positions[orig * 2], vboRead.getFloat(i * POS_COLOR_STRIDE), 0f, "pos.x v=" + orig);
|
||||
assertEquals(positions[orig * 2 + 1], vboRead.getFloat(i * POS_COLOR_STRIDE + 4), 0f, "pos.y v=" + orig);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
assertEquals(colors[orig][c], vboRead.get(i * POS_COLOR_STRIDE + 8 + c), "col v=" + orig + " c=" + c);
|
||||
}
|
||||
}
|
||||
MemoryUtilities.memFree(vboRead);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "indexType=0x{0}")
|
||||
@ValueSource(ints = {GL11.GL_UNSIGNED_BYTE, GL11.GL_UNSIGNED_SHORT, GL11.GL_UNSIGNED_INT})
|
||||
void anyIndexType_widensToUnsignedInt(int indexType) {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(indexType, 0, 1, 2, 2, 3, 0);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 6, indexType);
|
||||
|
||||
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
|
||||
assertNotNull(cmd);
|
||||
assertEquals(6, cmd.getIndexCount());
|
||||
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "batching always widens indices to UINT for safe base-vertex shifting");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyRecorder_producesNoBatches() {
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertTrue(batchesOf(list).isEmpty(), "recorder with no draws produces no batches");
|
||||
assertTrue(allBatchedCmds(list).isEmpty(), "no placeholders either");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noEboBoundSkipsAndDoesNotBake() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertTrue(allBatchedCmds(list).isEmpty(), "no EBO bound → no batched command attached");
|
||||
assertTrue(batchesOf(list).isEmpty(), "no batches either");
|
||||
}
|
||||
|
||||
@Test
|
||||
void customLoc3FloatAttribRoundtrips() {
|
||||
// 4-attrib layout: pos(2f) | uv(2f) | col(4ub) | texBounds(4f) = 36 bytes/vertex.
|
||||
final int STRIDE = 36;
|
||||
final int N = 4;
|
||||
final float[][] pos = {{0f,0f},{1f,0f},{1f,1f},{0f,1f}};
|
||||
final float[][] uv = {{0.1f,0.2f},{0.3f,0.4f},{0.5f,0.6f},{0.7f,0.8f}};
|
||||
final byte[][] col = {{10,20,30,40},{50,60,70,(byte)80},{(byte)90,(byte)100,(byte)110,(byte)120},{(byte)130,(byte)140,(byte)150,(byte)160}};
|
||||
final float[][] tb = {{1f,2f,3f,4f},{5f,6f,7f,8f},{9f,10f,11f,12f},{13f,14f,15f,16f}};
|
||||
final int vbo = newBuffer();
|
||||
final ByteBuffer data = MemoryUtilities.memAlloc(N * STRIDE).order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < N; i++) {
|
||||
data.putFloat(pos[i][0]); data.putFloat(pos[i][1]);
|
||||
data.putFloat(uv[i][0]); data.putFloat(uv[i][1]);
|
||||
data.put(col[i][0]); data.put(col[i][1]); data.put(col[i][2]); data.put(col[i][3]);
|
||||
data.putFloat(tb[i][0]); data.putFloat(tb[i][1]); data.putFloat(tb[i][2]); data.putFloat(tb[i][3]);
|
||||
}
|
||||
data.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
MemoryUtilities.memFree(data);
|
||||
|
||||
final int vao = newVao();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, STRIDE, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, STRIDE, 8);
|
||||
GLStateManager.glEnableVertexAttribArray(1);
|
||||
GLStateManager.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 16);
|
||||
GLStateManager.glEnableVertexAttribArray(2);
|
||||
GLStateManager.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, STRIDE, 20);
|
||||
GLStateManager.glEnableVertexAttribArray(3);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 2, 3, 0);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
assertNotNull(firstBatchedCmd(list), "loc3 attrib should not cause the baker to skip");
|
||||
|
||||
// Baked layout is tight interleaved in ascending-location order — same stride as source.
|
||||
final ByteBuffer read = readBufferBytes(GL15.GL_ARRAY_BUFFER, firstBatch(list).getSharedVBO(), N * STRIDE);
|
||||
for (int i = 0; i < N; i++) {
|
||||
final int base = i * STRIDE;
|
||||
assertEquals(pos[i][0], read.getFloat(base), 0f, "pos.x v=" + i);
|
||||
assertEquals(pos[i][1], read.getFloat(base + 4), 0f, "pos.y v=" + i);
|
||||
assertEquals(uv[i][0], read.getFloat(base + 8), 0f, "uv.s v=" + i);
|
||||
assertEquals(uv[i][1], read.getFloat(base + 12), 0f, "uv.t v=" + i);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
assertEquals(col[i][c], read.get(base + 16 + c), "col v=" + i + " c=" + c);
|
||||
}
|
||||
for (int c = 0; c < 4; c++) {
|
||||
assertEquals(tb[i][c], read.getFloat(base + 20 + c * 4), 0f, "tb v=" + i + " c=" + c);
|
||||
}
|
||||
}
|
||||
MemoryUtilities.memFree(read);
|
||||
}
|
||||
|
||||
@Test
|
||||
void glDeleteLists_freesBatchedObjects() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final IndexedDrawBatch batch = firstBatch(list);
|
||||
final int sharedVAO = batch.getSharedVAO();
|
||||
final int sharedVBO = batch.getSharedVBO();
|
||||
final int sharedEBO = batch.getSharedEBO();
|
||||
assertTrue(GL15.glIsBuffer(sharedVBO), "shared VBO live");
|
||||
assertTrue(GL15.glIsBuffer(sharedEBO), "shared EBO live");
|
||||
assertTrue(GL30.glIsVertexArray(sharedVAO), "shared VAO live");
|
||||
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
untrackList(list);
|
||||
|
||||
assertFalse(GL15.glIsBuffer(sharedVBO), "shared VBO freed");
|
||||
assertFalse(GL15.glIsBuffer(sharedEBO), "shared EBO freed");
|
||||
assertFalse(GL30.glIsVertexArray(sharedVAO), "shared VAO freed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reRecord_freesOldBatchedObjects() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final IndexedDrawBatch first = firstBatch(list);
|
||||
final int oldVAO = first.getSharedVAO();
|
||||
final int oldVBO = first.getSharedVBO();
|
||||
final int oldEBO = first.getSharedEBO();
|
||||
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertFalse(GL15.glIsBuffer(oldVBO), "old shared VBO freed on re-record");
|
||||
assertFalse(GL15.glIsBuffer(oldEBO), "old shared EBO freed on re-record");
|
||||
assertFalse(GL30.glIsVertexArray(oldVAO), "old shared VAO freed on re-record");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientPointerAttribsBakeIntoSharedVbo() {
|
||||
final int N = 4;
|
||||
final ByteBuffer clientData = BufferUtils.createByteBuffer(N * POS_COLOR_STRIDE).order(ByteOrder.nativeOrder());
|
||||
final float[][] pos = {{0f,0f},{1f,0f},{1f,1f},{0f,1f}};
|
||||
final byte[][] col = {{(byte)0xAA,(byte)0xBB,(byte)0xCC,(byte)0xDD},
|
||||
{(byte)0x11,(byte)0x22,(byte)0x33,(byte)0x44},
|
||||
{(byte)0x55,(byte)0x66,(byte)0x77,(byte)0x88},
|
||||
{(byte)0x99,(byte)0x00,(byte)0x0F,(byte)0xF0}};
|
||||
for (int i = 0; i < N; i++) {
|
||||
clientData.putFloat(pos[i][0]); clientData.putFloat(pos[i][1]);
|
||||
clientData.put(col[i][0]); clientData.put(col[i][1]);
|
||||
clientData.put(col[i][2]); clientData.put(col[i][3]);
|
||||
}
|
||||
clientData.flip();
|
||||
|
||||
final int vao = newVao();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
|
||||
colorSlice.position(8);
|
||||
VertexAttribState.set(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData, 0);
|
||||
VertexAttribState.setEnabled(0, true);
|
||||
VertexAttribState.set(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice, 0);
|
||||
VertexAttribState.setEnabled(1, true);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
assertNotNull(firstBatchedCmd(list));
|
||||
|
||||
// Tight interleaved baked layout for 3 drawn vertices = 36 bytes total.
|
||||
final ByteBuffer owned = readBufferBytes(GL15.GL_ARRAY_BUFFER, firstBatch(list).getSharedVBO(), 3 * POS_COLOR_STRIDE);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
assertEquals(pos[i][0], owned.getFloat(i * POS_COLOR_STRIDE), 0f, "loc0 pos.x v=" + i);
|
||||
assertEquals(pos[i][1], owned.getFloat(i * POS_COLOR_STRIDE + 4), 0f, "loc0 pos.y v=" + i);
|
||||
for (int c = 0; c < 4; c++) {
|
||||
assertEquals(col[i][c], owned.get(i * POS_COLOR_STRIDE + 8 + c), "loc1 color v=" + i + " c=" + c);
|
||||
}
|
||||
}
|
||||
MemoryUtilities.memFree(owned);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiDrawSameFormat_mergesIntoSingleBatch() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int eboA = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
final int eboB = uploadEbo(GL11.GL_UNSIGNED_SHORT, 2, 3, 0);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboA);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboB);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size(), "two indexed draws → two batched commands");
|
||||
final List<IndexedDrawBatch> batches = batchesOf(list);
|
||||
assertEquals(1, batches.size(), "same layout → one shared batch");
|
||||
|
||||
assertEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "shared VAO across draws in the same batch");
|
||||
assertEquals(batches.getFirst().getSharedVAO(), cmds.get(0).getSharedVAO());
|
||||
|
||||
assertEquals(0L, cmds.get(0).getIndexByteOffset());
|
||||
assertEquals(3 * 4L, cmds.get(1).getIndexByteOffset(), "second draw offset = first.indexCount * sizeof(UINT)");
|
||||
|
||||
// Shared EBO holds concatenated + rebased UINT indices. First draw minVtx=0 → [0,1,2];
|
||||
// second draw minVtx=0, vertexBase=3 → [2+3, 3+3, 0+3] = [5, 6, 3].
|
||||
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, batches.getFirst().getSharedEBO(), 6 * 4);
|
||||
assertUintIndices(ib, 0, 1, 2, 5, 6, 3);
|
||||
MemoryUtilities.memFree(ib);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiDrawDifferentFormats_splitBatches() {
|
||||
// Draw A: pos+color (stride=12). Draw B: pos-only (stride=8). Different layout → two batches.
|
||||
final int vboA = defaultQuadVbo();
|
||||
final int vaoA = setupPosColorVao(vboA);
|
||||
|
||||
final int vboB = newBuffer();
|
||||
final ByteBuffer bData = MemoryUtilities.memAlloc(4 * 8).order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2]);
|
||||
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2 + 1]);
|
||||
}
|
||||
bData.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bData, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
MemoryUtilities.memFree(bData);
|
||||
|
||||
final int vaoB = newVao();
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
|
||||
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 8, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vaoA);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size());
|
||||
assertEquals(2, batchesOf(list).size(), "incompatible layouts → two batches");
|
||||
assertNotEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "VAOs from separate batches must be distinct");
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameSizesDifferentLocations_splitIntoSeparateBatches() {
|
||||
// Both VAOs: pos 2f + color 4ub (same sizes/types/normalized). Only the enabled
|
||||
// location sets differ — VAO-A uses {0,1}, VAO-B uses {0,2}. If AttribLayoutKey
|
||||
// forgot about locations they'd merge and VAO-B's color data would get wired to
|
||||
// loc 1 instead of loc 2.
|
||||
final int vbo = defaultQuadVbo();
|
||||
|
||||
final int vaoA = setupPosColorVao(vbo);
|
||||
|
||||
final int vaoB = newVao();
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, 8);
|
||||
GLStateManager.glEnableVertexAttribArray(2);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vaoA);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size());
|
||||
assertEquals(2, batchesOf(list).size(), "different enabled locations → two batches");
|
||||
assertNotEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "VAOs from separate batches must be distinct");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullListProbeCompiles() {
|
||||
assertNull(DisplayListManager.getDisplayList(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiLayoutDraws_emitInInsertionOrder() {
|
||||
// Two distinct layouts. The builder uses a LinkedHashMap, so the first
|
||||
// recorded layout must always be the first batch out.
|
||||
final int vboA = defaultQuadVbo();
|
||||
final int vaoA = setupPosColorVao(vboA);
|
||||
|
||||
final int vboB = newBuffer();
|
||||
final ByteBuffer bData = MemoryUtilities.memAlloc(4 * 8).order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2]);
|
||||
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2 + 1]);
|
||||
}
|
||||
bData.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bData, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
MemoryUtilities.memFree(bData);
|
||||
|
||||
final int vaoB = newVao();
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
|
||||
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 8, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
|
||||
// Record (A, B) and (B, A) as independent lists; each list's batch order
|
||||
// must match its own recording order.
|
||||
final int listAB = newList();
|
||||
GLStateManager.glNewList(listAB, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vaoA);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final int listBA = newList();
|
||||
GLStateManager.glNewList(listBA, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(vaoA);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final List<BatchedIndexedDrawCmd> ab = allBatchedCmds(listAB);
|
||||
final List<BatchedIndexedDrawCmd> ba = allBatchedCmds(listBA);
|
||||
// Placeholders sit in command-stream order (A-first vs B-first).
|
||||
// Batches must be ordered so that placeholder[0] points at batch[0].
|
||||
assertEquals(ab.get(0).getSharedVAO(), batchesOf(listAB).get(0).getSharedVAO(),
|
||||
"listAB: batch[0] must match the first-recorded layout");
|
||||
assertEquals(ab.get(1).getSharedVAO(), batchesOf(listAB).get(1).getSharedVAO(),
|
||||
"listAB: batch[1] must match the second-recorded layout");
|
||||
assertEquals(ba.get(0).getSharedVAO(), batchesOf(listBA).get(0).getSharedVAO(),
|
||||
"listBA: batch[0] must match the first-recorded layout");
|
||||
assertEquals(ba.get(1).getSharedVAO(), batchesOf(listBA).get(1).getSharedVAO(),
|
||||
"listBA: batch[1] must match the second-recorded layout");
|
||||
}
|
||||
|
||||
// === local helpers ===
|
||||
|
||||
private static void assertUintIndices(ByteBuffer ib, int... expected) {
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertEquals(expected[i], ib.getInt(i * 4), "index[" + i + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class IndexedDrawDeleteSafetyTest extends DisplayListTestFixture {
|
||||
|
||||
@Test
|
||||
void vaoEboMapSweep_clearsStaleEntryOnForeignVao() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final IndexedDrawBatch batch = firstBatch(list);
|
||||
final int sharedEbo = batch.getSharedEBO();
|
||||
|
||||
final int foreignVao = newVao();
|
||||
GLStateManager.glBindVertexArray(foreignVao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEbo);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
untrackList(list);
|
||||
|
||||
GLStateManager.glBindVertexArray(foreignVao);
|
||||
assertEquals(0, GLStateManager.getBoundEBO(), "vaoEboMap still references the freed sharedEBO — non-gen crash imminent");
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindBufferCycle_afterListDelete_isGlNoError() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final int sharedEbo = firstBatch(list).getSharedEBO();
|
||||
final int foreignVao = newVao();
|
||||
GLStateManager.glBindVertexArray(foreignVao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEbo);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
untrackList(list);
|
||||
|
||||
GLStateManager.glBindVertexArray(foreignVao);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
final int err = GL11.glGetError();
|
||||
assertEquals(GL11.GL_NO_ERROR, err, () -> "bindBuffer(prevEbo=" + prevEbo + ") hit GL 0x" + Integer.toHexString(err) + " — cache carried a freed id through a VAO rebind");
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleVaos_sharingOneEbo_allClearedOnEboDelete() {
|
||||
// Two VAOs, same EBO. Deleting the EBO must clear vaoEboMap for both,
|
||||
// not just whichever VAO happens to be currently bound.
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vaoA = newVao();
|
||||
final int vaoB = newVao();
|
||||
|
||||
GLStateManager.glBindVertexArray(vaoA);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.glDeleteBuffers(ebo);
|
||||
|
||||
GLStateManager.glBindVertexArray(vaoA);
|
||||
assertEquals(0, GLStateManager.getBoundEBO(), "vaoA still references freed EBO");
|
||||
GLStateManager.glBindVertexArray(vaoB);
|
||||
assertEquals(0, GLStateManager.getBoundEBO(), "vaoB still references freed EBO");
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void glDeleteProgram_zero_isNoOpAndLeavesActiveProgram() {
|
||||
final int prog = linkMinimalProgram();
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
GLStateManager.glUseProgram(prog);
|
||||
assertEquals(prog, GLStateManager.getActiveProgram());
|
||||
|
||||
GLStateManager.glDeleteProgram(0);
|
||||
|
||||
assertEquals(prog, GLStateManager.getActiveProgram(), "glDeleteProgram(0) must be a silent no-op");
|
||||
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(prog);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void activeProgram_clearedOnDelete() {
|
||||
final int prog = linkMinimalProgram();
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
GLStateManager.glUseProgram(prog);
|
||||
assertEquals(prog, GLStateManager.getActiveProgram());
|
||||
|
||||
GLStateManager.glDeleteProgram(prog);
|
||||
|
||||
assertEquals(0, GLStateManager.getActiveProgram(), "activeProgram not cleared when the active program was deleted");
|
||||
|
||||
GLStateManager.glUseProgram(0);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
private static int linkMinimalProgram() {
|
||||
final int vert = GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER);
|
||||
GLStateManager.glShaderSource(vert, MIN_VERT_SRC);
|
||||
GLStateManager.glCompileShader(vert);
|
||||
if (GL20.glGetShaderi(vert, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
|
||||
throw new AssertionError("vertex shader failed to compile: " + GL20.glGetShaderInfoLog(vert, 4096));
|
||||
}
|
||||
|
||||
final int frag = GLStateManager.glCreateShader(GL20.GL_FRAGMENT_SHADER);
|
||||
GLStateManager.glShaderSource(frag, MIN_FRAG_SRC);
|
||||
GLStateManager.glCompileShader(frag);
|
||||
if (GL20.glGetShaderi(frag, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
|
||||
throw new AssertionError("fragment shader failed to compile: " + GL20.glGetShaderInfoLog(frag, 4096));
|
||||
}
|
||||
|
||||
final int prog = GLStateManager.glCreateProgram();
|
||||
GLStateManager.glAttachShader(prog, vert);
|
||||
GLStateManager.glAttachShader(prog, frag);
|
||||
GLStateManager.glLinkProgram(prog);
|
||||
if (GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
|
||||
throw new AssertionError("program failed to link: " + GL20.glGetProgramInfoLog(prog, 4096));
|
||||
}
|
||||
GLStateManager.glDetachShader(prog, vert);
|
||||
GLStateManager.glDetachShader(prog, frag);
|
||||
GLStateManager.glDeleteShader(vert);
|
||||
GLStateManager.glDeleteShader(frag);
|
||||
return prog;
|
||||
}
|
||||
|
||||
private static final String MIN_VERT_SRC = "#version 330 core\nvoid main(){gl_Position=vec4(0.0);}";
|
||||
private static final String MIN_FRAG_SRC = "#version 330 core\nout vec4 oC;void main(){oC=vec4(1.0);}";
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.AttribLayoutKey;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
||||
class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
@Test
|
||||
void compileAndExecute_firesLiveAndBakes() {
|
||||
// COMPILE_AND_EXECUTE must do two things: (1) run the draw immediately,
|
||||
// (2) record a placeholder so the list can replay it later. A samples-passed
|
||||
// query wrapping the record block confirms the live half; asserting the
|
||||
// placeholder post-endList confirms the baked half.
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 2, 3, 0);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
GL11.glViewport(0, 0, 16, 16);
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
final int query = GL15.glGenQueries();
|
||||
GL15.glBeginQuery(GL15.GL_SAMPLES_PASSED, query);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
GL15.glEndQuery(GL15.GL_SAMPLES_PASSED);
|
||||
final int samples = GL15.glGetQueryObjecti(query, GL15.GL_QUERY_RESULT);
|
||||
GL15.glDeleteQueries(query);
|
||||
|
||||
assertTrue(samples > 0, "COMPILE_AND_EXECUTE did not fire the live draw (samples=" + samples + ")");
|
||||
|
||||
final BatchedIndexedDrawCmd baked = firstBatchedCmd(list);
|
||||
assertNotNull(baked, "COMPILE_AND_EXECUTE did not bake a placeholder");
|
||||
assertEquals(6, baked.getIndexCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharedVaoRetainsItsEbo() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int callerEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, callerEbo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
final BatchedIndexedDrawCmd batched = firstBatchedCmd(list);
|
||||
assertNotNull(batched);
|
||||
final IndexedDrawBatch batch = firstBatch(list);
|
||||
|
||||
GLStateManager.glBindVertexArray(batched.getSharedVAO());
|
||||
assertEquals(batch.getSharedEBO(), GLStateManager.getBoundEBO(),
|
||||
"batched VAO's EBO binding corrupted");
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replayAfterSourceMutationAndStateChange() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int callerEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 2, 3, 0);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, callerEbo, GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
overwriteBufferWithGarbage(GL15.GL_ARRAY_BUFFER, vbo, 4 * POS_COLOR_STRIDE);
|
||||
overwriteBufferWithGarbage(GL15.GL_ELEMENT_ARRAY_BUFFER, callerEbo, 6 * 2);
|
||||
|
||||
// Bind unrelated state so the baked command has to restore its own bindings.
|
||||
final int distractorVao = newVao();
|
||||
GLStateManager.glBindVertexArray(distractorVao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
compiledOf(list).render();
|
||||
|
||||
final int err = GL11.glGetError();
|
||||
assertEquals(GL11.GL_NO_ERROR, err,
|
||||
() -> "replay produced GL error 0x" + Integer.toHexString(err));
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replayThenDelete_thenBindBufferCycle_isGlNoError() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
compiledOf(list).render();
|
||||
|
||||
|
||||
final int sharedEbo = firstBatch(list).getSharedEBO();
|
||||
final int foreignVao = newVao();
|
||||
GLStateManager.glBindVertexArray(foreignVao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEbo);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
untrackList(list);
|
||||
|
||||
GLStateManager.glBindVertexArray(foreignVao);
|
||||
assertEquals(0, GLStateManager.getBoundEBO(), "batched EBO id survived list delete in vaoEboMap — non-gen crash imminent");
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
final int err = GL11.glGetError();
|
||||
assertEquals(GL11.GL_NO_ERROR, err, () -> "post-delete bindBuffer(" + prevEbo + ") hit GL 0x" + Integer.toHexString(err));
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_doesNotRestoreVAO_backToBackDraws() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size());
|
||||
final int sharedVAO = cmds.getFirst().getSharedVAO();
|
||||
assertEquals(sharedVAO, cmds.get(1).getSharedVAO());
|
||||
|
||||
final int distractor = newVao();
|
||||
GLStateManager.glBindVertexArray(distractor);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
compiledOf(list).render();
|
||||
|
||||
assertEquals(sharedVAO, GLStateManager.getBoundVAO());
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void build_partialFailure_deletesEarlierBatchesAndLeavesPlaceholdersUnfilled() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
|
||||
final IndexedDrawCapture good = IndexedDrawCapture.create(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L, ebo);
|
||||
assertNotNull(good);
|
||||
|
||||
// Synthetic capture with a different layout that trips the >2GiB size guard.
|
||||
final AttribLayoutKey otherLayout = new AttribLayoutKey(
|
||||
new int[]{2}, new int[]{4}, new int[]{GL11.GL_FLOAT}, new boolean[]{false});
|
||||
final ByteBuffer tinyVtx = MemoryUtilities.memAlloc(16);
|
||||
final ByteBuffer tinyIdx = MemoryUtilities.memAlloc(16);
|
||||
final IndexedDrawCapture huge = new IndexedDrawCapture(
|
||||
otherLayout, GL11.GL_TRIANGLES,
|
||||
Integer.MAX_VALUE / 4, 1, tinyVtx, tinyIdx,
|
||||
new BatchedIndexedDrawCmd());
|
||||
|
||||
final IndexedDrawBatchBuilder builder = new IndexedDrawBatchBuilder();
|
||||
builder.add(good);
|
||||
builder.add(huge);
|
||||
|
||||
try {
|
||||
assertThrows(IllegalStateException.class, builder::build);
|
||||
assertEquals(0, good.placeholder.getSharedVAO());
|
||||
assertEquals(0, huge.placeholder.getSharedVAO());
|
||||
} finally {
|
||||
MemoryUtilities.memFree(tinyVtx);
|
||||
MemoryUtilities.memFree(tinyIdx);
|
||||
good.freeBuffers();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void recorderFree_releasesUnfilledCaptureNativeBuffers() {
|
||||
final CommandRecorder rec = new CommandRecorder();
|
||||
final ByteBuffer vtx = MemoryUtilities.memAlloc(64);
|
||||
final ByteBuffer idx = MemoryUtilities.memAlloc(32);
|
||||
final IndexedDrawCapture cap = new IndexedDrawCapture(
|
||||
new AttribLayoutKey(new int[]{0}, new int[]{2}, new int[]{GL11.GL_FLOAT}, new boolean[]{false}),
|
||||
GL11.GL_TRIANGLES, 4, 6, vtx, idx, new BatchedIndexedDrawCmd());
|
||||
|
||||
rec.recordIndexedDrawCapture(cap);
|
||||
assertFalse(cap.isFreed());
|
||||
|
||||
rec.free();
|
||||
|
||||
assertTrue(cap.isFreed());
|
||||
cap.freeBuffers();
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_idempotentReplay_doesNotErrorOnSecondPass() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
|
||||
compiledOf(list).render();
|
||||
compiledOf(list).render();
|
||||
|
||||
final int err = GL11.glGetError();
|
||||
assertEquals(GL11.GL_NO_ERROR, err,
|
||||
() -> "double replay produced GL error 0x" + Integer.toHexString(err));
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
class AttribLayoutKeyTest {
|
||||
|
||||
private static int[] offsetsOf(AttribLayoutKey k) {
|
||||
final int[] out = new int[k.locations.length];
|
||||
for (int i = 0; i < out.length; i++) out[i] = k.offset(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardPosColorUvNormal_isAlignedAndPacked() {
|
||||
final AttribLayoutKey k = new AttribLayoutKey(
|
||||
new int[]{0, 1, 2, 4},
|
||||
new int[]{3, 4, 2, 3},
|
||||
new int[]{GL11.GL_FLOAT, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT, GL11.GL_BYTE},
|
||||
new boolean[]{false, true, false, true});
|
||||
assertArrayEquals(new int[]{0, 12, 16, 24}, offsetsOf(k));
|
||||
assertEquals(28, k.stride());
|
||||
}
|
||||
|
||||
@Test
|
||||
void posUvNormal3_roundsTailStrideTo24() {
|
||||
final AttribLayoutKey k = new AttribLayoutKey(
|
||||
new int[]{0, 2, 4},
|
||||
new int[]{3, 2, 3},
|
||||
new int[]{GL11.GL_FLOAT, GL11.GL_FLOAT, GL11.GL_BYTE},
|
||||
new boolean[]{false, false, true});
|
||||
assertArrayEquals(new int[]{0, 12, 20}, offsetsOf(k));
|
||||
assertEquals(24, k.stride());
|
||||
}
|
||||
|
||||
@Test
|
||||
void interiorHazardOrder_roundsMidAttribOffset() {
|
||||
final AttribLayoutKey k = new AttribLayoutKey(
|
||||
new int[]{0, 4, 2},
|
||||
new int[]{3, 3, 2},
|
||||
new int[]{GL11.GL_FLOAT, GL11.GL_BYTE, GL11.GL_FLOAT},
|
||||
new boolean[]{false, true, false});
|
||||
assertArrayEquals(new int[]{0, 12, 16}, offsetsOf(k));
|
||||
assertEquals(24, k.stride());
|
||||
}
|
||||
|
||||
@Test
|
||||
void allByteLayout_packsTight() {
|
||||
final AttribLayoutKey k = new AttribLayoutKey(
|
||||
new int[]{0, 1, 2},
|
||||
new int[]{3, 4, 2},
|
||||
new int[]{GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE},
|
||||
new boolean[]{true, true, false});
|
||||
assertArrayEquals(new int[]{0, 3, 7}, offsetsOf(k));
|
||||
assertEquals(9, k.stride());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shortsFollowingByte_roundToEvenAndStrideToEven() {
|
||||
final AttribLayoutKey k = new AttribLayoutKey(
|
||||
new int[]{0, 1},
|
||||
new int[]{1, 2},
|
||||
new int[]{GL11.GL_BYTE, GL11.GL_SHORT},
|
||||
new boolean[]{true, false});
|
||||
assertArrayEquals(new int[]{0, 2}, offsetsOf(k));
|
||||
assertEquals(6, k.stride());
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalInputs_produceEqualKeys() {
|
||||
final AttribLayoutKey a = new AttribLayoutKey(
|
||||
new int[]{0, 2}, new int[]{3, 2},
|
||||
new int[]{GL11.GL_FLOAT, GL11.GL_FLOAT}, new boolean[]{false, false});
|
||||
final AttribLayoutKey b = new AttribLayoutKey(
|
||||
new int[]{0, 2}, new int[]{3, 2},
|
||||
new int[]{GL11.GL_FLOAT, GL11.GL_FLOAT}, new boolean[]{false, false});
|
||||
assertEquals(a, b);
|
||||
assertEquals(a.hashCode(), b.hashCode());
|
||||
assertEquals(a.stride(), b.stride());
|
||||
assertArrayEquals(offsetsOf(a), offsetsOf(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
void differentNormalized_producesDifferentKeys() {
|
||||
final AttribLayoutKey a = new AttribLayoutKey(
|
||||
new int[]{0}, new int[]{4}, new int[]{GL11.GL_UNSIGNED_BYTE}, new boolean[]{true});
|
||||
final AttribLayoutKey b = new AttribLayoutKey(
|
||||
new int[]{0}, new int[]{4}, new int[]{GL11.GL_UNSIGNED_BYTE}, new boolean[]{false});
|
||||
assertNotEquals(a, b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.support;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMExtension;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@ExtendWith(GLSMExtension.class)
|
||||
public abstract class DisplayListTestFixture {
|
||||
|
||||
public static final float[] DEFAULT_QUAD_POSITIONS = {0f, 0f, 1f, 0f, 1f, 1f, 0f, 1f};
|
||||
public static final byte[][] DEFAULT_SOLID_COLORS = {
|
||||
{1, 1, 1, 1}, {2, 2, 2, 2}, {3, 3, 3, 3}, {4, 4, 4, 4}
|
||||
};
|
||||
|
||||
public static final int POS_COLOR_STRIDE = 12;
|
||||
|
||||
private final List<Integer> listsToCleanup = new ArrayList<>();
|
||||
private final List<Integer> buffersToCleanup = new ArrayList<>();
|
||||
private final List<Integer> vaosToCleanup = new ArrayList<>();
|
||||
|
||||
@AfterEach
|
||||
void cleanupFixtureResources() {
|
||||
for (int list : listsToCleanup) GLStateManager.glDeleteLists(list, 1);
|
||||
for (int buf : buffersToCleanup) {
|
||||
if (GL15.glIsBuffer(buf)) GL15.glDeleteBuffers(buf);
|
||||
}
|
||||
for (int vao : vaosToCleanup) {
|
||||
if (GL30.glIsVertexArray(vao)) GLStateManager.glDeleteVertexArrays(vao);
|
||||
}
|
||||
listsToCleanup.clear();
|
||||
buffersToCleanup.clear();
|
||||
vaosToCleanup.clear();
|
||||
}
|
||||
|
||||
protected final int newList() {
|
||||
final int id = GL11.glGenLists(1);
|
||||
listsToCleanup.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
protected final void untrackList(int list) {
|
||||
listsToCleanup.remove((Integer) list);
|
||||
}
|
||||
|
||||
protected final int newBuffer() {
|
||||
final int id = GL15.glGenBuffers();
|
||||
buffersToCleanup.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
protected final int newVao() {
|
||||
final int id = GL30.glGenVertexArrays();
|
||||
vaosToCleanup.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** 2 floats pos + 4 bytes color, stride 12, one vertex per entry in {@code colors}. */
|
||||
protected final int uploadPosColorVbo(float[] positions, byte[][] colors) {
|
||||
if (positions.length / 2 != colors.length) {
|
||||
throw new IllegalArgumentException("positions/colors length mismatch");
|
||||
}
|
||||
final int n = colors.length;
|
||||
final int vbo = newBuffer();
|
||||
final ByteBuffer data = MemoryUtilities.memAlloc(n * POS_COLOR_STRIDE).order(ByteOrder.nativeOrder());
|
||||
for (int i = 0; i < n; i++) {
|
||||
data.putFloat(positions[i * 2]);
|
||||
data.putFloat(positions[i * 2 + 1]);
|
||||
data.put(colors[i][0]);
|
||||
data.put(colors[i][1]);
|
||||
data.put(colors[i][2]);
|
||||
data.put(colors[i][3]);
|
||||
}
|
||||
data.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
MemoryUtilities.memFree(data);
|
||||
return vbo;
|
||||
}
|
||||
|
||||
/** Pos+color VBO populated with {@link #DEFAULT_QUAD_POSITIONS} / {@link #DEFAULT_SOLID_COLORS}. */
|
||||
protected final int defaultQuadVbo() {
|
||||
return uploadPosColorVbo(DEFAULT_QUAD_POSITIONS, DEFAULT_SOLID_COLORS);
|
||||
}
|
||||
|
||||
protected final int uploadEbo(int indexType, int... values) {
|
||||
final int bytesPer = switch (indexType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> 1;
|
||||
case GL11.GL_UNSIGNED_SHORT -> 2;
|
||||
case GL11.GL_UNSIGNED_INT -> 4;
|
||||
default -> throw new IllegalArgumentException("unsupported indexType 0x" + Integer.toHexString(indexType));
|
||||
};
|
||||
final int ebo = newBuffer();
|
||||
final ByteBuffer data = MemoryUtilities.memAlloc(values.length * bytesPer).order(ByteOrder.nativeOrder());
|
||||
for (int v : values) {
|
||||
switch (indexType) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> data.put((byte) v);
|
||||
case GL11.GL_UNSIGNED_SHORT -> data.putShort((short) v);
|
||||
case GL11.GL_UNSIGNED_INT -> data.putInt(v);
|
||||
}
|
||||
}
|
||||
data.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
MemoryUtilities.memFree(data);
|
||||
return ebo;
|
||||
}
|
||||
|
||||
/** VAO with loc0 = 2*FLOAT pos, loc1 = 4*UNSIGNED_BYTE norm color, stride 12. */
|
||||
protected final int setupPosColorVao(int vbo) {
|
||||
final int vao = newVao();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glVertexAttribPointer(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, 8);
|
||||
GLStateManager.glEnableVertexAttribArray(1);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
return vao;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record one {@code glDrawElements} into a {@code GL_COMPILE} list with the given VAO and EBO
|
||||
* already-bound setup. Returns the list id (tracked for cleanup).
|
||||
*/
|
||||
protected final int recordSingleIndexedDraw(int vao, int ebo, int drawMode, int indexCount, int indexType) {
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glDrawElements(drawMode, indexCount, indexType, 0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
return list;
|
||||
}
|
||||
|
||||
protected static CompiledDisplayList compiledOf(int listId) {
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
|
||||
assertNotNull(compiled, "compiled display list must exist for id " + listId);
|
||||
return compiled;
|
||||
}
|
||||
|
||||
protected static List<BatchedIndexedDrawCmd> allBatchedCmds(int listId) {
|
||||
final List<BatchedIndexedDrawCmd> out = new ArrayList<>();
|
||||
final Object[] complex = compiledOf(listId).getComplexObjects();
|
||||
if (complex == null) return out;
|
||||
for (Object o : complex) {
|
||||
if (o instanceof BatchedIndexedDrawCmd b) out.add(b);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
protected static BatchedIndexedDrawCmd firstBatchedCmd(int listId) {
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(listId);
|
||||
return cmds.isEmpty() ? null : cmds.getFirst();
|
||||
}
|
||||
|
||||
protected static List<IndexedDrawBatch> batchesOf(int listId) {
|
||||
return compiledOf(listId).getIndexedBatches();
|
||||
}
|
||||
|
||||
protected static IndexedDrawBatch firstBatch(int listId) {
|
||||
return batchesOf(listId).getFirst();
|
||||
}
|
||||
|
||||
protected static ByteBuffer readBufferBytes(int target, int bufferId, int size) {
|
||||
final int prev;
|
||||
if (target == GL15.GL_ARRAY_BUFFER) prev = GLStateManager.getBoundVBO();
|
||||
else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) prev = GLStateManager.getBoundEBO();
|
||||
else prev = 0;
|
||||
|
||||
GLStateManager.glBindBuffer(target, bufferId);
|
||||
final ByteBuffer buf = MemoryUtilities.memAlloc(size).order(ByteOrder.nativeOrder());
|
||||
GL15.glGetBufferSubData(target, 0, buf);
|
||||
GLStateManager.glBindBuffer(target, prev);
|
||||
return buf;
|
||||
}
|
||||
|
||||
protected static void overwriteBufferWithGarbage(int target, int bufferId, int size) {
|
||||
final ByteBuffer garbage = MemoryUtilities.memAlloc(size);
|
||||
for (int i = 0; i < garbage.capacity(); i++) garbage.put(i, (byte) 0xAA);
|
||||
GLStateManager.glBindBuffer(target, bufferId);
|
||||
GL15.glBufferData(target, garbage, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(target, 0);
|
||||
MemoryUtilities.memFree(garbage);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 216 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 187 B |
|
After Width: | Height: | Size: 310 B |
|
After Width: | Height: | Size: 192 B |
|
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;
|
||||
|
||||
@@ -45,23 +45,9 @@ public class CompatConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixSecurityCraft;
|
||||
|
||||
@Config.Comment("Apply tweaks to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean tweakNTMSpace;
|
||||
|
||||
@Config.Comment("Apply fixes to NTM:Space")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixNTMSpace;
|
||||
|
||||
@Config.Comment("Disables rendering of the planet below the player at high altitudes [Requires tweakNTMSpace]")
|
||||
@Config.DefaultEnum("ShadersOnly")
|
||||
public static altitudePlanetRendererState NTMSpace_disableAltitudePlanetRenderer;
|
||||
public enum altitudePlanetRendererState {
|
||||
Always,
|
||||
ShadersOnly,
|
||||
Never
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,21 +19,25 @@ import net.coderbot.iris.texture.pbr.PBRTextureManager;
|
||||
import net.coderbot.iris.vertices.ImmediateState;
|
||||
|
||||
public class IrisGLSMBridge {
|
||||
|
||||
|
||||
private static Runnable alphaFuncListener = null;
|
||||
private static Runnable alphaTestListener = null;
|
||||
private static Runnable blendFuncListener = null;
|
||||
private static Runnable fogModeListener = null;
|
||||
private static Runnable fogStartListener = null;
|
||||
private static Runnable fogEndListener = null;
|
||||
private static Runnable fogDensityListener = null;
|
||||
|
||||
|
||||
static {
|
||||
StateUpdateNotifiers.alphaFuncNotifier = listener -> alphaFuncListener = listener;
|
||||
StateUpdateNotifiers.alphaTestNotifier = listener -> alphaTestListener = listener;
|
||||
StateUpdateNotifiers.blendFuncNotifier = listener -> blendFuncListener = listener;
|
||||
StateUpdateNotifiers.fogModeNotifier = listener -> fogModeListener = listener;
|
||||
StateUpdateNotifiers.fogStartNotifier = listener -> fogStartListener = listener;
|
||||
StateUpdateNotifiers.fogEndNotifier = listener -> fogEndListener = listener;
|
||||
StateUpdateNotifiers.fogDensityNotifier = listener -> fogDensityListener = listener;
|
||||
}
|
||||
|
||||
|
||||
public static void register() {
|
||||
IrisSamplers.initRenderer();
|
||||
GLSMHooks.blendHandler = new DeferredBlendHandler() {
|
||||
@@ -91,13 +95,20 @@ public class IrisGLSMBridge {
|
||||
DepthColorStorage.deferColorMask(r, g, b, a);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.addListener(event -> {
|
||||
if (Iris.enabled) {
|
||||
if (alphaFuncListener != null) alphaFuncListener.run();
|
||||
if (alphaTestListener != null) alphaTestListener.run();
|
||||
}
|
||||
});
|
||||
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.addListener(event -> {
|
||||
if (Iris.enabled) {
|
||||
if (blendFuncListener != null) blendFuncListener.run();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
GLSMHooks.FOG_STATE_CHANGE.addListener(event -> {
|
||||
if (Iris.enabled) {
|
||||
if (fogModeListener != null) fogModeListener.run();
|
||||
@@ -106,7 +117,7 @@ public class IrisGLSMBridge {
|
||||
if (fogDensityListener != null) fogDensityListener.run();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
GLSMHooks.TEXTURE_BIND.addListener(event -> {
|
||||
if (Iris.enabled) {
|
||||
TextureTracker.INSTANCE.onBindTexture();
|
||||
|
||||
@@ -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,22 @@ 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(
|
||||
"/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;
|
||||
|
||||
@@ -287,6 +287,14 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
DRAGONAPI_SHADER_REGISTRY_PARSE_ERROR(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.addRequiredMod(TargetedMod.DRAGON_API)
|
||||
.addClientMixins(
|
||||
"dragonapi.MixinShaderRegistry_ParseError"
|
||||
)
|
||||
),
|
||||
|
||||
IRIS_RENDERING_NOBACKHAND(new MixinBuilder("Iris Hand Shaders")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
@@ -380,17 +388,16 @@ public enum Mixins implements IMixins {
|
||||
.addRequiredMod(TargetedMod.MINEFACTORY_RELOADED)
|
||||
.setApplyIf(() -> CompatConfig.fixMinefactoryReloaded)
|
||||
.addClientMixins("client.minefactoryreloaded.MixinRedNetCableRenderer")),
|
||||
|
||||
NTM_SPACE_TWEAKS(new MixinBuilder("Support for 'Disable Horizon' & 'disableAltitudePlanetRenderer' options in NTM:Space")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.NTM_SPACE)
|
||||
.setApplyIf(() -> CompatConfig.tweakNTMSpace)
|
||||
.addClientMixins("client.ntmSpace.MixinSkyProviderCelestial_Tweaks")),
|
||||
NTM_SPACE_SHADER_COMPAT(new MixinBuilder("Multiple shader fixes for NTM:Space")
|
||||
|
||||
NTM_SPACE_COMPAT(new MixinBuilder("Multiple fixes for NTM:Space")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.NTM_SPACE)
|
||||
.setApplyIf(() -> CompatConfig.fixNTMSpace && AngelicaConfig.enableIris)
|
||||
.addClientMixins("client.ntmSpace.MixinSkyProviderCelestial_ShaderCompat", "client.ntmSpace.MixinSkyProviderLaytheSunset")),
|
||||
.addClientMixins(
|
||||
"client.ntmSpace.MixinSkyProviderCelestial",
|
||||
"client.ntmSpace.MixinSkyProviderOrbit",
|
||||
"client.ntmSpace.MixinSkyProviderLaytheSunset"
|
||||
)),
|
||||
|
||||
SPEEDUP_CAMPFIRE_BACKPORT_ANIMATIONS(new MixinBuilder("Add animation speedup support to Campfire Backport")
|
||||
.setPhase(Phase.LATE)
|
||||
|
||||
@@ -43,6 +43,7 @@ import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import jss.notfine.core.Settings;
|
||||
import jss.notfine.gui.GuiCustomMenu;
|
||||
import jss.notfine.gui.NotFineGameOptionPages;
|
||||
import jss.notfine.gui.options.named.FOVMode;
|
||||
import me.flashyreese.mods.reeses_sodium_options.client.gui.ReeseSodiumVideoOptionsScreen;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
|
||||
@@ -342,10 +343,19 @@ public final class ClientProxy extends CommonProxy {
|
||||
}
|
||||
}
|
||||
|
||||
// This is a bit of a hack to prevent the FOV from being modified by other mods
|
||||
// This only disables FOV changes from vanilla, leaving mod FOV changes untouched
|
||||
@SubscribeEvent(priority = EventPriority.HIGHEST)
|
||||
public void onFOVModifierUpdateEarly(FOVUpdateEvent event) {
|
||||
if(Settings.DYNAMIC_FOV.option.getStore() == FOVMode.MODS) {
|
||||
event.fov = 1.0F;
|
||||
event.newfov = 1.0F;
|
||||
}
|
||||
}
|
||||
|
||||
// This removes all FOV dynamics, both from vanilla (speed/sprinting), and mods
|
||||
@SubscribeEvent(priority = EventPriority.LOWEST)
|
||||
public void onFOVModifierUpdate(FOVUpdateEvent event) {
|
||||
if (!(boolean) Settings.DYNAMIC_FOV.option.getStore()) {
|
||||
public void onFOVModifierUpdateLate(FOVUpdateEvent event) {
|
||||
if(Settings.DYNAMIC_FOV.option.getStore() == FOVMode.NONE) {
|
||||
event.newfov = 1.0F;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,4 +16,8 @@ public interface StateAwareTessellator {
|
||||
void angelica$setCeleritasMeshing(boolean active);
|
||||
|
||||
int[] angelica$getVertexStates();
|
||||
|
||||
int[] angelica$getShaderOverrideBlockIds();
|
||||
|
||||
void angelica$setShaderOverrideBlockId(short blockId);
|
||||
}
|
||||
@@ -34,11 +34,13 @@ public final class TileEntityRenderBoundsRegistry {
|
||||
}
|
||||
|
||||
private static boolean probeAndCache(TileEntity te, Class<? extends TileEntity> clazz) {
|
||||
boolean isInfinite = false;
|
||||
boolean isInfinite;
|
||||
try {
|
||||
final AxisAlignedBB aabb = te.getRenderBoundingBox();
|
||||
isInfinite = isInfiniteExtentsBox(aabb);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Throwable t) {
|
||||
isInfinite = true;
|
||||
}
|
||||
|
||||
synchronized (classRegistry) {
|
||||
classRegistry.put(clazz, isInfinite ? INFINITE : FINITE);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -144,13 +144,18 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
if (TileEntityRenderBoundsRegistry.isAlwaysInfiniteExtent(tileEntity)) {
|
||||
isGlobal = true;
|
||||
} else {
|
||||
final AxisAlignedBB aabb = tileEntity.getRenderBoundingBox();
|
||||
AxisAlignedBB aabb;
|
||||
try {
|
||||
aabb = tileEntity.getRenderBoundingBox();
|
||||
} catch (Throwable t) {
|
||||
aabb = null;
|
||||
}
|
||||
if (aabb != null) {
|
||||
final int secMinX = x & ~15, secMinY = y & ~15, secMinZ = z & ~15;
|
||||
isGlobal = aabb.minX < secMinX || aabb.minY < secMinY || aabb.minZ < secMinZ
|
||||
|| aabb.maxX > secMinX + 16 || aabb.maxY > secMinY + 16 || aabb.maxZ > secMinZ + 16;
|
||||
} else {
|
||||
isGlobal = false;
|
||||
isGlobal = true;
|
||||
}
|
||||
}
|
||||
(isGlobal ? renderData.globalBlockEntities : renderData.culledBlockEntities).add(tileEntity);
|
||||
@@ -270,6 +275,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.utils;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class WorkaroundUtils {
|
||||
|
||||
public static ResourceLocation GL_TEXTURE_2D_WORKAROUND = new ResourceLocation("angelica:textures/shaders_workaround.png");
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -219,6 +233,7 @@ public class CTMUtils {
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
clearCurrentCompact();
|
||||
}
|
||||
|
||||
private static boolean checkFace(int face) {
|
||||
|
||||
@@ -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,7 @@ 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 +122,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,6 +11,7 @@ import jss.notfine.gui.options.control.NotFineControlValueFormatter;
|
||||
import jss.notfine.gui.options.named.AlwaysNever;
|
||||
import jss.notfine.gui.options.named.BackgroundSelect;
|
||||
import jss.notfine.gui.options.named.DownfallQuality;
|
||||
import jss.notfine.gui.options.named.FOVMode;
|
||||
import jss.notfine.gui.options.named.GraphicsQualityOff;
|
||||
import jss.notfine.gui.options.named.LeavesQuality;
|
||||
import jss.notfine.gui.options.named.GraphicsToggle;
|
||||
@@ -49,7 +50,7 @@ public enum Settings {
|
||||
SettingsManager.downfallDistanceUpdated();
|
||||
}
|
||||
},
|
||||
DYNAMIC_FOV(new NotFineOptionTickBox(true, null)),
|
||||
DYNAMIC_FOV(new NotFineOptionCycling<>(FOVMode.ALL, OptionImpact.VARIES)),
|
||||
HURT_SHAKE(new NotFineOptionSliderPercentage(100, 0, 300, 5, OptionImpact.LOW)),
|
||||
DYNAMIC_LIGHTS(new NotFineOptionCycling<>(DynamicLightsMode.FANCY, OptionImpact.VARIES)){
|
||||
@Override
|
||||
@@ -99,7 +100,7 @@ public enum Settings {
|
||||
ChunkRebuildManager.setMaxTicksWaiting((int) this.option.getStore());
|
||||
}
|
||||
},
|
||||
HORIZON(new NotFineOptionTickBox(true, OptionImpact.LOW)),
|
||||
HORIZON(new NotFineOptionTickBox(false, OptionImpact.LOW)),
|
||||
TERRAIN_FOG(new NotFineOptionTickBox(true, OptionImpact.LOW)),
|
||||
FOG_NEAR_DISTANCE(new NotFineOptionSliderPercentage(75, 1, 100, 1, OptionImpact.LOW)),
|
||||
GUI_BACKGROUND(new NotFineOptionCycling<>(BackgroundSelect.DEFAULT, null)) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package jss.notfine.gui.options.named;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.NamedState;
|
||||
|
||||
public enum FOVMode implements NamedState {
|
||||
ALL("options.fov.all"),
|
||||
MODS("options.fov.mods"),
|
||||
NONE("options.fov.none");
|
||||
|
||||
private final String name;
|
||||
|
||||
FOVMode(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,10 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
|
||||
import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
|
||||
import net.coderbot.iris.shaderpack.materialmap.FlatteningMap;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.init.Blocks;
|
||||
@@ -39,8 +41,16 @@ public class BlockMaterialMapping {
|
||||
Map<Block, BlockRenderLayer> blockTypeIds = new Reference2ReferenceOpenHashMap<>();
|
||||
|
||||
blockPropertiesMap.forEach((id, blockType) -> {
|
||||
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
|
||||
final Block block = Block.getBlockFromName(resourceLocation.toString());
|
||||
Block block = resolveBlock(id);
|
||||
|
||||
// Try flattening map for modern names
|
||||
if ((block == null || block == Blocks.air) && "minecraft".equals(id.getNamespace())) {
|
||||
List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(id.getName(), Map.of());
|
||||
if (legacyEntries != null) {
|
||||
// Use the first entry for render type (render type applies per-block)
|
||||
block = resolveBlock(legacyEntries.getFirst().getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (block == null || block == Blocks.air) {
|
||||
return;
|
||||
@@ -73,19 +83,32 @@ public class BlockMaterialMapping {
|
||||
* Based on Iris's addBlockStates method, adapted for 1.7.10 metadata system.
|
||||
*/
|
||||
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId) {
|
||||
final NamespacedId id = entry.getId();
|
||||
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
|
||||
NamespacedId id = entry.getId();
|
||||
String name = id.getName();
|
||||
boolean hasStateProps = !entry.getStateProperties().isEmpty();
|
||||
boolean hasExplicitMetas = !entry.getMetas().isEmpty();
|
||||
|
||||
final Block block = (Block) Block.blockRegistry.getObject(resourceLocation.toString());
|
||||
if ("minecraft".equals(id.getNamespace()) && (hasStateProps || !hasExplicitMetas)) {
|
||||
List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(name, entry.getStateProperties());
|
||||
if (legacyEntries != null) {
|
||||
for (BlockEntry legacy : legacyEntries) {
|
||||
applyMetas(resolveBlock(legacy.getId()), legacy.getMetas(), idMap, intId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the block doesn't exist, by default the registry will return AIR. That probably isn't what we want.
|
||||
// TODO: Assuming that Registry.BLOCK.getDefaultId() == "minecraft:air" here
|
||||
// Fall back to registry with the entry's own metas
|
||||
Block block = resolveBlock(id);
|
||||
applyMetas(block, entry.getMetas(), idMap, intId);
|
||||
}
|
||||
// If the block doesn't exist, by default the registry will return AIR. That probably isn't what we want.
|
||||
// TODO: Assuming that Registry.BLOCK.getDefaultId() == "minecraft:air" here
|
||||
private static void applyMetas(Block block, Set<Integer> metas, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId) {
|
||||
if (block == null || block == Blocks.air) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<Integer> metas = entry.getMetas();
|
||||
|
||||
Int2IntMap metaMap = idMap.get(block);
|
||||
if (metaMap == null) {
|
||||
metaMap = new Int2IntOpenHashMap();
|
||||
@@ -94,15 +117,18 @@ public class BlockMaterialMapping {
|
||||
}
|
||||
|
||||
if (metas.isEmpty()) {
|
||||
// Add all metadata values (0-15) if there aren't any specific ones
|
||||
for (int meta = 0; meta < 16; meta++) {
|
||||
metaMap.putIfAbsent(meta, intId);
|
||||
}
|
||||
} else {
|
||||
// Add only specific metadata values
|
||||
for (int meta : metas) {
|
||||
metaMap.putIfAbsent(meta, intId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Block resolveBlock(NamespacedId id) {
|
||||
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
|
||||
return (Block) Block.blockRegistry.getObject(resourceLocation.toString());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -8,6 +8,8 @@ public class StateUpdateNotifiers {
|
||||
public static ValueUpdateNotifier fogStartNotifier;
|
||||
public static ValueUpdateNotifier fogEndNotifier;
|
||||
public static ValueUpdateNotifier fogDensityNotifier;
|
||||
public static ValueUpdateNotifier alphaFuncNotifier;
|
||||
public static ValueUpdateNotifier alphaTestNotifier;
|
||||
public static ValueUpdateNotifier blendFuncNotifier;
|
||||
public static ValueUpdateNotifier bindTextureNotifier;
|
||||
public static ValueUpdateNotifier normalTextureChangeNotifier;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -46,7 +46,22 @@ public class CommonTransformer {
|
||||
// runtime discard using the GLSM-tracked alpha reference value.
|
||||
if (found.contains(0) && parameters.patch != Patch.COMPOSITE && parameters.patch != Patch.COMPUTE) {
|
||||
root.injectVariable("uniform float iris_currentAlphaTest;");
|
||||
root.appendMain("if (iris_FragData0.a <= iris_currentAlphaTest) discard;");
|
||||
root.injectVariable("uniform int iris_currentAlphaFunc;");
|
||||
root.injectFunction(
|
||||
"bool iris_alphaTestPass(float a) {" +
|
||||
" if (iris_currentAlphaFunc == 7) return true;" + // ALWAYS / disabled
|
||||
" if (iris_currentAlphaFunc == 0) return false;" + // NEVER
|
||||
" float qa = round(a * 255.0);" +
|
||||
" float qr = round(iris_currentAlphaTest * 255.0);" +
|
||||
" if (iris_currentAlphaFunc == 1) return qa < qr;" + // LESS
|
||||
" if (iris_currentAlphaFunc == 2) return qa == qr;" + // EQUAL
|
||||
" if (iris_currentAlphaFunc == 3) return qa <= qr;" + // LEQUAL
|
||||
" if (iris_currentAlphaFunc == 4) return qa > qr;" + // GREATER
|
||||
" if (iris_currentAlphaFunc == 5) return qa != qr;" + // NOTEQUAL
|
||||
" return qa >= qr;" + // GEQUAL (6)
|
||||
"}"
|
||||
);
|
||||
root.appendMain("if (!iris_alphaTestPass(iris_FragData0.a)) discard;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -7,8 +8,10 @@ import it.unimi.dsi.fastutil.objects.Object2IntFunction;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMaps;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
|
||||
import net.coderbot.iris.shaderpack.materialmap.EntityFlatteningMap;
|
||||
import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
|
||||
@@ -55,17 +58,45 @@ public class IdMap {
|
||||
/**
|
||||
* A set of render type overrides for specific blocks. Allows shader packs to move blocks to different render types.
|
||||
*/
|
||||
private Map<NamespacedId, BlockRenderType> blockRenderTypeMap;
|
||||
@Getter
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -112,7 +143,7 @@ public class IdMap {
|
||||
private static String readProperties(Path shaderPath, String name) {
|
||||
try {
|
||||
// ID maps should be encoded in ISO_8859_1.
|
||||
return new String(Files.readAllBytes(shaderPath.resolve(name)), StandardCharsets.ISO_8859_1);
|
||||
return Files.readString(shaderPath.resolve(name), StandardCharsets.ISO_8859_1);
|
||||
} catch (NoSuchFileException e) {
|
||||
Iris.logger.debug("An " + name + " file was not found in the current shaderpack");
|
||||
|
||||
@@ -129,7 +160,24 @@ public class IdMap {
|
||||
}
|
||||
|
||||
private static Object2IntMap<NamespacedId> parseEntityIdMap(Properties properties) {
|
||||
return parseIdMap(properties, "entity.", "entity.properties");
|
||||
Object2IntMap<NamespacedId> idMap = parseIdMap(properties, "entity.", "entity.properties");
|
||||
|
||||
// For modern shader packs: translate modern entity names to 1.7.10 names
|
||||
// so runtime lookups match EntityList's registered names directly.
|
||||
Object2IntMap<NamespacedId> augmented = new Object2IntOpenHashMap<>(idMap);
|
||||
augmented.defaultReturnValue(-1);
|
||||
|
||||
for (Object2IntMap.Entry<NamespacedId> entry : idMap.object2IntEntrySet()) {
|
||||
NamespacedId id = entry.getKey();
|
||||
if ("minecraft".equals(id.getNamespace())) {
|
||||
String legacyName = EntityFlatteningMap.toLegacy(id.getName());
|
||||
if (legacyName != null) {
|
||||
augmented.putIfAbsent(new NamespacedId(legacyName), entry.getIntValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object2IntMaps.unmodifiable(augmented);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,7 +229,7 @@ public class IdMap {
|
||||
} else if (c == '"') {
|
||||
inQuotes = !inQuotes;
|
||||
} else if (Character.isWhitespace(c) && !inQuotes) {
|
||||
if (current.length() > 0) {
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
current.setLength(0);
|
||||
}
|
||||
@@ -201,7 +249,7 @@ public class IdMap {
|
||||
}
|
||||
|
||||
// Add final token
|
||||
if (current.length() > 0) {
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
}
|
||||
|
||||
@@ -353,11 +401,7 @@ public class IdMap {
|
||||
return entityIdMap;
|
||||
}
|
||||
|
||||
public Map<NamespacedId, BlockRenderType> getBlockRenderTypeMap() {
|
||||
return blockRenderTypeMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
|
||||
@@ -1,98 +1,49 @@
|
||||
package net.coderbot.iris.shaderpack;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.Function;
|
||||
import cpw.mods.fml.common.registry.GameData;
|
||||
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;
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class LegacyIdMap {
|
||||
private static final ImmutableList<String> COLORS =
|
||||
ImmutableList.of("white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray",
|
||||
"light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black");
|
||||
|
||||
private static final ImmutableList<String> WOOD_TYPES =
|
||||
ImmutableList.of("oak", "birch", "jungle", "spruce", "acacia", "dark_oak");
|
||||
|
||||
/**
|
||||
* Fills the block ID map with all vanilla blocks in the case where block.properties is not found.
|
||||
*
|
||||
* ShadersMod passed block IDs as a signed byte, so IDs 128-255 wrapped to -128 to -1.
|
||||
* For example, emerald_block (ID 133) becomes -123. Shader packs like SEUS v11 were written
|
||||
* against this behavior, so we replicate it here.
|
||||
*/
|
||||
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"));
|
||||
add(blockIdMap, 4, block("cobblestone"));
|
||||
for (String name : (Iterable<String>) GameData.getBlockRegistry().getKeys()) {
|
||||
// Only include vanilla minecraft blocks
|
||||
if (!name.startsWith("minecraft:")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
add(blockIdMap, 50, block("torch"));
|
||||
add(blockIdMap, 89, block("glowstone"));
|
||||
final Block block = (Block) GameData.getBlockRegistry().getObject(name);
|
||||
if (block == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: what about inactive redstone lamps?
|
||||
add(blockIdMap, 124, block("redstone_lamp"));
|
||||
final int id = GameData.getBlockRegistry().getIDForObject(block);
|
||||
if (id <= 0 || id > 255) {
|
||||
continue;
|
||||
}
|
||||
|
||||
add(blockIdMap, 12, block("sand"));
|
||||
add(blockIdMap, 24, block("sandstone"));
|
||||
// Apply signed byte overflow
|
||||
final int overflowId = (byte) id;
|
||||
|
||||
add(blockIdMap, 41, block("gold_block"));
|
||||
add(blockIdMap, 42, block("iron_block"));
|
||||
add(blockIdMap, 57, block("diamond_block"));
|
||||
// Apparently this is what SEUS v11 expects? Maybe old shadersmod was buggy.
|
||||
add(blockIdMap, -123, block("emerald_block"));
|
||||
final String blockName = name.substring("minecraft:".length());
|
||||
final BlockEntry entry = new BlockEntry(new NamespacedId("minecraft", blockName), Collections.emptySet());
|
||||
|
||||
addMany(blockIdMap, 35, COLORS, color -> block(color + "_wool"));
|
||||
|
||||
// NB: Use the "still" IDs for water and lava, since some shader packs don't properly support the "flowing"
|
||||
// versions: https://github.com/IrisShaders/Iris/issues/1462
|
||||
add(blockIdMap, 9, block("water"));
|
||||
add(blockIdMap, 11, block("lava"));
|
||||
add(blockIdMap, 79, block("ice"));
|
||||
|
||||
addMany(blockIdMap, 18, WOOD_TYPES, woodType -> block(woodType + "_leaves"));
|
||||
|
||||
addMany(blockIdMap, 95, COLORS, color -> block(color + "_stained_glass"));
|
||||
addMany(blockIdMap, 160, COLORS, color -> block(color + "_stained_glass_pane"));
|
||||
|
||||
// Short grass / bush
|
||||
add(blockIdMap, 31, block("grass"), block("seagrass"), block("sweet_berry_bush"));
|
||||
|
||||
// Crops (59 = wheat), but we include carrots and potatoes too.
|
||||
add(blockIdMap, 59, block("wheat"), block("carrots"), block("potatoes"));
|
||||
|
||||
// Small flowers
|
||||
add(blockIdMap, 37, block("dandelion"), block("poppy"), block("blue_orchid"),
|
||||
block("allium"), block("azure_bluet"), block("red_tulip"), block("pink_tulip"),
|
||||
block("white_tulip"), block("orange_tulip"), block("oxeye_daisy"),
|
||||
block("cornflower"), block("lily_of_the_valley"), block("wither_rose"));
|
||||
|
||||
// Big tall grass / flowers
|
||||
// Also include seagrass here
|
||||
add(blockIdMap, 175, block("sunflower"), block("lilac"), block("tall_grass"),
|
||||
block("large_fern"), block("rose_bush"), block("peony"), block("tall_seagrass"));
|
||||
|
||||
// Fire
|
||||
add(blockIdMap, 51, block("fire"));
|
||||
|
||||
// Lily pad
|
||||
add(blockIdMap, 111, block("lily_pad"));
|
||||
|
||||
// TODO: 76 -> redstone_torch (on)
|
||||
}
|
||||
|
||||
private static BlockEntry block(String name) {
|
||||
return new BlockEntry(new NamespacedId("minecraft", name), Collections.emptySet());
|
||||
}
|
||||
|
||||
private static void addMany(Int2ObjectMap<List<BlockEntry>> blockIdMap, int id, List<String> prefixes, Function<String, BlockEntry> toId) {
|
||||
List<BlockEntry> entries = new ArrayList<>();
|
||||
|
||||
for (String prefix : prefixes) {
|
||||
entries.add(toId.apply(prefix));
|
||||
blockIdMap.computeIfAbsent(overflowId, k -> new ArrayList<>()).add(entry);
|
||||
}
|
||||
|
||||
blockIdMap.put(id, entries);
|
||||
}
|
||||
|
||||
private static void add(Int2ObjectMap<List<BlockEntry>> blockIdMap, int id, BlockEntry... entries) {
|
||||
blockIdMap.put(id, Arrays.asList(entries));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -14,10 +16,16 @@ import java.util.Set;
|
||||
public class BlockEntry {
|
||||
private final NamespacedId id;
|
||||
private final Set<Integer> metas;
|
||||
private final Map<String, String> stateProperties;
|
||||
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas) {
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas) {
|
||||
this(id, metas, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas, Map<String, String> stateProperties) {
|
||||
this.id = id;
|
||||
this.metas = metas;
|
||||
this.stateProperties = stateProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,60 +47,66 @@ public class BlockEntry {
|
||||
return new BlockEntry(new NamespacedId("minecraft", entry), Collections.emptySet());
|
||||
}
|
||||
|
||||
// Examples of what we'll accept
|
||||
// Examples of what we accept:
|
||||
// stone
|
||||
// stone:0
|
||||
// minecraft:stone:0
|
||||
// minecraft:stone:0,1,2 # Theoretically valid, but I haven't seen any examples in actual shaders
|
||||
// minecraft:stone:0,1,2
|
||||
// minecraft:furnace:lit=true (blockstate property)
|
||||
// minecraft:oak_log:axis=y (blockstate property)
|
||||
|
||||
// Examples of what we don't (Yet?) accept - Seems to be from MC 1.8+
|
||||
// minecraft:farmland:moisture=0
|
||||
// minecraft:farmland:moisture=1
|
||||
// minecraft:double_plant:half=lower
|
||||
// minecraft:double_plant:half=upper
|
||||
// minecraft:grass:snowy=true
|
||||
// minecraft:unpowered_comparator:powered=false
|
||||
|
||||
|
||||
// Less trivial case: no metas involved, just a namespace
|
||||
//
|
||||
// The first term MUST be a valid ResourceLocation component
|
||||
// The second term, if it is not numeric, must be a valid ResourceLocation component.
|
||||
if (splitStates.length == 2 && !StringUtils.isNumeric(splitStates[1].substring(0, 1))) {
|
||||
// Less trivial case: no metas or state properties, just a namespace
|
||||
if (splitStates.length == 2 && !StringUtils.isNumeric(splitStates[1].substring(0, 1))
|
||||
&& !splitStates[1].contains("=")) {
|
||||
return new BlockEntry(new NamespacedId(splitStates[0], splitStates[1]), Collections.emptySet());
|
||||
}
|
||||
|
||||
// Complex case: One or more states involved...
|
||||
// Complex case: metas and/or state properties
|
||||
final int statesStart;
|
||||
final NamespacedId id;
|
||||
|
||||
if (StringUtils.isNumeric(splitStates[1].substring(0, 1))) {
|
||||
// We have an entry of the form "stone:0"
|
||||
if (splitStates.length == 2) {
|
||||
// "stone:0" or "stone:lit=true"
|
||||
statesStart = 1;
|
||||
id = new NamespacedId("minecraft", splitStates[0]);
|
||||
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
|
||||
// "stone:0:something" or "stone:lit=true:something" — unlikely but handle it
|
||||
statesStart = 1;
|
||||
id = new NamespacedId("minecraft", splitStates[0]);
|
||||
} else {
|
||||
// We have an entry of the form "minecraft:stone:0"
|
||||
// "minecraft:stone:0" or "minecraft:furnace:lit=true"
|
||||
statesStart = 2;
|
||||
id = new NamespacedId(splitStates[0], splitStates[1]);
|
||||
}
|
||||
|
||||
final Set<Integer> metas = new HashSet<>();
|
||||
final Set<Integer> metas = new HashSet<>();
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
|
||||
for (int index = statesStart; index < splitStates.length; index++) {
|
||||
// Parse out one or more metadata ids
|
||||
final String[] metaParts = splitStates[index].split(",");
|
||||
String segment = splitStates[index];
|
||||
|
||||
for (String metaPart : metaParts) {
|
||||
try {
|
||||
metas.add(Integer.parseInt(metaPart));
|
||||
} catch (NumberFormatException e) {
|
||||
Iris.logger.warn("Warning: the block ID map entry \"" + entry + "\" could not be fully parsed:");
|
||||
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but "+ splitStates[index] + " is not of that form!");
|
||||
}
|
||||
}
|
||||
if (segment.contains("=")) {
|
||||
// Blockstate property: key=value
|
||||
for (String prop : segment.split(",")) {
|
||||
int eq = prop.indexOf('=');
|
||||
if (eq > 0 && eq < prop.length() - 1) {
|
||||
properties.put(prop.substring(0, eq), prop.substring(eq + 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Metadata IDs: comma-separated integers
|
||||
for (String metaPart : segment.split(",")) {
|
||||
try {
|
||||
metas.add(Integer.parseInt(metaPart));
|
||||
} catch (NumberFormatException e) {
|
||||
Iris.logger.warn("Warning: the block ID map entry \"" + entry + "\" could not be fully parsed:");
|
||||
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but " + segment + " is not of that form!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new BlockEntry(id, metas);
|
||||
return new BlockEntry(id, metas, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -100,11 +114,11 @@ public class BlockEntry {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final BlockEntry that = (BlockEntry) o;
|
||||
return Objects.equals(id, that.id) && Objects.equals(metas, that.metas);
|
||||
return Objects.equals(id, that.id) && Objects.equals(metas, that.metas) && Objects.equals(stateProperties, that.stateProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, metas);
|
||||
return Objects.hash(id, metas, stateProperties);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package net.coderbot.iris.shaderpack.materialmap;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Maps modern entity names to their 1.7.10 equivalents.
|
||||
*
|
||||
* In 1.11+, entity IDs were changed to namespaced: "minecraft:cave_spider".
|
||||
* In 1.7.10, entities are registered with names like "CaveSpider".
|
||||
* Some entities were also renamed or split into multiple types.
|
||||
*
|
||||
* This map is used at lookup time: when a modern shader pack references an entity by its
|
||||
* modern name, we resolve it to the 1.7.10 name that EntityList uses.
|
||||
*/
|
||||
public class EntityFlatteningMap {
|
||||
|
||||
/** Maps modern entity name -> 1.7.10 entity name. */
|
||||
private static final Map<String, String> MODERN_TO_LEGACY = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Simple case changes
|
||||
simple("Arrow", "arrow");
|
||||
simple("Bat", "bat");
|
||||
simple("Blaze", "blaze");
|
||||
simple("Boat", "boat");
|
||||
simple("Chicken", "chicken");
|
||||
simple("Cow", "cow");
|
||||
simple("Creeper", "creeper");
|
||||
simple("Enderman", "enderman");
|
||||
simple("Ghast", "ghast");
|
||||
simple("Giant", "giant");
|
||||
simple("Item", "item");
|
||||
simple("Painting", "painting");
|
||||
simple("Pig", "pig");
|
||||
simple("Sheep", "sheep");
|
||||
simple("Silverfish", "silverfish");
|
||||
simple("Skeleton", "skeleton");
|
||||
simple("Slime", "slime");
|
||||
simple("Snowball", "snowball");
|
||||
simple("Spider", "spider");
|
||||
simple("Squid", "squid");
|
||||
simple("Villager", "villager");
|
||||
simple("Witch", "witch");
|
||||
simple("Wolf", "wolf");
|
||||
simple("Zombie", "zombie");
|
||||
|
||||
// Renamed entities
|
||||
simple("CaveSpider", "cave_spider");
|
||||
simple("EnderCrystal", "end_crystal");
|
||||
simple("EnderDragon", "ender_dragon");
|
||||
simple("EntityHorse", "horse");
|
||||
simple("EyeOfEnderSignal", "eye_of_ender");
|
||||
simple("FallingSand", "falling_block");
|
||||
simple("Fireball", "fireball");
|
||||
simple("FireworksRocketEntity", "firework_rocket");
|
||||
simple("ItemFrame", "item_frame");
|
||||
simple("LavaSlime", "magma_cube");
|
||||
simple("LeashKnot", "leash_knot");
|
||||
simple("MushroomCow", "mooshroom");
|
||||
simple("Ozelot", "ocelot");
|
||||
simple("PigZombie", "zombified_piglin");
|
||||
simple("PrimedTnt", "tnt");
|
||||
simple("SmallFireball", "small_fireball");
|
||||
simple("SnowMan", "snow_golem");
|
||||
simple("ThrownEnderpearl", "ender_pearl");
|
||||
simple("ThrownExpBottle", "experience_bottle");
|
||||
simple("ThrownPotion", "potion");
|
||||
simple("VillagerGolem", "iron_golem");
|
||||
simple("WitherBoss", "wither");
|
||||
simple("WitherSkull", "wither_skull");
|
||||
simple("XPOrb", "experience_orb");
|
||||
|
||||
// Minecarts
|
||||
simple("MinecartRideable", "minecart");
|
||||
simple("MinecartChest", "chest_minecart");
|
||||
simple("MinecartCommandBlock", "command_block_minecart");
|
||||
simple("MinecartFurnace", "furnace_minecart");
|
||||
simple("MinecartHopper", "hopper_minecart");
|
||||
simple("MinecartSpawner", "spawner_minecart");
|
||||
simple("MinecartTNT", "tnt_minecart");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the 1.7.10 entity name for a modern entity name, or null if no mapping exists.
|
||||
* The input name should not include the "minecraft:" namespace prefix.
|
||||
*/
|
||||
public static String toLegacy(String modernName) {
|
||||
return MODERN_TO_LEGACY.get(modernName);
|
||||
}
|
||||
|
||||
private static void simple(String legacy, String modern) {
|
||||
MODERN_TO_LEGACY.put(modern, legacy);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package net.coderbot.iris.uniforms;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.coderbot.iris.gl.state.FogMode;
|
||||
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
|
||||
import net.coderbot.iris.gl.uniform.DynamicUniformHolder;
|
||||
import org.joml.Vector3d;
|
||||
import org.joml.Vector4f;
|
||||
@@ -22,6 +23,12 @@ public class IrisInternalUniforms {
|
||||
return GLStateManager.getAlphaState().getReference();
|
||||
}
|
||||
|
||||
private static int getEffectiveAlphaFunc() {
|
||||
if (!GLStateManager.getAlphaTest().isEnabled()) return 7;
|
||||
final int func = GLStateManager.getAlphaState().getFunction();
|
||||
return func & 0x7;
|
||||
}
|
||||
|
||||
public static void addFogUniforms(DynamicUniformHolder uniforms, FogMode fogMode) {
|
||||
uniforms.uniform4f(PER_FRAME, "iris_FogColor", () -> {
|
||||
final Vector3d color = GLStateManager.getFogState().getFogColor();
|
||||
@@ -34,7 +41,8 @@ public class IrisInternalUniforms {
|
||||
.uniform1f(PER_FRAME, "iris_FogDensity", () -> Math.max(0.0F, GLStateManager.getFogState().getDensity()));
|
||||
|
||||
uniforms
|
||||
.uniform1f(PER_FRAME, "iris_currentAlphaTest", IrisInternalUniforms::getEffectiveAlphaRef)
|
||||
.uniform1f(PER_FRAME, "alphaTestRef", IrisInternalUniforms::getEffectiveAlphaRef);
|
||||
.uniform1f("iris_currentAlphaTest", IrisInternalUniforms::getEffectiveAlphaRef, StateUpdateNotifiers.alphaTestNotifier)
|
||||
.uniform1f("alphaTestRef", IrisInternalUniforms::getEffectiveAlphaRef, StateUpdateNotifiers.alphaTestNotifier)
|
||||
.uniform1i("iris_currentAlphaFunc", IrisInternalUniforms::getEffectiveAlphaFunc, StateUpdateNotifiers.alphaFuncNotifier);
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,6 @@ public net.minecraft.block.BlockLeaves field_150129_M
|
||||
#For the interpolated icon class
|
||||
public net.minecraft.client.renderer.texture.TextureAtlasSprite field_110982_k #animationMetadata
|
||||
#For interpolated icon mixin
|
||||
public net.minecraft.client.resources.SimpleResource field_110530_g #mcmetaJson
|
||||
public net.minecraft.client.resources.SimpleResource field_110530_g #mcmetaJson
|
||||
#To prevent mods from un-disabling FOV changes
|
||||
public-f net.minecraftforge.client.event.FOVUpdateEvent fov # fov
|
||||
@@ -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=按层合并粒子绘制调用,减少绘制次数。如果粒子渲染不正确请禁用此项
|
||||
@@ -19,13 +19,16 @@ options.gui_background.tooltip=Background
|
||||
options.downfall_distance=Weather Effects
|
||||
options.downfall_distance.tooltip=Weather Effects
|
||||
options.dynamic_fov=Dynamic FOV
|
||||
options.dynamic_fov.tooltip=If enabled, the game's field of view will change based on the player's speed.
|
||||
options.dynamic_fov.tooltip=Depending on the mode, the game's field of view will change based on the player's speed or from certain mod interactions (including tool zooming/weapon scopes).
|
||||
options.horizon=Horizon
|
||||
options.horizon.tooltip=Horizon
|
||||
options.terrain_fog=Terrain Fog
|
||||
options.terrain_fog.tooltip=Terrain Fog (aka View Distance Fog)
|
||||
options.fog_near_distance=Near Fog Distance
|
||||
options.fog_near_distance.tooltip=Distance away from the player when the view distance fog will start (0%% opacity then linear increase to render distance)
|
||||
options.fov.all=Enabled
|
||||
options.fov.mods=Mods Only
|
||||
options.fov.none=Disabled
|
||||
options.mode_cloud_translucency=Cloud Translucency
|
||||
options.mode_cloud_translucency.tooltip=Cloud Translucency
|
||||
options.mode_clouds=Clouds
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -35,6 +35,13 @@ public class MixinSplashProgressCaching {
|
||||
ImmediateModeRecorder.initSplashTessellator();
|
||||
}
|
||||
|
||||
// VAOs aren't shared across GL contexts; the SharedDrawable the client thread just swapped
|
||||
// onto has none. Core profile (macOS) rejects glValidateProgram without one.
|
||||
@Inject(method = "start", at = @At("RETURN"))
|
||||
private static void angelica$bindSharedDrawableVAO(CallbackInfo ci) {
|
||||
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
|
||||
}
|
||||
|
||||
/**
|
||||
* On return from finish() - destroy splash tessellator and mark splash complete
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.dragonapi;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
|
||||
@Pseudo
|
||||
@Mixin(targets = { "Reika/DragonAPI/IO/Shaders/ShaderRegistry" }, remap = false)
|
||||
public class MixinShaderRegistry_ParseError {
|
||||
|
||||
@Redirect(method = "parseError",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL20;glGetShaderInfoLog(II)Ljava/lang/String;"))
|
||||
private static String angelica$routeInfoLogByType(int id, int maxLen) {
|
||||
return GLStateManager.glIsProgram(id) ? GLStateManager.glGetProgramInfoLog(id, maxLen) : GLStateManager.glGetShaderInfoLog(id, maxLen);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||