Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edc0081b7c | ||
|
|
913256b032 | ||
|
|
e1f5083d3e | ||
|
|
b55e693ad2 | ||
|
|
21c01a1aa4 | ||
|
|
83a26b2291 | ||
|
|
7e44c1efcb | ||
|
|
155f209db5 | ||
|
|
c4a6c165a0 | ||
|
|
624fe6b453 | ||
|
|
fd45fda0ab | ||
|
|
00fc97a611 | ||
|
|
445dc487b9 | ||
|
|
f43fbd4d9f | ||
|
|
dbabc4b3dc | ||
|
|
e01097264b | ||
|
|
0a922f3129 | ||
|
|
4a2dcd0b22 | ||
|
|
0db691db0b | ||
|
|
e5cd1372e7 | ||
|
|
fbd07cc575 | ||
|
|
91f2cf9dfb | ||
|
|
34a6967c71 |
No files matched your search
@@ -1,302 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
---
|
||||
@@ -121,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"))
|
||||
@@ -129,6 +129,8 @@ dependencies {
|
||||
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.3-GTNH:dev")
|
||||
|
||||
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
|
||||
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -9,13 +9,10 @@ import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -186,6 +188,8 @@ public class GLStateManager {
|
||||
|
||||
@Getter private static Vendor VENDOR;
|
||||
|
||||
@Getter private static boolean windows;
|
||||
|
||||
// This setting varies depending on driver, so it gets queried at runtime
|
||||
public static int DEFAULT_DRAW_BUFFER = 0x0405; // GL_BACK
|
||||
|
||||
@@ -516,6 +520,12 @@ public class GLStateManager {
|
||||
// After Display.create(), viewport is (0, 0, width, height)
|
||||
viewportState.setViewPort(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
|
||||
final String glVendor = RENDER_BACKEND.getString(GL11.GL_VENDOR);
|
||||
VENDOR = Vendor.getVendor(glVendor.toLowerCase());
|
||||
|
||||
final String os = System.getProperty("os.name").toLowerCase();
|
||||
windows = os.contains("win");
|
||||
}
|
||||
|
||||
static void init(Runnable initCallback) {
|
||||
@@ -523,9 +533,6 @@ public class GLStateManager {
|
||||
|
||||
DEFAULT_DRAW_BUFFER = RENDER_BACKEND.getInteger(GL11.GL_DRAW_BUFFER);
|
||||
|
||||
final String glVendor = RENDER_BACKEND.getString(GL11.GL_VENDOR);
|
||||
VENDOR = Vendor.getVendor(glVendor.toLowerCase());
|
||||
|
||||
// The initial mask value should be defined as all 1's. However, some drivers have it set to 0's.
|
||||
// To ensure consistency & correctness across all drivers, we're setting them to 0xFF.
|
||||
RENDER_BACKEND.stencilFunc(stencilState.getFuncFront(), stencilState.getRefFront(), 0xFF);
|
||||
@@ -619,16 +626,6 @@ public class GLStateManager {
|
||||
return BYPASS_CACHE || !isCachingEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if we should use DSA for texture operations. DSA relies on cached texture bindings, which aren't tracked when caching is disabled. Also,
|
||||
* proxy textures are special query textures that shouldn't use DSA.
|
||||
*/
|
||||
private static boolean shouldUseDSA(int target) {
|
||||
if (!isCachingEnabled()) return false;
|
||||
// Proxy textures are for capability queries, not real textures
|
||||
return target != GL11.GL_PROXY_TEXTURE_1D && target != GL11.GL_PROXY_TEXTURE_2D && target != GL12.GL_PROXY_TEXTURE_3D;
|
||||
}
|
||||
|
||||
// LWJGL Overrides
|
||||
public static void glEnable(int cap) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
@@ -1636,6 +1633,9 @@ public class GLStateManager {
|
||||
}
|
||||
alphaTest.enable();
|
||||
fragmentGeneration++;
|
||||
if (GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void disableAlphaTest() {
|
||||
@@ -1653,6 +1653,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) {
|
||||
@@ -1672,6 +1675,9 @@ public class GLStateManager {
|
||||
alphaState.setFunction(function);
|
||||
alphaState.setReference(reference);
|
||||
fragmentGeneration++;
|
||||
if (GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
|
||||
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1817,13 +1823,7 @@ public class GLStateManager {
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
suspendPixelUnpackBuffer();
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(getBoundTextureForServerState(), target, level, internalformat, width, height, border, format, type, pixels);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
@@ -1868,11 +1868,7 @@ public class GLStateManager {
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels != null ? pixels.asIntBuffer() : null);
|
||||
suspendPixelUnpackBuffer();
|
||||
if (shouldUseDSA(target)) {
|
||||
RenderSystem.textureImage2D(getBoundTextureForServerState(), target, level, internalformat, width, height, border, format, type, pixels);
|
||||
} else {
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
@@ -2229,15 +2225,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;
|
||||
@@ -4287,6 +4280,7 @@ public class GLStateManager {
|
||||
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
|
||||
GLSMHooks.programChangeEvent.previousProgram = prev;
|
||||
GLSMHooks.programChangeEvent.newProgram = 0;
|
||||
GLSMHooks.programChangeEvent.postBind = false;
|
||||
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
|
||||
}
|
||||
ffp.activate();
|
||||
@@ -4307,6 +4301,7 @@ public class GLStateManager {
|
||||
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
|
||||
GLSMHooks.programChangeEvent.previousProgram = prev;
|
||||
GLSMHooks.programChangeEvent.newProgram = program;
|
||||
GLSMHooks.programChangeEvent.postBind = false;
|
||||
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
|
||||
}
|
||||
if (initConfig != null && initConfig.isLwjglDebug()) {
|
||||
@@ -4315,6 +4310,12 @@ public class GLStateManager {
|
||||
}
|
||||
RENDER_BACKEND.useProgram(program);
|
||||
CompatUniformManager.onUseProgram(program);
|
||||
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
|
||||
GLSMHooks.programChangeEvent.previousProgram = prev;
|
||||
GLSMHooks.programChangeEvent.newProgram = program;
|
||||
GLSMHooks.programChangeEvent.postBind = true;
|
||||
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4391,6 +4392,7 @@ public class GLStateManager {
|
||||
|
||||
public static int glGetError() {
|
||||
if (!RENDER_BACKEND.hasContext()) return 0;
|
||||
if (initConfig.noErrorChecks()) return 0;
|
||||
return RENDER_BACKEND.getError();
|
||||
}
|
||||
|
||||
@@ -4445,13 +4447,7 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
suspendPixelUnpackBuffer();
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture - keeps GL binding state unchanged
|
||||
RenderSystem.textureSubImage2D(getBoundTextureForServerState(), target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
@@ -4464,13 +4460,7 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
suspendPixelUnpackBuffer();
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureSubImage2D(getBoundTextureForServerState(), target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
RENDER_BACKEND.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
@@ -4948,12 +4938,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5070,14 +5062,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;
|
||||
}
|
||||
@@ -5196,7 +5180,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static boolean isFramebufferEnabled() {
|
||||
return initConfig != null && initConfig.isFramebufferSupported() && initConfig.isFboEnabled();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final boolean FFP_WARN_ON_UNSUPPORTED = Boolean.parseBoolean(System.getProperty("angelica.ffp.warnOnUnsupported", "false"));
|
||||
@@ -5661,7 +5645,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);
|
||||
}
|
||||
|
||||
@@ -5721,6 +5708,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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -61,7 +62,13 @@ public class RenderSystem {
|
||||
if (rendererInitialized) return;
|
||||
rendererInitialized = true;
|
||||
try {
|
||||
if (GLStateManager.capabilities.OpenGL45) {
|
||||
if (GLStateManager.vendorIsIntel() && GLStateManager.isWindows()) {
|
||||
dsaState = new DSAUnsupported();
|
||||
GLStateManager.LOGGER.info("Detected Intel drivers on Windows, disabling DSA.");
|
||||
} else if (!GLStateManager.getInitConfig().isDSAEnabled()) {
|
||||
dsaState = new DSAUnsupported();
|
||||
GLStateManager.LOGGER.info("enableDSA is set to false, disabling DSA.");
|
||||
} else if (GLStateManager.capabilities.OpenGL45) {
|
||||
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
|
||||
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
|
||||
}
|
||||
@@ -432,7 +439,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void deleteBuffers(int buffer) {
|
||||
RENDER_BACKEND.deleteBuffers(buffer);
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
public static long getVRAM() {
|
||||
@@ -491,7 +498,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() {}
|
||||
}
|
||||
@@ -8,24 +8,24 @@ import java.util.function.Consumer;
|
||||
public final class GLSMInitConfig {
|
||||
private final boolean lwjglDebug;
|
||||
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
|
||||
private final boolean framebufferSupported;
|
||||
private final boolean fboEnabled;
|
||||
private final Consumer<DirectTessellator> directDrawer;
|
||||
private final Runnable streamingDrawerDestroy;
|
||||
private final int displayWidth;
|
||||
private final int displayHeight;
|
||||
private final Runnable postInitCallback;
|
||||
private final boolean enableDSA;
|
||||
private final boolean noErrorChecks;
|
||||
|
||||
private GLSMInitConfig(Builder builder) {
|
||||
this.lwjglDebug = builder.lwjglDebug;
|
||||
this.streamingUploadStrategy = builder.streamingUploadStrategy;
|
||||
this.framebufferSupported = builder.framebufferSupported;
|
||||
this.fboEnabled = builder.fboEnabled;
|
||||
this.noErrorChecks = builder.noErrorChecks;
|
||||
this.directDrawer = builder.directDrawer;
|
||||
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
|
||||
this.displayWidth = builder.displayWidth;
|
||||
this.displayHeight = builder.displayHeight;
|
||||
this.postInitCallback = builder.postInitCallback;
|
||||
this.enableDSA = builder.enableDSA;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
@@ -34,24 +34,24 @@ public final class GLSMInitConfig {
|
||||
|
||||
public boolean isLwjglDebug() { return lwjglDebug; }
|
||||
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
|
||||
public boolean isFramebufferSupported() { return framebufferSupported; }
|
||||
public boolean isFboEnabled() { return fboEnabled; }
|
||||
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
|
||||
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
|
||||
public int getDisplayWidth() { return displayWidth; }
|
||||
public int getDisplayHeight() { return displayHeight; }
|
||||
public Runnable getPostInitCallback() { return postInitCallback; }
|
||||
public boolean isDSAEnabled() { return enableDSA; }
|
||||
public boolean noErrorChecks() { return noErrorChecks; }
|
||||
|
||||
public static final class Builder {
|
||||
private boolean lwjglDebug = false;
|
||||
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
|
||||
private boolean framebufferSupported = false;
|
||||
private boolean fboEnabled = false;
|
||||
private Consumer<DirectTessellator> directDrawer = null;
|
||||
private Runnable streamingDrawerDestroy = null;
|
||||
private int displayWidth = 0;
|
||||
private int displayHeight = 0;
|
||||
private Runnable postInitCallback = null;
|
||||
private boolean enableDSA = false;
|
||||
private boolean noErrorChecks;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
@@ -65,13 +65,8 @@ public final class GLSMInitConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder framebufferSupported(boolean framebufferSupported) {
|
||||
this.framebufferSupported = framebufferSupported;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fboEnabled(boolean fboEnabled) {
|
||||
this.fboEnabled = fboEnabled;
|
||||
public Builder noErrorChecks(boolean noErrorChecks) {
|
||||
this.noErrorChecks = noErrorChecks;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -96,6 +91,11 @@ public final class GLSMInitConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enableDSA(boolean enableDSA) {
|
||||
this.enableDSA = enableDSA;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GLSMInitConfig build() {
|
||||
return new GLSMInitConfig(this);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -5,4 +5,5 @@ import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
public final class ProgramChangeEvent extends MutableEvent {
|
||||
public int previousProgram;
|
||||
public int newProgram;
|
||||
public boolean postBind;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 187 B |
|
Before Width: | Height: | Size: 310 B |
|
Before Width: | Height: | Size: 192 B |
|
Before Width: | Height: | Size: 316 B |
@@ -24,6 +24,7 @@ public class ModStatus {
|
||||
public static boolean isThaumcraftLoaded;
|
||||
public static boolean isThaumicHorizonsLoaded;
|
||||
public static boolean isBaublesLoaded;
|
||||
public static boolean isCosmeticArmorReworkedLoaded;
|
||||
public static boolean isFluidLoggedLoaded;
|
||||
public static boolean isBOPLoaded;
|
||||
|
||||
@@ -43,6 +44,7 @@ public class ModStatus {
|
||||
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
|
||||
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
|
||||
isBaublesLoaded = Loader.isModLoaded("Baubles");
|
||||
isCosmeticArmorReworkedLoaded = Loader.isModLoaded("cosmeticarmorreworked");
|
||||
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
|
||||
|
||||
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
|
||||
|
||||
@@ -63,6 +63,11 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVAO;
|
||||
|
||||
@Config.Comment("Enables DSA (Direct State Access) for faster bindings. Disable if you notice terrible performance.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableDSA;
|
||||
|
||||
@Config.Comment("Enable NotFine features")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
@@ -297,4 +302,9 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean disableGLVersionPinning;
|
||||
|
||||
@Config.Comment("Disables GL Error checks. Always set to false in dev env or if LWJGL debug is on. Improves performance.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean disableErrorChecks;
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.dynamiclights;
|
||||
|
||||
import baubles.common.lib.PlayerHandler;
|
||||
import lain.mods.cos.CosmeticArmorReworked;
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
@@ -180,6 +181,19 @@ public class DynamicLights {
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all tracked light sources, to call when unloading the client world
|
||||
*/
|
||||
public void removeAllLightSources() {
|
||||
this.lightSourcesLock.writeLock().lock();
|
||||
|
||||
this.dynamicLightSources.clear();
|
||||
this.lightSourceCount = 0;
|
||||
rebuildLightSourceArray();
|
||||
|
||||
this.lightSourcesLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes light sources if the filter matches.
|
||||
*
|
||||
@@ -625,6 +639,18 @@ public class DynamicLights {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ModStatus.isCosmeticArmorReworkedLoaded) {
|
||||
final var cosArmor = CosmeticArmorReworked.invMan.getCosArmorInventoryClient(player.getUniqueID());
|
||||
if (cosArmor != null) {
|
||||
for (int i = 0; i < cosArmor.getSizeInventory(); i++) {
|
||||
final var stack = cosArmor.getStackInSlot(i);
|
||||
if (stack != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(stack));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return luminance;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class HUDCaching {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
if (!AngelicaConfig.hudCachingActive || !GLStateManager.isFramebufferEnabled()) {
|
||||
if (!AngelicaConfig.hudCachingActive) {
|
||||
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,24 +16,27 @@ import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.samplers.IrisSamplers;
|
||||
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 +94,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 +116,7 @@ public class IrisGLSMBridge {
|
||||
if (fogDensityListener != null) fogDensityListener.run();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
GLSMHooks.TEXTURE_BIND.addListener(event -> {
|
||||
if (Iris.enabled) {
|
||||
TextureTracker.INSTANCE.onBindTexture();
|
||||
@@ -138,20 +148,37 @@ public class IrisGLSMBridge {
|
||||
}
|
||||
});
|
||||
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> ProgramUniforms.clearActiveUniforms());
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (event.postBind) return;
|
||||
ProgramUniforms.clearActiveUniforms();
|
||||
});
|
||||
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (event.postBind) return;
|
||||
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return;
|
||||
if (!drp.shouldOverrideShaders()) return;
|
||||
if (drp.getActivePassProgramId() == -1) return;
|
||||
|
||||
if (!ImmediateState.isRenderingLevel || DepthColorStorage.isOwnedProgram(event.newProgram)) {
|
||||
|
||||
if (DepthColorStorage.isOwnedProgram(event.newProgram)) {
|
||||
DepthColorStorage.unlockDepthColor();
|
||||
} else {
|
||||
drp.onModProgramOverride();
|
||||
}
|
||||
});
|
||||
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (!event.postBind) return;
|
||||
WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (pipeline instanceof DeferredWorldRenderingPipeline drp) {
|
||||
DeferredWorldRenderingPipeline.Pass activePass = drp.getActivePassProgram();
|
||||
if (activePass != null && activePass.getProgram() != null && activePass.getProgram().getProgramId() == event.newProgram) {
|
||||
activePass.getProgram().getUniforms().update();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -105,9 +105,6 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
new DependencyVerifier.Check(
|
||||
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
|
||||
"Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new DependencyVerifier.Check(
|
||||
"/com/gtnewhorizon/gtnhlib/client/renderer/PrimitiveExtractor.class",
|
||||
"GTNHLib is outdated: Angelica requires GTNHLib 0.8.21 or newer! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new DependencyVerifier.Check(
|
||||
"/xyz/wagyourtail/jvmdg/exc/MissingStubError.class",
|
||||
"GTNHLib is outdated: Angelica requires GTNHLib 0.9.0 or newer! Download: https://modrinth.com/mod/gtnhlib")
|
||||
|
||||
@@ -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;
|
||||
@@ -105,26 +106,6 @@ public final class ClientProxy extends CommonProxy {
|
||||
blockError = new BlockError();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldLoad(WorldEvent.Load event) {
|
||||
if (GLStateManager.isRunningSplash()) {
|
||||
GLStateManager.setRunningSplash(false);
|
||||
LOGGER.info("World loaded - Enabling GLSM Cache");
|
||||
}
|
||||
|
||||
if (AngelicaConfig.enableCeleritas) {
|
||||
ChunkTaskRegistry.reset();
|
||||
ChunkTaskRegistry.registerProvider(DefaultChunkTaskProvider.INSTANCE);
|
||||
ChunkTaskRegistry.registerProvider(ThreadedChunkTaskProvider.INSTANCE);
|
||||
|
||||
// Register all blocks. Because blockids are unique to a world, this must be done each load
|
||||
GameData.getBlockRegistry().typeSafeIterable().forEach(o -> {
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, true, true);
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, false, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FMLInitializationEvent event) {
|
||||
super.init(event);
|
||||
@@ -169,7 +150,6 @@ public final class ClientProxy extends CommonProxy {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void postInit(FMLPostInitializationEvent event) {
|
||||
super.postInit(event);
|
||||
@@ -189,11 +169,38 @@ public final class ClientProxy extends CommonProxy {
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldLoad(WorldEvent.Load event) {
|
||||
if (!event.world.isRemote) return;
|
||||
if (GLStateManager.isRunningSplash()) {
|
||||
GLStateManager.setRunningSplash(false);
|
||||
LOGGER.info("World loaded - Enabling GLSM Cache");
|
||||
}
|
||||
|
||||
if (AngelicaConfig.enableCeleritas) {
|
||||
ChunkTaskRegistry.reset();
|
||||
ChunkTaskRegistry.registerProvider(DefaultChunkTaskProvider.INSTANCE);
|
||||
ChunkTaskRegistry.registerProvider(ThreadedChunkTaskProvider.INSTANCE);
|
||||
|
||||
// Register all blocks. Because blockids are unique to a world, this must be done each load
|
||||
GameData.getBlockRegistry().typeSafeIterable().forEach(o -> {
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, true, true);
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, false, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onWorldUnload(WorldEvent.Unload event) {
|
||||
if (!event.world.isRemote) return;
|
||||
DynamicLights.get().removeAllLightSources();
|
||||
}
|
||||
|
||||
float lastIntegratedTickTime;
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTick(TickEvent.ServerTickEvent event) {
|
||||
if (FMLCommonHandler.instance().getSide().isClient() && event.phase == TickEvent.Phase.END) {
|
||||
if (event.phase == TickEvent.Phase.END) {
|
||||
final IntegratedServer srv = Minecraft.getMinecraft().getIntegratedServer();
|
||||
if (srv != null) {
|
||||
final long currentTickTime = srv.tickTimeArray[srv.getTickCounter() % 100];
|
||||
@@ -342,10 +349,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -8,7 +8,10 @@ import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.BlockRenderContext;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.ContextAwareChunkVertexEncoder;
|
||||
import com.prupe.mcpatcher.mal.block.RenderBlocksUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
|
||||
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.vertices.ExtendedDataHelper;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -144,13 +147,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);
|
||||
@@ -256,7 +264,13 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
if (isFluid) {
|
||||
contextEncoder.prepareToRenderFluid(blockRenderContext, block, lightValue);
|
||||
} else {
|
||||
contextEncoder.prepareToRenderBlock(blockRenderContext, block, metadata,
|
||||
int effectiveMeta = metadata;
|
||||
if (BlockRenderingSettings.INSTANCE.hasSnowyEntries()
|
||||
&& BlockRenderingSettings.INSTANCE.getSnowyBlocks().contains(block)
|
||||
&& RenderBlocksUtils.isSnowCovered(renderBlocks.blockAccess, x, y, z)) {
|
||||
effectiveMeta |= BlockMaterialMapping.SNOWY_META_BIT;
|
||||
}
|
||||
contextEncoder.prepareToRenderBlock(blockRenderContext, block, effectiveMeta,
|
||||
ExtendedDataHelper.BLOCK_RENDER_TYPE, lightValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
}
|
||||
@@ -180,6 +180,7 @@ public class CTMUtils {
|
||||
local.renderBlockState.setBlock(block, blockAccess, x, y, z);
|
||||
local.renderBlockState.setFace(face);
|
||||
TileOverride lastOverride = local.ijkIterator.go(local.renderBlockState, icon);
|
||||
local.renderBlockState.blockAccess = null;
|
||||
if (lastOverride != null) {
|
||||
return skipDefaultRendering(block) ? RenderBlocksUtils.blankIcon : local.ijkIterator.getIcon();
|
||||
}
|
||||
@@ -233,6 +234,7 @@ public class CTMUtils {
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
clearCurrentCompact();
|
||||
}
|
||||
|
||||
private static boolean checkFace(int face) {
|
||||
|
||||
@@ -76,6 +76,18 @@ public final class CompactCtmQuadProcessor {
|
||||
private static final int[] QUAD_B = {UP, RIGHT, DOWN, LEFT};
|
||||
private static final int[] QUAD_DIAG = {UP_LEFT, UP_RIGHT, DOWN_RIGHT, DOWN_LEFT};
|
||||
|
||||
/** Maps split-face quadrant (0=TL,1=TR,2=BR,3=BL face-local) to RenderBlocks per-face TL/TR/BR/BL orientation. */
|
||||
private static final int[][] RENDER_FACE_QUADRANT_MAP = {
|
||||
{3, 2, 1, 0}, // Y-
|
||||
{2, 3, 0, 1}, // Y+
|
||||
{3, 0, 1, 2}, // Z-
|
||||
{0, 1, 2, 3}, // Z+
|
||||
{3, 0, 1, 2}, // X-
|
||||
{1, 2, 3, 0} // X+
|
||||
};
|
||||
private static final float[] QUADRANT_MIN_U = {0.0f, 0.5f, 0.5f, 0.0f};
|
||||
private static final float[] QUADRANT_MIN_V = {0.0f, 0.0f, 0.5f, 0.5f};
|
||||
|
||||
private final IIcon[] sprites;
|
||||
private final CompactConnectingCtmProperties props;
|
||||
|
||||
@@ -148,30 +160,107 @@ public final class CompactCtmQuadProcessor {
|
||||
}
|
||||
|
||||
private void renderSplitFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, int connections) {
|
||||
Block block = world.getBlock(x, y, z);
|
||||
final 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;
|
||||
final double minX = rb.renderMinX, minY = rb.renderMinY, minZ = rb.renderMinZ;
|
||||
final double maxX = rb.renderMaxX, maxY = rb.renderMaxY, maxZ = rb.renderMaxZ;
|
||||
|
||||
// Without AO the corner color/brightness fields are unused; skip the lighting dance.
|
||||
final boolean ao = rb.enableAO;
|
||||
|
||||
final float rTL, rTR, rBL, rBR, gTL, gTR, gBL, gBR, bTL, bTR, bBL, bBR;
|
||||
final int brTL, brTR, brBL, brBR;
|
||||
if (ao) {
|
||||
rTL = rb.colorRedTopLeft; rTR = rb.colorRedTopRight;
|
||||
rBL = rb.colorRedBottomLeft; rBR = rb.colorRedBottomRight;
|
||||
gTL = rb.colorGreenTopLeft; gTR = rb.colorGreenTopRight;
|
||||
gBL = rb.colorGreenBottomLeft; gBR = rb.colorGreenBottomRight;
|
||||
bTL = rb.colorBlueTopLeft; bTR = rb.colorBlueTopRight;
|
||||
bBL = rb.colorBlueBottomLeft; bBR = rb.colorBlueBottomRight;
|
||||
brTL = rb.brightnessTopLeft; brTR = rb.brightnessTopRight;
|
||||
brBL = rb.brightnessBottomLeft; brBR = rb.brightnessBottomRight;
|
||||
} else {
|
||||
rTL = rTR = rBL = rBR = 0f;
|
||||
gTL = gTR = gBL = gBR = 0f;
|
||||
bTL = bTR = bBL = bBR = 0f;
|
||||
brTL = brTR = brBL = brBR = 0;
|
||||
}
|
||||
|
||||
for (int q = 0; q < 4; q++) {
|
||||
IIcon icon = sprites[getQuadrantSprite(q, connections)];
|
||||
final IIcon icon = sprites[getQuadrantSprite(q, connections)];
|
||||
if (icon == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ao) {
|
||||
applyQuadrantLighting(rb, face, q, rTL, rTR, rBL, rBR, gTL, gTR, gBL, gBR, bTL, bTR, bBL, bBR, brTL, brTR, brBL, brBR);
|
||||
}
|
||||
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
|
||||
rb.overrideBlockTexture = icon;
|
||||
renderFace(rb, block, x, y, z, face, icon);
|
||||
}
|
||||
|
||||
if (ao) {
|
||||
rb.colorRedTopLeft = rTL; rb.colorRedTopRight = rTR;
|
||||
rb.colorRedBottomLeft = rBL; rb.colorRedBottomRight = rBR;
|
||||
rb.colorGreenTopLeft = gTL; rb.colorGreenTopRight = gTR;
|
||||
rb.colorGreenBottomLeft = gBL; rb.colorGreenBottomRight = gBR;
|
||||
rb.colorBlueTopLeft = bTL; rb.colorBlueTopRight = bTR;
|
||||
rb.colorBlueBottomLeft = bBL; rb.colorBlueBottomRight = bBR;
|
||||
rb.brightnessTopLeft = brTL; rb.brightnessTopRight = brTR;
|
||||
rb.brightnessBottomLeft = brBL; rb.brightnessBottomRight = brBR;
|
||||
}
|
||||
rb.overrideBlockTexture = null;
|
||||
rb.setRenderBounds(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
private static void applyQuadrantLighting(RenderBlocks rb, int face, int quadrant, float rTL, float rTR, float rBL, float rBR, float gTL, float gTR, float gBL, float gBR, float bTL, float bTR, float bBL, float bBR, int brTL, int brTR, int brBL, int brBR) {
|
||||
final int rq = RENDER_FACE_QUADRANT_MAP[face][quadrant];
|
||||
final float u0 = QUADRANT_MIN_U[rq], u1 = u0 + 0.5f;
|
||||
final float v0 = QUADRANT_MIN_V[rq], v1 = v0 + 0.5f;
|
||||
|
||||
rb.colorRedTopLeft = bilerp(rTL, rTR, rBL, rBR, u0, v0);
|
||||
rb.colorRedTopRight = bilerp(rTL, rTR, rBL, rBR, u1, v0);
|
||||
rb.colorRedBottomLeft = bilerp(rTL, rTR, rBL, rBR, u0, v1);
|
||||
rb.colorRedBottomRight = bilerp(rTL, rTR, rBL, rBR, u1, v1);
|
||||
|
||||
rb.colorGreenTopLeft = bilerp(gTL, gTR, gBL, gBR, u0, v0);
|
||||
rb.colorGreenTopRight = bilerp(gTL, gTR, gBL, gBR, u1, v0);
|
||||
rb.colorGreenBottomLeft = bilerp(gTL, gTR, gBL, gBR, u0, v1);
|
||||
rb.colorGreenBottomRight = bilerp(gTL, gTR, gBL, gBR, u1, v1);
|
||||
|
||||
rb.colorBlueTopLeft = bilerp(bTL, bTR, bBL, bBR, u0, v0);
|
||||
rb.colorBlueTopRight = bilerp(bTL, bTR, bBL, bBR, u1, v0);
|
||||
rb.colorBlueBottomLeft = bilerp(bTL, bTR, bBL, bBR, u0, v1);
|
||||
rb.colorBlueBottomRight = bilerp(bTL, bTR, bBL, bBR, u1, v1);
|
||||
|
||||
rb.brightnessTopLeft = bilerpLight(brTL, brTR, brBL, brBR, u0, v0);
|
||||
rb.brightnessTopRight = bilerpLight(brTL, brTR, brBL, brBR, u1, v0);
|
||||
rb.brightnessBottomLeft = bilerpLight(brTL, brTR, brBL, brBR, u0, v1);
|
||||
rb.brightnessBottomRight = bilerpLight(brTL, brTR, brBL, brBR, u1, v1);
|
||||
}
|
||||
|
||||
private static float bilerp(float c00, float c10, float c01, float c11, float u, float v) {
|
||||
final float top = c00 + (c10 - c00) * u;
|
||||
final float bot = c01 + (c11 - c01) * u;
|
||||
return top + (bot - top) * v;
|
||||
}
|
||||
|
||||
/** Brightness packs sky in upper 16 bits, block in lower; bilerp each independently then repack. */
|
||||
private static int bilerpLight(int c00, int c10, int c01, int c11, float u, float v) {
|
||||
final int b00 = c00 & 0xFFFF, b10 = c10 & 0xFFFF, b01 = c01 & 0xFFFF, b11 = c11 & 0xFFFF;
|
||||
final int s00 = c00 >>> 16, s10 = c10 >>> 16, s01 = c01 >>> 16, s11 = c11 >>> 16;
|
||||
|
||||
final float bTop = b00 + (b10 - b00) * u;
|
||||
final float bBot = b01 + (b11 - b01) * u;
|
||||
final float sTop = s00 + (s10 - s00) * u;
|
||||
final float sBot = s01 + (s11 - s01) * u;
|
||||
|
||||
final int block = Math.round(bTop + (bBot - bTop) * v);
|
||||
final int sky = Math.round(sTop + (sBot - sTop) * v);
|
||||
return (sky << 16) | (block & 0xFFFF);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -105,7 +105,6 @@ abstract public class TileOverrideIterator implements Iterator<TileOverride> {
|
||||
|
||||
public TileOverride go(RenderBlockState renderBlockState, IIcon origIcon) {
|
||||
CTMUtils.clearCurrentCompact();
|
||||
|
||||
this.renderBlockState = renderBlockState;
|
||||
renderBlockState.setFilter(null);
|
||||
currentIcon = origIcon;
|
||||
|
||||
@@ -157,7 +157,7 @@ public class RenderBlocksUtils {
|
||||
return isSnow ? Blocks.snow_layer.getIcon(blockAccess, x, y, z, face) : topIcon;
|
||||
}
|
||||
|
||||
private static boolean isSnowCovered(IBlockAccess blockAccess, int x, int y, int z) {
|
||||
public static boolean isSnowCovered(IBlockAccess blockAccess, int x, int y, int z) {
|
||||
Block topBlock = blockAccess.getBlock(x, y + 1, z);
|
||||
return topBlock == Blocks.snow_layer || topBlock == Blocks.snow;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,8 +7,11 @@ 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 it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSet;
|
||||
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;
|
||||
@@ -19,37 +22,46 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class BlockMaterialMapping {
|
||||
|
||||
/** Meta-key bit OR'd in at runtime when a snowy-tagged block has snow above it. */
|
||||
public static final int SNOWY_META_BIT = 0x10;
|
||||
|
||||
/**
|
||||
* Creates a two-level map structure for block material IDs.
|
||||
* Based on Iris's BlockState mapping approach adapted for 1.7.10's metadata system.
|
||||
*/
|
||||
public static Reference2ObjectMap<Block, Int2IntMap> createBlockMetaIdMap(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap) {
|
||||
Reference2ObjectMap<Block, Int2IntMap> blockMatches = new Reference2ObjectOpenHashMap<>();
|
||||
public static Reference2ObjectMap<Block, Int2IntMap> createBlockMetaIdMap(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
|
||||
final Reference2ObjectMap<Block, Int2IntMap> blockMatches = new Reference2ObjectOpenHashMap<>();
|
||||
final ReferenceSet<Block> snowyBlocks = new ReferenceOpenHashSet<>();
|
||||
|
||||
blockPropertiesMap.forEach((intId, entries) -> {
|
||||
for (BlockEntry entry : entries) {
|
||||
addBlockMetas(entry, blockMatches, intId);
|
||||
addBlockMetas(entry, blockMatches, intId, skipFlattening, snowyBlocks);
|
||||
}
|
||||
});
|
||||
|
||||
BlockRenderingSettings.INSTANCE.setHasSnowyEntries(!snowyBlocks.isEmpty());
|
||||
BlockRenderingSettings.INSTANCE.setSnowyBlocks(snowyBlocks);
|
||||
return blockMatches;
|
||||
}
|
||||
|
||||
public static Map<Block, BlockRenderLayer> createBlockTypeMap(Map<NamespacedId, BlockRenderType> blockPropertiesMap) {
|
||||
Map<Block, BlockRenderLayer> blockTypeIds = new Reference2ReferenceOpenHashMap<>();
|
||||
final 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 = resolveBlockOrNull(id);
|
||||
|
||||
if (block == null || block == Blocks.air) {
|
||||
return;
|
||||
// Modern names like "grass_block" don't exist in 1.7.10's registry; fall back to flattening.
|
||||
if (block == null && "minecraft".equals(id.getNamespace())) {
|
||||
final List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(id.getName(), Map.of());
|
||||
if (legacyEntries != null) {
|
||||
block = resolveBlockOrNull(legacyEntries.getFirst().getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (block == null) return;
|
||||
final BlockRenderLayer layer = convertBlockToRenderLayer(blockType);
|
||||
if (layer != null) {
|
||||
blockTypeIds.put(block, layer);
|
||||
}
|
||||
if (layer != null) blockTypeIds.put(block, layer);
|
||||
});
|
||||
|
||||
return blockTypeIds;
|
||||
@@ -72,20 +84,29 @@ public class BlockMaterialMapping {
|
||||
* Adds block+metadata combinations to the material ID map.
|
||||
* 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) {
|
||||
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
|
||||
final NamespacedId id = entry.getId();
|
||||
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
|
||||
final Map<String, String> stateProps = entry.getStateProperties();
|
||||
final String snowy = stateProps.get("snowy");
|
||||
final int snowyBit = "true".equals(snowy) ? SNOWY_META_BIT : 0;
|
||||
|
||||
final Block block = (Block) Block.blockRegistry.getObject(resourceLocation.toString());
|
||||
|
||||
// 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
|
||||
if (block == null || block == Blocks.air) {
|
||||
return;
|
||||
// Vanilla modern names go through FlatteningMap; legacy-section packs and modded blocks
|
||||
// resolve directly from the registry.
|
||||
List<BlockEntry> targets = null;
|
||||
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.getMetas().isEmpty())) {
|
||||
targets = FlatteningMap.toLegacy(id.getName(), stateProps);
|
||||
}
|
||||
if (targets == null) targets = List.of(entry);
|
||||
|
||||
Set<Integer> metas = entry.getMetas();
|
||||
for (BlockEntry target : targets) {
|
||||
final Block block = resolveBlockOrNull(target.getId());
|
||||
if (block == null) continue;
|
||||
applyMetas(block, target.getMetas(), idMap, intId, snowyBit);
|
||||
if (snowy != null) snowyBlocks.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyMetas(Block block, Set<Integer> metas, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, int snowyBit) {
|
||||
Int2IntMap metaMap = idMap.get(block);
|
||||
if (metaMap == null) {
|
||||
metaMap = new Int2IntOpenHashMap();
|
||||
@@ -94,15 +115,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);
|
||||
}
|
||||
for (int meta = 0; meta < 16; meta++) metaMap.putIfAbsent(meta | snowyBit, intId);
|
||||
} else {
|
||||
// Add only specific metadata values
|
||||
for (int meta : metas) {
|
||||
metaMap.putIfAbsent(meta, intId);
|
||||
}
|
||||
for (int meta : metas) metaMap.putIfAbsent(meta | snowyBit, intId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered Block for an id, or null if unknown.
|
||||
* The registry returns Blocks.air (its default) for missing keys, which we coerce to null.
|
||||
*/
|
||||
private static Block resolveBlockOrNull(NamespacedId id) {
|
||||
final Block block = (Block) Block.blockRegistry.getObject(new ResourceLocation(id.getNamespace(), id.getName()).toString());
|
||||
return block == Blocks.air ? null : block;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSet;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSets;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -19,12 +22,21 @@ public class BlockRenderingSettings {
|
||||
private boolean reloadRequired;
|
||||
private Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches;
|
||||
private Map<Block, BlockRenderLayer> blockTypeIds;
|
||||
private Object2IntFunction<NamespacedId> entityIds;
|
||||
private Object2IntFunction<NamespacedId> itemIds;
|
||||
private float ambientOcclusionLevel;
|
||||
// note: no reload needed, entities are rebuilt every frame.
|
||||
@Setter
|
||||
private Object2IntFunction<NamespacedId> entityIds;
|
||||
// note: no reload needed, items are rendered every frame.
|
||||
@Setter
|
||||
private Object2IntFunction<NamespacedId> itemIds;
|
||||
@Getter
|
||||
private float ambientOcclusionLevel;
|
||||
private boolean disableDirectionalShading;
|
||||
private boolean useSeparateAo;
|
||||
private boolean useExtendedVertexFormat;
|
||||
@Setter
|
||||
private boolean hasSnowyEntries;
|
||||
@Getter
|
||||
private ReferenceSet<Block> snowyBlocks = ReferenceSets.emptySet();
|
||||
|
||||
public BlockRenderingSettings() {
|
||||
reloadRequired = false;
|
||||
@@ -34,6 +46,7 @@ public class BlockRenderingSettings {
|
||||
disableDirectionalShading = false;
|
||||
useSeparateAo = false;
|
||||
useExtendedVertexFormat = false;
|
||||
hasSnowyEntries = false;
|
||||
}
|
||||
|
||||
public void clearReloadRequired() {
|
||||
@@ -74,6 +87,14 @@ public class BlockRenderingSettings {
|
||||
this.blockMetaMatches = blockMetaIds;
|
||||
}
|
||||
|
||||
public boolean hasSnowyEntries() {
|
||||
return hasSnowyEntries;
|
||||
}
|
||||
|
||||
public void setSnowyBlocks(ReferenceSet<Block> snowyBlocks) {
|
||||
this.snowyBlocks = snowyBlocks != null ? snowyBlocks : ReferenceSets.emptySet();
|
||||
}
|
||||
|
||||
public void setBlockTypeIds(Map<Block, BlockRenderLayer> blockTypeIds) {
|
||||
if (this.blockTypeIds != null && this.blockTypeIds.equals(blockTypeIds)) {
|
||||
return;
|
||||
@@ -83,21 +104,7 @@ public class BlockRenderingSettings {
|
||||
this.blockTypeIds = blockTypeIds;
|
||||
}
|
||||
|
||||
public void setEntityIds(Object2IntFunction<NamespacedId> entityIds) {
|
||||
// note: no reload needed, entities are rebuilt every frame.
|
||||
this.entityIds = entityIds;
|
||||
}
|
||||
|
||||
public void setItemIds(Object2IntFunction<NamespacedId> itemIds) {
|
||||
// note: no reload needed, items are rendered every frame.
|
||||
this.itemIds = itemIds;
|
||||
}
|
||||
|
||||
public float getAmbientOcclusionLevel() {
|
||||
return ambientOcclusionLevel;
|
||||
}
|
||||
|
||||
public void setAmbientOcclusionLevel(float ambientOcclusionLevel) {
|
||||
public void setAmbientOcclusionLevel(float ambientOcclusionLevel) {
|
||||
if (ambientOcclusionLevel == this.ambientOcclusionLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package net.coderbot.iris.gl.program;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.gl.GlResource;
|
||||
import net.coderbot.iris.gl.blending.DepthColorStorage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
public final class Program extends GlResource {
|
||||
|
||||
@Getter
|
||||
private final ProgramUniforms uniforms;
|
||||
|
||||
@Getter
|
||||
private final ProgramSamplers samplers;
|
||||
|
||||
@Getter
|
||||
private final ProgramImages images;
|
||||
|
||||
Program(int program, ProgramUniforms uniforms, ProgramSamplers samplers, ProgramImages images) {
|
||||
@@ -18,15 +25,15 @@ public final class Program extends GlResource {
|
||||
|
||||
DepthColorStorage.registerOwnedProgram(program);
|
||||
}
|
||||
|
||||
|
||||
public void use() {
|
||||
GLStateManager.glUseProgram(getGlId());
|
||||
|
||||
|
||||
uniforms.update();
|
||||
samplers.update();
|
||||
images.update();
|
||||
}
|
||||
|
||||
|
||||
public static void unbind() {
|
||||
ProgramUniforms.clearActiveUniforms();
|
||||
ProgramSamplers.clearActiveSamplers();
|
||||
@@ -38,12 +45,7 @@ public final class Program extends GlResource {
|
||||
DepthColorStorage.unregisterOwnedProgram(getGlId());
|
||||
GLStateManager.glDeleteProgram(getGlId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the OpenGL ID of this program.
|
||||
* @deprecated this should be encapsulated eventually
|
||||
*/
|
||||
@Deprecated
|
||||
|
||||
public int getProgramId() {
|
||||
return getGlId();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -181,9 +181,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private final boolean allowConcurrentCompute;
|
||||
private final OptionalInt forcedShadowRenderDistanceChunks;
|
||||
private final CloudSetting dhCloudSetting;
|
||||
|
||||
private Pass current = null;
|
||||
|
||||
private WorldRenderingPhase overridePhase = null;
|
||||
private WorldRenderingPhase phase = WorldRenderingPhase.NONE;
|
||||
private boolean isBeforeTranslucent;
|
||||
@@ -279,7 +277,9 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
holder -> CommonUniforms.addNonDynamicUniforms(holder, programs.getPack().getIdMap(), programs.getPackDirectives(), this.updateNotifier)
|
||||
);
|
||||
|
||||
BlockRenderingSettings.INSTANCE.setBlockMetaMatches(BlockMaterialMapping.createBlockMetaIdMap(programs.getPack().getIdMap().getBlockProperties()));
|
||||
BlockRenderingSettings.INSTANCE.setBlockMetaMatches(BlockMaterialMapping.createBlockMetaIdMap(
|
||||
programs.getPack().getIdMap().getBlockProperties(),
|
||||
programs.getPack().getIdMap().hasLegacySection()));
|
||||
BlockRenderingSettings.INSTANCE.setBlockTypeIds(BlockMaterialMapping.createBlockTypeMap(programs.getPack().getIdMap().getBlockRenderTypeMap()));
|
||||
|
||||
BlockRenderingSettings.INSTANCE.setEntityIds(programs.getPack().getIdMap().getEntityIdMap());
|
||||
@@ -767,7 +767,11 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
public boolean shouldOverrideShaders() {
|
||||
return isRenderingWorld && !isRenderingFullScreenPass && !isPostChain && isMainBound;
|
||||
}
|
||||
|
||||
|
||||
public Pass getActivePassProgram() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public int getActivePassProgramId() {
|
||||
if (current == null) return -1;
|
||||
final Program p = current.getProgram();
|
||||
@@ -1048,7 +1052,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return shadowRenderTargets != null;
|
||||
}
|
||||
|
||||
private final class Pass {
|
||||
public final class Pass {
|
||||
@Nullable
|
||||
private final Program program;
|
||||
private final GlFramebuffer framebufferBeforeTranslucents;
|
||||
|
||||
@@ -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;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,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;
|
||||
@@ -33,6 +35,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A utility class for parsing entries in item.properties, block.properties, and entities.properties files in shaderpacks
|
||||
@@ -56,16 +59,27 @@ 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;
|
||||
|
||||
/** True when block.properties contained a preprocessor directive guarding a 1.7.10-specific section. */
|
||||
private final boolean hasLegacySection;
|
||||
|
||||
public boolean hasLegacySection() {
|
||||
return hasLegacySection;
|
||||
}
|
||||
|
||||
private static final Pattern LEGACY_DIRECTIVE_PATTERN = Pattern.compile(
|
||||
"(?m)^\\s*#\\s*(?:if|elif|ifdef|ifndef)\\b[^\\n]*\\bMC_VERSION\\b[^\\n]*\\b10710\\b");
|
||||
|
||||
IdMap(Path shaderPath, ShaderPackOptions shaderPackOptions, Iterable<StringPair> environmentDefines) {
|
||||
// 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");
|
||||
this.hasLegacySection = rawBlockProperties != null
|
||||
&& LEGACY_DIRECTIVE_PATTERN.matcher(rawBlockProperties).find();
|
||||
|
||||
Iterable<StringPair> resolvedDefines;
|
||||
if (hasLegacySection) {
|
||||
if (this.hasLegacySection) {
|
||||
// Pack has a 1.7.10 section
|
||||
resolvedDefines = environmentDefines;
|
||||
loadProperties(shaderPath, "block.properties", shaderPackOptions, environmentDefines).ifPresent(blockProperties -> {
|
||||
@@ -89,7 +103,6 @@ public class IdMap {
|
||||
loadProperties(shaderPath, "block.properties", shaderPackOptions, modernDefines).ifPresent(blockProperties -> {
|
||||
blockPropertiesMap = parseBlockMap(blockProperties, "block.", "block.properties");
|
||||
blockRenderTypeMap = parseRenderTypeMap(blockProperties, "layer.", "block.properties");
|
||||
blockPropertiesMap = LegacyIdMap.convertModernBlockEntries(blockPropertiesMap);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,7 +154,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");
|
||||
|
||||
@@ -158,7 +171,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +240,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);
|
||||
}
|
||||
@@ -230,7 +260,7 @@ public class IdMap {
|
||||
}
|
||||
|
||||
// Add final token
|
||||
if (current.length() > 0) {
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
}
|
||||
|
||||
@@ -382,11 +412,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;
|
||||
|
||||
@@ -8,5 +8,5 @@ public class IrisLimits {
|
||||
* It's not recommended to raise this higher than 16 until code for avoiding allocation of unused color textures
|
||||
* is implemented.
|
||||
*/
|
||||
public static final int MAX_COLOR_BUFFERS = 16;
|
||||
public static final int MAX_COLOR_BUFFERS = 32;
|
||||
}
|
||||
@@ -1,131 +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;
|
||||
import java.util.Map;
|
||||
|
||||
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");
|
||||
|
||||
private static final Map<String, String> MODERN_TO_LEGACY_BLOCK_IDS = Map.of(
|
||||
"grass", "tallgrass",
|
||||
"grass_block", "grass"
|
||||
);
|
||||
|
||||
/**
|
||||
* 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"));
|
||||
|
||||
add(blockIdMap, 50, block("torch"));
|
||||
add(blockIdMap, 89, block("glowstone"));
|
||||
|
||||
// TODO: what about inactive redstone lamps?
|
||||
add(blockIdMap, 124, block("redstone_lamp"));
|
||||
|
||||
add(blockIdMap, 12, block("sand"));
|
||||
add(blockIdMap, 24, block("sandstone"));
|
||||
|
||||
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"));
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
public static Int2ObjectMap<List<BlockEntry>> convertModernBlockEntries(Int2ObjectMap<List<BlockEntry>> blockIdMap) {
|
||||
Int2ObjectMap<List<BlockEntry>> converted = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
blockIdMap.forEach((intId, entries) -> {
|
||||
List<BlockEntry> convertedEntries = new ArrayList<>(entries.size());
|
||||
for (BlockEntry entry : entries) {
|
||||
NamespacedId id = entry.getId();
|
||||
if (!"minecraft".equals(id.getNamespace())) {
|
||||
convertedEntries.add(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
String convertedName = MODERN_TO_LEGACY_BLOCK_IDS.get(id.getName());
|
||||
if (convertedName == null) {
|
||||
convertedEntries.add(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
convertedEntries.add(new BlockEntry(new NamespacedId(id.getNamespace(), convertedName), entry.getMetas()));
|
||||
for (String name : (Iterable<String>) GameData.getBlockRegistry().getKeys()) {
|
||||
// Only include vanilla minecraft blocks
|
||||
if (!name.startsWith("minecraft:")) {
|
||||
continue;
|
||||
}
|
||||
converted.put(intId, Collections.unmodifiableList(convertedEntries));
|
||||
});
|
||||
|
||||
return converted;
|
||||
}
|
||||
final Block block = (Block) GameData.getBlockRegistry().getObject(name);
|
||||
if (block == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
private static BlockEntry block(String name) {
|
||||
return new BlockEntry(new NamespacedId("minecraft", name), Collections.emptySet());
|
||||
}
|
||||
final int id = GameData.getBlockRegistry().getIDForObject(block);
|
||||
if (id <= 0 || id > 255) {
|
||||
continue;
|
||||
}
|
||||
|
||||
private static void addMany(Int2ObjectMap<List<BlockEntry>> blockIdMap, int id, List<String> prefixes, Function<String, BlockEntry> toId) {
|
||||
List<BlockEntry> entries = new ArrayList<>();
|
||||
// Apply signed byte overflow
|
||||
final int overflowId = (byte) id;
|
||||
|
||||
for (String prefix : prefixes) {
|
||||
entries.add(toId.apply(prefix));
|
||||
final String blockName = name.substring("minecraft:".length());
|
||||
final BlockEntry entry = new BlockEntry(new NamespacedId("minecraft", blockName), Collections.emptySet());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -128,9 +128,9 @@ public class IrisExclusiveUniforms {
|
||||
|
||||
public static class WorldInfoUniforms {
|
||||
public static void addWorldInfoUniforms(UniformHolder uniforms) {
|
||||
final WorldClient level = Minecraft.getMinecraft().theWorld;
|
||||
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "bedrockLevel", () -> 0);
|
||||
uniforms.uniform1f(UniformUpdateFrequency.PER_FRAME, "cloudHeight", () -> {
|
||||
final WorldClient level = Minecraft.getMinecraft().theWorld;
|
||||
if (level != null && level.provider != null) {
|
||||
return level.provider.getCloudHeight();
|
||||
} else {
|
||||
@@ -138,6 +138,7 @@ public class IrisExclusiveUniforms {
|
||||
}
|
||||
});
|
||||
uniforms.uniform1i(UniformUpdateFrequency.PER_FRAME, "heightLimit", () -> {
|
||||
final WorldClient level = Minecraft.getMinecraft().theWorld;
|
||||
if (level != null && level.provider != null) {
|
||||
return level.provider.getHeight();
|
||||
} else {
|
||||
@@ -145,6 +146,7 @@ public class IrisExclusiveUniforms {
|
||||
}
|
||||
});
|
||||
uniforms.uniform1b(UniformUpdateFrequency.PER_FRAME, "hasCeiling", () -> {
|
||||
final WorldClient level = Minecraft.getMinecraft().theWorld;
|
||||
if (level != null && level.provider != null) {
|
||||
return level.provider.hasNoSky;
|
||||
} else {
|
||||
@@ -152,6 +154,7 @@ public class IrisExclusiveUniforms {
|
||||
}
|
||||
});
|
||||
uniforms.uniform1b(UniformUpdateFrequency.PER_FRAME, "hasSkylight", () -> {
|
||||
final WorldClient level = Minecraft.getMinecraft().theWorld;
|
||||
if (level != null && level.provider != null) {
|
||||
return !level.provider.hasNoSky;
|
||||
} else {
|
||||
@@ -159,6 +162,7 @@ public class IrisExclusiveUniforms {
|
||||
}
|
||||
});
|
||||
uniforms.uniform1f(UniformUpdateFrequency.PER_FRAME, "ambientLight", () -> {
|
||||
final WorldClient level = Minecraft.getMinecraft().theWorld;
|
||||
if (level != null && level.provider != null) {
|
||||
return level.provider.lightBrightnessTable[0];
|
||||
} else {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package net.coderbot.iris.vertices;
|
||||
|
||||
/**
|
||||
* Some annoying global state needed for the extended vertex format disabling optimization.
|
||||
*/
|
||||
public class ImmediateState {
|
||||
public static boolean isRenderingLevel = false;
|
||||
public static boolean renderWithExtendedVertexFormat = true;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -42,8 +42,6 @@ public abstract class MixinEntity implements IDynamicLightSource {
|
||||
@Unique
|
||||
private int angelica$lastLuminance = 0;
|
||||
@Unique
|
||||
private static long angelica$lastUpdate = 0;
|
||||
@Unique
|
||||
private double angelica$prevX;
|
||||
@Unique
|
||||
private double angelica$prevY;
|
||||
@@ -54,7 +52,6 @@ public abstract class MixinEntity implements IDynamicLightSource {
|
||||
@Unique
|
||||
private LongOpenHashSet angelica$prevTrackedLitChunkPos = null;
|
||||
|
||||
|
||||
@Override
|
||||
public double angelica$getDynamicLightX() {
|
||||
return posX;
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldSettings;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -16,14 +15,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
@Mixin(World.class)
|
||||
public abstract class MixinWorld {
|
||||
|
||||
@Shadow
|
||||
protected abstract void initialize(WorldSettings p_72963_1_);
|
||||
@Shadow
|
||||
public abstract Block getBlock(int p_147439_1_, int p_147439_2_, int p_147439_3_);
|
||||
|
||||
@Inject(method = "onEntityRemoved", at = @At("HEAD"))
|
||||
private void angelica$removeEntity(Entity entity, CallbackInfo ci){
|
||||
if (entity instanceof IDynamicLightSource lightSource){
|
||||
private void angelica$removeEntity(Entity entity, CallbackInfo ci) {
|
||||
if (entity.worldObj.isRemote && entity instanceof IDynamicLightSource lightSource) {
|
||||
lightSource.angelica$setDynamicLightEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.startup;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.core.GTNHLibCore;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.client.rendering.TextureTracker;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.compat.DriverCompatabilityCheck;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
@@ -24,13 +26,14 @@ public class MixinInitGLStateManager {
|
||||
@Inject(method = "initializeTextures", at = @At("RETURN"))
|
||||
private static void angelica$initializeGLStateManager(CallbackInfo ci) {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
mc.gameSettings.fboEnable = true; // Angelica & many other GTNH features require FBO's
|
||||
GLStateManager.setDrawableGL(Display.getDrawable());
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(mc.displayWidth, mc.displayHeight)
|
||||
.lwjglDebug(AngelicaMod.lwjglDebug)
|
||||
.streamingUploadStrategy(ClientProxy.options().advanced.streamingUploadStrategy)
|
||||
.framebufferSupported(OpenGlHelper.framebufferSupported)
|
||||
.fboEnabled(mc.gameSettings.fboEnable)
|
||||
.noErrorChecks(AngelicaConfig.disableErrorChecks && GTNHLibCore.isObf() && !AngelicaMod.lwjglDebug)
|
||||
.enableDSA(AngelicaConfig.enableDSA)
|
||||
.directDrawer(TessellatorStreamingDrawer::drawDirect)
|
||||
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
|
||||
.postInitCallback(SelectionBoxRenderer::init)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -55,11 +55,13 @@ public abstract class MixinRenderBlocks {
|
||||
|
||||
@Inject(method = "renderBlockByRenderType", at = @At("HEAD"))
|
||||
private void renderingByTypeEnable(CallbackInfoReturnable<Boolean> ci) {
|
||||
CTMUtils.clearCurrentCompact();
|
||||
this.isRenderingByType = true;
|
||||
}
|
||||
|
||||
@Inject(method = "renderBlockByRenderType", at = @At("TAIL"))
|
||||
private void renderingByTypeDisable(CallbackInfoReturnable<Boolean> ci) {
|
||||
CTMUtils.clearCurrentCompact();
|
||||
this.isRenderingByType = false;
|
||||
}
|
||||
|
||||
@@ -203,16 +205,15 @@ public abstract class MixinRenderBlocks {
|
||||
return;
|
||||
}
|
||||
|
||||
CompactCtmQuadProcessor processor = getCompactProcessor();
|
||||
CTMUtils.clearCurrentCompact();
|
||||
if (this.blockAccess == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompactCtmQuadProcessor processor = getCompactProcessor();
|
||||
if (processor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
compactCtmRendering = true;
|
||||
try {
|
||||
boolean handled = processor.processFace(
|
||||
(RenderBlocks)(Object)this,
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.minecraft.init.Blocks;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.world.World;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
@@ -23,7 +24,7 @@ public abstract class MixinTileEntity implements ITileEntityBoundingBoxCache {
|
||||
@Shadow public int yCoord;
|
||||
@Shadow public int zCoord;
|
||||
|
||||
@Shadow(remap = false) public static final AxisAlignedBB INFINITE_EXTENT_AABB = null;
|
||||
@Shadow(remap = false) @Final public static AxisAlignedBB INFINITE_EXTENT_AABB;
|
||||
|
||||
/**
|
||||
* @author mitchej123
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import com.prupe.mcpatcher.ctm.CTMUtils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
@@ -17,6 +18,7 @@ public class MixinTileEntityRendererDispatcher {
|
||||
|
||||
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("HEAD"))
|
||||
private void iris$setBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
|
||||
CTMUtils.clearCurrentCompact();
|
||||
if (te == null) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
|
||||
return;
|
||||
@@ -46,6 +48,7 @@ public class MixinTileEntityRendererDispatcher {
|
||||
|
||||
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"))
|
||||
private void iris$resetBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
|
||||
CTMUtils.clearCurrentCompact();
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.gtnewhorizons.angelica.mixins.late.client.ntmSpace;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.utils.WorkaroundUtils;
|
||||
import com.hbm.dim.CelestialBody;
|
||||
import com.hbm.dim.SkyProviderCelestial;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import jss.notfine.core.Settings;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPhase;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Ported with additional fixes from the ShaderFixer mod
|
||||
* @author kotmatross28729
|
||||
*/
|
||||
@Mixin(value = SkyProviderCelestial.class, priority = 100)
|
||||
public class MixinSkyProviderCelestial {
|
||||
|
||||
/**
|
||||
* Avoid program rebinding due to pipeline.setInputs
|
||||
*/
|
||||
@WrapWithCondition(method = {
|
||||
"renderSunset",
|
||||
"renderCelestials",
|
||||
"renderAtmosphereGlow",
|
||||
}, at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
|
||||
private boolean iris$common$redirectTex2D(int cap) {
|
||||
return this.iris$common$redirectTexture(cap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid program rebinding due to pipeline.setInputs | Doesn't affect texture disabling before glSkyList
|
||||
*/
|
||||
@WrapWithCondition(method = "render",
|
||||
slice = @Slice(from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V", ordinal = 1)),
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
|
||||
private boolean iris$common$redirectTex2DException(int cap) {
|
||||
return this.iris$common$redirectTexture(cap);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean iris$common$redirectTexture(int cap) {
|
||||
if (cap == GL11.GL_TEXTURE_2D) {
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force skybox to render with the default program | Fix various issues with shaders enabled
|
||||
*/
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderHelper;disableStandardItemLighting()V"), remap = false)
|
||||
public void iris$main$renderInDefaultProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
|
||||
main$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "render",
|
||||
at = @At(value = "INVOKE", target = "Lcom/hbm/dim/SkyProviderCelestial;renderSun(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;Lcom/hbm/dim/CelestialBody;DDFF)V"), remap = false)
|
||||
private void iris$main$renderSunInShaderProgram(SkyProviderCelestial instance, float partialTicks, WorldClient world, Minecraft mc, CelestialBody sun, double sunSize, double coronaSize, float visibility, float pressure, Operation<Void> original, @Share("main$previousProgram") LocalIntRef main$previousProgram){
|
||||
GLStateManager.glUseProgram(main$previousProgram.get());
|
||||
original.call(instance, partialTicks, world, mc, sun, sunSize, coronaSize, visibility, pressure);
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "TAIL"), remap = false)
|
||||
public void iris$main$restorePreviousProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
|
||||
GLStateManager.glUseProgram(main$previousProgram.get());
|
||||
}
|
||||
|
||||
@Inject(method = "renderSun", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"),
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glColor4f(FFFF)V", ordinal = 0),
|
||||
to = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glColor4f(FFFF)V", ordinal = 1)
|
||||
)
|
||||
)
|
||||
private void iris$sun$disableBlanking(CallbackInfo ci) {
|
||||
Tessellator.instance.vertexCount = 0;
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"),
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 1),
|
||||
to = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 2)
|
||||
)
|
||||
)
|
||||
private void iris$main$disableVoid(CallbackInfo ci) {
|
||||
Tessellator.instance.vertexCount = 0;
|
||||
}
|
||||
|
||||
@WrapWithCondition(method = "render", at = {
|
||||
@At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 1),
|
||||
@At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 2)
|
||||
}, remap = false)
|
||||
private boolean angelica$renderHorizon(int i) {
|
||||
return (boolean) Settings.HORIZON.option.getStore();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;instance:Lnet/minecraft/client/renderer/Tessellator;", opcode = Opcodes.GETSTATIC))
|
||||
private void iris$renderSky$beginNormalSky(CallbackInfo ci) {
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.SKY));
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glRotatef(FFFF)V", ordinal = 2), remap = false)
|
||||
private void iris$renderSky$tiltSun(CallbackInfo ci) {
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(
|
||||
p -> GLStateManager.glRotatef(p.getSunPathRotation(), 0.0F, 0.0F, 1.0F)
|
||||
);
|
||||
}
|
||||
|
||||
@Inject(method = "renderSun", at = @At(value = "HEAD"), remap = false)
|
||||
private void iris$setSunRenderStage(CallbackInfo ci) {
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.SUN));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.late.client.ntmSpace;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.hbm.dim.SkyProviderCelestial;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPhase;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyArg;
|
||||
import org.spongepowered.asm.mixin.injection.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Ported with additional fixes from the ShaderFixer mod
|
||||
* @author kotmatross28729
|
||||
*/
|
||||
@Mixin(value = SkyProviderCelestial.class, priority = 100)
|
||||
public class MixinSkyProviderCelestial_ShaderCompat {
|
||||
|
||||
/// Simple white 2x2 texture
|
||||
@Unique
|
||||
private static ResourceLocation GL_TEXTURE_2D_WORKAROUND = new ResourceLocation("angelica:textures/shaders_workaround.png");
|
||||
|
||||
// Fixes
|
||||
|
||||
/**
|
||||
* Disables {@link GL11#glDisable(int GL_TEXTURE_2D)} call
|
||||
* @see #iris$atmosphereBlend$renderInDefaultProgram(CallbackInfo, Minecraft, LocalIntRef)
|
||||
*/
|
||||
@WrapWithCondition(method = "renderSunset", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V", ordinal = 0), remap = false)
|
||||
private boolean iris$sunset$revertTextureDisable(int i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables {@link GL11#glEnable(int GL_TEXTURE_2D)} call
|
||||
* @see #iris$atmosphereBlend$renderInDefaultProgram(CallbackInfo, Minecraft, LocalIntRef)
|
||||
*/
|
||||
@WrapWithCondition(method = "renderSunset", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glEnable(I)V", ordinal = 0), remap = false)
|
||||
private boolean iris$sunsetBlend$revertTextureEnable(int i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces sunset to render with the default program
|
||||
* <p>
|
||||
* Fixes sunset not rendering at all with shaders enabled
|
||||
*/
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/hbm/dim/SkyProviderCelestial;renderSunset(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;FFLnet/minecraft/util/ResourceLocation;)V"), remap = false)
|
||||
public void iris$sunset$renderInDefaultProgram(CallbackInfo ci, @Local(argsOnly = true) Minecraft mc, @Share("sunset$previousProgram") LocalIntRef sunset$previousProgram) {
|
||||
sunset$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
mc.getTextureManager().bindTexture(GL_TEXTURE_2D_WORKAROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the program back before {@link #iris$sunset$renderInDefaultProgram(CallbackInfo, Minecraft, LocalIntRef)} call
|
||||
*/
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/hbm/dim/SkyProviderCelestial;renderSunset(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;FFLnet/minecraft/util/ResourceLocation;)V", shift = At.Shift.AFTER), remap = false)
|
||||
public void iris$sunset$restorePreviousProgram(CallbackInfo ci, @Share("sunset$previousProgram") LocalIntRef sunset$previousProgram) {
|
||||
GLStateManager.glUseProgram(sunset$previousProgram.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables sun blanking with shaders enabled (shaders ignore it anyway)
|
||||
* <p>
|
||||
* Fixes the huge white square near the sun with shaders enabled
|
||||
*/
|
||||
@Inject(method = "renderSun", at = @At(value="INVOKE", target="Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 1))
|
||||
private void iris$sun$disableBlanking(CallbackInfo ci) {
|
||||
if(IrisApi.getInstance().isShaderPackInUse())
|
||||
Tessellator.instance.vertexCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the sun a little closer to the camera
|
||||
* <p>
|
||||
* Fixes z-fighting between the sun and the shader skybox
|
||||
*/
|
||||
@ModifyArg(method = "renderSun",
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;addVertexWithUV(DDDDD)V", ordinal = 8),
|
||||
to = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 3)),
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;addVertexWithUV(DDDDD)V"),
|
||||
index = 1)
|
||||
private double iris$sun$offsetY(double original) {
|
||||
return original - 0.3D;
|
||||
}
|
||||
|
||||
/**
|
||||
* In accordance with {@link #iris$sun$offsetY(double)} - moves the flare closer to the camera
|
||||
*/
|
||||
@ModifyArg(method = "renderSun",
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;addVertexWithUV(DDDDD)V", ordinal = 12),
|
||||
to = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 4)),
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;addVertexWithUV(DDDDD)V"),
|
||||
index = 1)
|
||||
private double iris$sunFlare$offsetY(double original) {
|
||||
return original - 0.3D;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables {@link GL11#glDisable(int GL_TEXTURE_2D)} call
|
||||
* @see #iris$atmosphereBlend$renderInDefaultProgram(CallbackInfo, Minecraft, LocalIntRef)
|
||||
*/
|
||||
@WrapWithCondition(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V", ordinal = 2), remap = false)
|
||||
private boolean iris$atmosphereBlend$revertTextureDisable(int i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables {@link GL11#glEnable(int GL_TEXTURE_2D)} call
|
||||
* @see #iris$atmosphereBlend$renderInDefaultProgram(CallbackInfo, Minecraft, LocalIntRef)
|
||||
*/
|
||||
@WrapWithCondition(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glEnable(I)V", ordinal = 3), remap = false)
|
||||
private boolean iris$atmosphereBlend$revertTextureEnable(int i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces atmosphere blend to render with the default program
|
||||
* <p>
|
||||
* Sometimes shaders have serious visual issues if something is rendered with {@link GL11#glDisable(int GL_TEXTURE_2D)}
|
||||
* <p>
|
||||
* Using {@link net.minecraft.client.renderer.texture.TextureManager#bindTexture(ResourceLocation GL_TEXTURE_2D_WORKAROUND)}, we achieve the same effect as {@link GL11#glDisable(int GL_TEXTURE_2D)}, except it works with shaders
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value="INVOKE", target="Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V", ordinal = 10))
|
||||
private void iris$atmosphereBlend$renderInDefaultProgram(CallbackInfo ci, @Local(argsOnly = true) Minecraft mc, @Share("atmo$previousProgram") LocalIntRef atmo$previousProgram) {
|
||||
atmo$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
mc.getTextureManager().bindTexture(GL_TEXTURE_2D_WORKAROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the program back before {@link #iris$atmosphereBlend$renderInDefaultProgram(CallbackInfo, Minecraft, LocalIntRef)} call
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value="INVOKE", target="Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 10, shift = At.Shift.AFTER))
|
||||
private void iris$atmosphereBlend$restorePreviousProgram(CallbackInfo ci, @Share("atmo$previousProgram") LocalIntRef atmo$previousProgram) {
|
||||
GLStateManager.glUseProgram(atmo$previousProgram.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces celestial bodies to render with the default program
|
||||
* <p>
|
||||
* Requires `iris$atmosphereBlend$.*`
|
||||
* <p>
|
||||
* Fixes many bugs, such as:
|
||||
* <p>
|
||||
* - [All Shaders] At certain time, celestial bodies are rendered as a just solid color squares
|
||||
* <p>
|
||||
* - [All Shaders] Some celestial bodies appear as heavily (70-90%) translucent
|
||||
* <p>
|
||||
* - [Complementary 5.5/6.x] Stars render on top of celestial bodies; not behind them
|
||||
* <p>
|
||||
* - [Complementary 5.5.x] Celestial bodies have incorrect colors near the sun
|
||||
* <p>
|
||||
* - [Complementary 5.6.x] Celestial bodies don't render at all far from the sun
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V", ordinal = 5))
|
||||
private void iris$celestials$renderInDefaultProgram(CallbackInfo ci, @Share("celestials$previousProgram") LocalIntRef celestials$previousProgram) {
|
||||
celestials$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the program back before {@link #iris$celestials$renderInDefaultProgram(CallbackInfo, LocalIntRef)} call
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 5, shift = At.Shift.AFTER))
|
||||
private void iris$celestials$restorePreviousProgram(CallbackInfo ci, @Share("celestials$previousProgram") LocalIntRef celestials$previousProgram) {
|
||||
GLStateManager.glUseProgram(celestials$previousProgram.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces celestial body ring (back) to render with the default program
|
||||
* @see #iris$celestials$renderInDefaultProgram(CallbackInfo, LocalIntRef)
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V", ordinal = 0))
|
||||
private void iris$ringsBack$renderInDefaultProgram(CallbackInfo ci, @Share("ringsBack$previousProgram") LocalIntRef ringsBack$previousProgram) {
|
||||
ringsBack$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the program back before {@link #iris$ringsBack$renderInDefaultProgram(CallbackInfo, LocalIntRef)} call
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 0, shift = At.Shift.AFTER))
|
||||
private void iris$ringsBack$restorePreviousProgram(CallbackInfo ci, @Share("ringsBack$previousProgram") LocalIntRef ringsBack$previousProgram) {
|
||||
GLStateManager.glUseProgram(ringsBack$previousProgram.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces celestial body ring (front) to render with the default program
|
||||
* @see #iris$celestials$renderInDefaultProgram(CallbackInfo, LocalIntRef)
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V", ordinal = 11))
|
||||
private void iris$ringsFront$renderInDefaultProgram(CallbackInfo ci, @Share("ringsFront$previousProgram") LocalIntRef ringsFront$previousProgram) {
|
||||
ringsFront$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the program back before {@link #iris$ringsFront$renderInDefaultProgram(CallbackInfo, LocalIntRef)} call
|
||||
*/
|
||||
@Inject(method = "renderCelestials", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 11, shift = At.Shift.AFTER))
|
||||
private void iris$ringsFront$restorePreviousProgram(CallbackInfo ci, @Share("ringsFront$previousProgram") LocalIntRef ringsFront$previousProgram) {
|
||||
GLStateManager.glUseProgram(ringsFront$previousProgram.get());
|
||||
}
|
||||
|
||||
// Pipeline setup
|
||||
|
||||
@Inject(method = "render", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;instance:Lnet/minecraft/client/renderer/Tessellator;"))
|
||||
private void iris$renderSky$beginNormalSky(CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
|
||||
pipeline.set(Iris.getPipelineManager().getPipelineNullable());
|
||||
if(pipeline.get() != null)
|
||||
pipeline.get().setPhase(WorldRenderingPhase.SKY);
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/entity/EntityClientPlayerMP;getPosition(F)Lnet/minecraft/util/Vec3;"))
|
||||
private void iris$setVoidRenderStage(CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
|
||||
if(pipeline.get() != null)
|
||||
pipeline.get().setPhase(WorldRenderingPhase.VOID);
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glRotatef(FFFF)V", ordinal = 2), remap = false)
|
||||
private void iris$renderSky$tiltSun(CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
|
||||
if(pipeline.get() != null)
|
||||
GLStateManager.glRotatef(pipeline.get().getSunPathRotation(), 0.0F, 0.0F, 1.0F);
|
||||
}
|
||||
|
||||
@Inject(method = "renderSun", at = @At(value = "FIELD", target = "Lcom/hbm/dim/CelestialBody;texture:Lnet/minecraft/util/ResourceLocation;"), remap = false)
|
||||
private void iris$setSunRenderStage(CallbackInfo ci) {
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.SUN));
|
||||
}
|
||||
|
||||
@Inject(method = "renderSunset", at = @At(value = "INVOKE", target = "Lcom/hbm/dim/SkyProviderCelestial;calcSunriseSunsetColors(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;FF)[F"), remap = false)
|
||||
private void iris$setSunsetRenderStage(CallbackInfo ci) {
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.SUNSET));
|
||||
}
|
||||
|
||||
@Inject(method = "renderStars", at = @At(value = "HEAD"), remap = false)
|
||||
private void iris$setStarRenderStage(CallbackInfo ci, @Share("pipeline") LocalRef<WorldRenderingPipeline> pipeline) {
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setPhase(WorldRenderingPhase.STARS));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.late.client.ntmSpace;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.CompatConfig;
|
||||
import com.hbm.dim.SkyProviderCelestial;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import jss.notfine.core.Settings;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = SkyProviderCelestial.class, priority = 100)
|
||||
public class MixinSkyProviderCelestial_Tweaks {
|
||||
|
||||
@WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", ordinal = 2), remap = false)
|
||||
private boolean angelica$renderHorizon(int i) {
|
||||
return (boolean) Settings.HORIZON.option.getStore();
|
||||
}
|
||||
|
||||
@Inject(method = "render", at = @At(value="INVOKE", target="Lnet/minecraft/client/renderer/Tessellator;draw()I", ordinal = 1))
|
||||
private void angelica$disableAltitudePlanetRenderer(CallbackInfo ci) {
|
||||
if(CompatConfig.NTMSpace_disableAltitudePlanetRenderer == CompatConfig.altitudePlanetRendererState.Always || (IrisApi.getInstance().isShaderPackInUse() && CompatConfig.NTMSpace_disableAltitudePlanetRenderer == CompatConfig.altitudePlanetRendererState.ShadersOnly)) {
|
||||
Tessellator.instance.vertexCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.late.client.ntmSpace;
|
||||
|
||||
import com.gtnewhorizons.angelica.utils.WorkaroundUtils;
|
||||
import com.hbm.dim.laythe.SkyProviderLaytheSunset;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
@@ -10,19 +12,15 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
public class MixinSkyProviderLaytheSunset {
|
||||
|
||||
/**
|
||||
* Disables {@link GL11#glDisable(int GL_TEXTURE_2D)} call
|
||||
* Avoid program rebinding due to pipeline.setInputs
|
||||
*/
|
||||
@WrapWithCondition(method = "renderSunset", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V", ordinal = 0), remap = false)
|
||||
private boolean iris$sunset$revertTextureDisable(int i) {
|
||||
return false;
|
||||
@WrapWithCondition(method = "renderSunset", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
|
||||
private boolean iris$sunset$redirectTex2D(int cap) {
|
||||
if(cap == GL11.GL_TEXTURE_2D) {
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables {@link GL11#glEnable(int GL_TEXTURE_2D)} call
|
||||
*/
|
||||
@WrapWithCondition(method = "renderSunset", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glEnable(I)V", ordinal = 0), remap = false)
|
||||
private boolean iris$sunsetBlend$revertTextureEnable(int i) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.gtnewhorizons.angelica.mixins.late.client.ntmSpace;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.utils.WorkaroundUtils;
|
||||
import com.hbm.dim.CelestialBody;
|
||||
import com.hbm.dim.orbit.SkyProviderOrbit;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(value = SkyProviderOrbit.class, priority = 100)
|
||||
public class MixinSkyProviderOrbit {
|
||||
|
||||
/**
|
||||
* Avoid program rebinding due to pipeline.setInputs
|
||||
*/
|
||||
@WrapWithCondition(method = "render", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDisable(I)V"), remap = false)
|
||||
private boolean iris$common$redirectTex2D(int cap) {
|
||||
if (cap == GL11.GL_TEXTURE_2D) {
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force skybox to render with the default program | Fix various issues with shaders enabled
|
||||
*/
|
||||
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderHelper;disableStandardItemLighting()V"), remap = false)
|
||||
public void iris$main$renderInDefaultProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
|
||||
main$previousProgram.set(GLStateManager.getActiveProgram());
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
@WrapOperation(method = "render",
|
||||
at = @At(value = "INVOKE", target = "Lcom/hbm/dim/orbit/SkyProviderOrbit;renderSun(FLnet/minecraft/client/multiplayer/WorldClient;Lnet/minecraft/client/Minecraft;Lcom/hbm/dim/CelestialBody;DDFF)V"), remap = false)
|
||||
private void iris$main$renderSunInShaderProgram(SkyProviderOrbit instance, float partialTicks, WorldClient world, Minecraft mc, CelestialBody sun, double sunSize, double coronaSize, float visibility, float pressure, Operation<Void> original, @Share("main$previousProgram") LocalIntRef main$previousProgram){
|
||||
GLStateManager.glUseProgram(main$previousProgram.get());
|
||||
original.call(instance, partialTicks, world, mc, sun, sunSize, coronaSize, visibility, pressure);
|
||||
GLStateManager.glUseProgram(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the program back before {@link #iris$main$renderInDefaultProgram(CallbackInfo, LocalIntRef)} call
|
||||
*/
|
||||
@Inject(method = "render", at = @At(value = "TAIL"), remap = false)
|
||||
public void iris$main$restorePreviousProgram(CallbackInfo ci, @Share("main$previousProgram") LocalIntRef main$previousProgram) {
|
||||
GLStateManager.glUseProgram(main$previousProgram.get());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,8 +70,8 @@ public class UmbraClientTweaker implements IFMLLoadingPlugin, IEarlyMixinLoader
|
||||
private static void verifyDependencies() {
|
||||
DependencyVerifier.verify(UmbraClientTweaker.class, List.of(
|
||||
new DependencyVerifier.Check(
|
||||
"/com/gtnewhorizon/gtnhlib/client/renderer/PrimitiveExtractor.class",
|
||||
"Missing dependency: Umbra requires GTNHLib 0.8.21+! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
|
||||
"Missing dependency: Umbra requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new DependencyVerifier.Check(
|
||||
"/xyz/wagyourtail/jvmdg/exc/MissingStubError.class",
|
||||
"GTNHLib is outdated: Umbra requires GTNHLib 0.9.0+! Download: https://modrinth.com/mod/gtnhlib")
|
||||
|
||||