mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 09:44:52 +00:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
580b72829d | ||
|
|
f31161fcc7 | ||
|
|
6e6d90aceb | ||
|
|
d96c10d52f | ||
|
|
ef3f500b1c | ||
|
|
be2f76e23e | ||
|
|
6ef0c77848 | ||
|
|
67656e6342 | ||
|
|
09f1cc7f01 | ||
|
|
c8619a5e27 | ||
|
|
bca80d5518 | ||
|
|
4c0c0970aa | ||
|
|
e1d150f4d5 | ||
|
|
28f62ca04b | ||
|
|
75c5b0f26f | ||
|
|
a62fae73c3 | ||
|
|
16a0e1b8a4 | ||
|
|
91a45c2b69 | ||
|
|
3deec73d3e | ||
|
|
c0ea254d62 | ||
|
|
dea84d4279 | ||
|
|
7a2286f3d5 | ||
|
|
c4c44bd14e | ||
|
|
936d6f82b3 | ||
|
|
6225fcd344 |
No files matched your search
+4
-3
@@ -72,7 +72,7 @@ dependencies {
|
||||
|
||||
// Iris Shaders
|
||||
compileOnly('org.jetbrains:annotations:26.0.2')
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.33:dev")
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.10:dev")
|
||||
|
||||
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
|
||||
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
|
||||
@@ -118,8 +118,9 @@ dependencies {
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
|
||||
|
||||
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
|
||||
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
|
||||
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:CoreTweaks:0.3.3.6-GTNH")
|
||||
// For testing alternative splash screens, can be sourced from https://github.com/MalTeeez/ModernSplash/releases
|
||||
//runtimeOnly(rfg.deobf(files("dependencies/modernsplash-1.7.10-1.2.2-dev.jar")))
|
||||
|
||||
// Hodgepodge
|
||||
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
|
||||
|
||||
@@ -22,18 +22,5 @@ repositories {
|
||||
includeGroup 'org.taumc'
|
||||
}
|
||||
}
|
||||
ivy {
|
||||
name = 'modernsplash-github'
|
||||
url = 'https://github.com/MalTeeez/ModernSplash/releases/download/'
|
||||
patternLayout {
|
||||
artifact '[revision]/[module]-[revision](-[classifier]).[ext]'
|
||||
}
|
||||
content {
|
||||
includeModule 'com.github.MalTeeez', 'modernsplash'
|
||||
}
|
||||
metadataSources {
|
||||
artifact()
|
||||
}
|
||||
}
|
||||
mavenLocal()
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger;
|
||||
public class ModStatus {
|
||||
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
|
||||
|
||||
public static BackhandReflectionCompat backhandCompat;
|
||||
public static boolean isBetterCrashesLoaded;
|
||||
public static boolean isNEIDLoaded;
|
||||
public static boolean isNEIDMetadataExtended;
|
||||
@@ -26,8 +27,9 @@ public class ModStatus {
|
||||
public static boolean isBaublesLoaded;
|
||||
public static boolean isFluidLoggedLoaded;
|
||||
|
||||
public static void preInit() {
|
||||
if (Loader.isModLoaded("backhand")) {
|
||||
public static void preInit(){
|
||||
isBackhandLoaded = Loader.isModLoaded("backhand");
|
||||
if(isBackhandLoaded) {
|
||||
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.compat.mojang;
|
||||
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
@@ -10,10 +9,14 @@ public enum InteractionHand {
|
||||
MAIN_HAND,
|
||||
OFF_HAND;
|
||||
|
||||
public ItemStack getItemInHand(EntityPlayer player) {
|
||||
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND) { // off hand (requires backhand)
|
||||
return BackhandReflectionCompat.getOffhandItem(player);
|
||||
} else { // main hand
|
||||
InteractionHand() {
|
||||
}
|
||||
|
||||
public ItemStack getItemInHand(EntityPlayer player){
|
||||
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND){ // off hand (requires backhand)
|
||||
return ModStatus.backhandCompat.getOffhandItem(player);
|
||||
}
|
||||
else { // main hand
|
||||
return player.getHeldItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean optimizeWorldUpdateLight;
|
||||
|
||||
@Config.Comment("Optimize Texture Animations. [From Hodgepodge]")
|
||||
@Config.Comment("Speedup Animations. [From Hodgepodge]")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean speedupAnimations;
|
||||
@@ -235,8 +235,4 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean disableF3Additions;
|
||||
|
||||
@Config.Comment("Replaces various FFP uploads with statically allocated VBO's.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean replaceFFPUploads;
|
||||
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
|
||||
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
|
||||
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
|
||||
@@ -553,7 +552,7 @@ public class DynamicLights {
|
||||
}
|
||||
}
|
||||
else if (ModStatus.isBackhandLoaded){
|
||||
ItemStack offhand = BackhandReflectionCompat.getOffhandItem(player);
|
||||
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
|
||||
if (offhand != null) {
|
||||
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ public class CompatShaderTransformer {
|
||||
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
|
||||
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
|
||||
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
|
||||
"shadow2D", "gl_FrontLightModelProduct",
|
||||
"gl_LightSource", "gl_FrontMaterial"
|
||||
"shadow2D", "gl_FrontLightModelProduct"
|
||||
);
|
||||
|
||||
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
|
||||
@@ -158,28 +157,6 @@ public class CompatShaderTransformer {
|
||||
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
|
||||
}
|
||||
|
||||
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_LightSource")) {
|
||||
transformer.injectFunction(
|
||||
"struct angelica_LightSourceParameters {"
|
||||
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
|
||||
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
|
||||
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
|
||||
transformer.rename("gl_LightSource", "angelica_LightSource");
|
||||
}
|
||||
|
||||
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
|
||||
if (source.contains("gl_FrontMaterial")) {
|
||||
transformer.injectFunction(
|
||||
"struct angelica_MaterialParameters {"
|
||||
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
|
||||
+ "};");
|
||||
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
|
||||
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
|
||||
}
|
||||
|
||||
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
|
||||
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
|
||||
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.joml.Matrix3f;
|
||||
@@ -43,62 +41,28 @@ public class CompatUniformManager {
|
||||
static final int LOC_SCENE_COLOR = 15;
|
||||
static final int LOC_CLIP_PLANES = 16;
|
||||
static final int LOC_CLIP_PLANES_ENABLED = 17;
|
||||
static final int LOC_COUNT = 18;
|
||||
|
||||
// Light source fields: 12 per light × 2 lights = 24 locations
|
||||
static final int LIGHT_FIELDS = 12;
|
||||
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
|
||||
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
|
||||
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
|
||||
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
|
||||
static final int LOC_LIGHT_BASE = 18;
|
||||
|
||||
// Material fields: 5 locations
|
||||
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
|
||||
static final int MAT_FIELDS = 5;
|
||||
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 42
|
||||
|
||||
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 47
|
||||
|
||||
private static final String[] LIGHT_FIELD_NAMES = {
|
||||
"ambient", "diffuse", "specular", "position", "halfVector",
|
||||
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
|
||||
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
|
||||
private static final String[] UNIFORM_NAMES = {
|
||||
"angelica_ModelViewMatrix",
|
||||
"angelica_ModelViewMatrixInverse",
|
||||
"angelica_ProjectionMatrix",
|
||||
"angelica_ProjectionMatrixInverse",
|
||||
"angelica_NormalMatrix",
|
||||
"angelica_LightmapTextureMatrix",
|
||||
"angelica_FogDensity",
|
||||
"angelica_FogStart",
|
||||
"angelica_FogEnd",
|
||||
"angelica_FogColor",
|
||||
"iris_ModelViewMatrix",
|
||||
"iris_ProjectionMatrix",
|
||||
"iris_NormalMatrix",
|
||||
"iris_LightmapTextureMatrix",
|
||||
"angelica_currentAlphaTest",
|
||||
"angelica_SceneColor",
|
||||
"angelica_ClipPlane[0]",
|
||||
"angelica_ClipPlanesEnabled",
|
||||
};
|
||||
private static final String[] MAT_FIELD_NAMES = {
|
||||
"emission", "ambient", "diffuse", "specular", "shininess",
|
||||
};
|
||||
|
||||
private static final String[] UNIFORM_NAMES;
|
||||
static {
|
||||
UNIFORM_NAMES = new String[LOC_COUNT];
|
||||
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
|
||||
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
|
||||
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
|
||||
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
|
||||
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
|
||||
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
|
||||
for (int li = 0; li < 2; li++) {
|
||||
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] =
|
||||
"angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
for (int fi = 0; fi < MAT_FIELDS; fi++) {
|
||||
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
|
||||
}
|
||||
}
|
||||
|
||||
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
|
||||
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
|
||||
@@ -174,13 +138,13 @@ public class CompatUniformManager {
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
// Lighting-derived uniforms (scene color) — skip if lighting state unchanged
|
||||
if (locs[LOC_SCENE_COLOR] != -1) {
|
||||
final int litGen = GLStateManager.lightingGeneration;
|
||||
if (programChanged || litGen != lastLightingGen) {
|
||||
lastLightingGen = litGen;
|
||||
uploadSceneColor(locs);
|
||||
}
|
||||
}
|
||||
|
||||
// Clip plane equations + enabled bool — uploaded when enable state or equations change
|
||||
@@ -313,75 +277,6 @@ public class CompatUniformManager {
|
||||
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
|
||||
}
|
||||
|
||||
private static void uploadLightSources(int[] locs) {
|
||||
// Early exit: if the first light field isn't present, no lighting uniforms exist
|
||||
if (locs[LOC_LIGHT_BASE] == -1) return;
|
||||
final LightStateStack[] lights = GLStateManager.getLightDataStates();
|
||||
for (int li = 0; li < 2; li++) {
|
||||
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
|
||||
final LightState light = lights[li];
|
||||
|
||||
if (locs[base + LF_AMBIENT] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
|
||||
if (locs[base + LF_DIFFUSE] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
|
||||
if (locs[base + LF_SPECULAR] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
|
||||
if (locs[base + LF_POSITION] != -1)
|
||||
GL20.glUniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
|
||||
if (locs[base + LF_HALF_VECTOR] != -1) {
|
||||
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
|
||||
float hx, hy, hz;
|
||||
if (light.position.w == 0.0f) {
|
||||
float len = (float) Math.sqrt(light.position.x * light.position.x
|
||||
+ light.position.y * light.position.y + light.position.z * light.position.z);
|
||||
if (len > 0) {
|
||||
hx = light.position.x / len;
|
||||
hy = light.position.y / len;
|
||||
hz = light.position.z / len + 1.0f;
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
} else {
|
||||
hx = 0; hy = 0; hz = 1;
|
||||
}
|
||||
float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
|
||||
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
|
||||
GL20.glUniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
|
||||
}
|
||||
if (locs[base + LF_SPOT_DIRECTION] != -1)
|
||||
GL20.glUniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
|
||||
if (locs[base + LF_SPOT_EXPONENT] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
|
||||
if (locs[base + LF_SPOT_CUTOFF] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
|
||||
if (locs[base + LF_SPOT_COS_CUTOFF] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
|
||||
if (locs[base + LF_CONSTANT_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
|
||||
if (locs[base + LF_LINEAR_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
|
||||
if (locs[base + LF_QUADRATIC_ATTEN] != -1)
|
||||
GL20.glUniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadMaterial(int[] locs) {
|
||||
// Early exit: if the first material field isn't present, no material uniforms exist
|
||||
if (locs[LOC_MAT_BASE] == -1) return;
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
|
||||
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
|
||||
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
|
||||
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
|
||||
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
|
||||
GL20.glUniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
|
||||
|
||||
@@ -299,7 +299,8 @@ public class Feature {
|
||||
));
|
||||
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
|
||||
GLStateManager.activeTextureUnit // Active texture unit
|
||||
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
|
||||
, GLStateManager.texEnvMode // GL_TEXTURE_ENV_MODE
|
||||
, GLStateManager.texEnvColor // GL_TEXTURE_ENV_COLOR
|
||||
// Enable bits for the four texture coordinates
|
||||
|
||||
// Border color for each texture image
|
||||
@@ -313,10 +314,10 @@ public class Feature {
|
||||
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
|
||||
));
|
||||
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
|
||||
// Current Texture Bindings - GL_TEXTURE_BINDING_2D
|
||||
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
|
||||
textureAttribs.add(GLStateManager.textures.getTexEnvState(i));
|
||||
// textureAttribs.add(GLStateManager.textures.getInfo(i))
|
||||
}
|
||||
|
||||
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
|
||||
|
||||
@@ -1,388 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Software emulation of GL feedback mode
|
||||
*/
|
||||
public class FeedbackManager {
|
||||
|
||||
private static final int FB_3D = 0x01;
|
||||
private static final int FB_4D = 0x02;
|
||||
private static final int FB_COLOR = 0x04;
|
||||
private static final int FB_TEXTURE = 0x08;
|
||||
|
||||
private static int renderMode = GL11.GL_RENDER;
|
||||
private static FloatBuffer feedbackBuffer;
|
||||
private static int feedbackBufferSize;
|
||||
private static int feedbackMask;
|
||||
private static int feedbackCount;
|
||||
|
||||
private static final Matrix4f mvpMatrix = new Matrix4f();
|
||||
private static final Vector4f clipVec = new Vector4f();
|
||||
|
||||
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
|
||||
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
|
||||
private static final IntBuffer attribBuf = ByteBuffer.allocateDirect(16 * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
|
||||
|
||||
@FunctionalInterface
|
||||
private interface IndexReader {
|
||||
int read(int i);
|
||||
}
|
||||
|
||||
public static boolean isFeedbackMode() {
|
||||
return renderMode == GL11.GL_FEEDBACK;
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
AngelicaTweaker.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
|
||||
return;
|
||||
}
|
||||
feedbackBuffer = buffer;
|
||||
feedbackBufferSize = buffer.remaining();
|
||||
feedbackMask = computeMask(type);
|
||||
feedbackCount = 0;
|
||||
}
|
||||
|
||||
public static int glRenderMode(int mode) {
|
||||
int result = 0;
|
||||
switch (renderMode) {
|
||||
case GL11.GL_FEEDBACK:
|
||||
result = (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
|
||||
break;
|
||||
case GL11.GL_RENDER:
|
||||
case GL11.GL_SELECT:
|
||||
result = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (mode == GL11.GL_SELECT) {
|
||||
AngelicaTweaker.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
|
||||
}
|
||||
|
||||
feedbackCount = 0;
|
||||
renderMode = mode;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void glPassThrough(float token) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
|
||||
feedbackToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
private static void feedbackToken(float token) {
|
||||
if (feedbackCount < feedbackBufferSize) {
|
||||
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
|
||||
}
|
||||
feedbackCount++;
|
||||
}
|
||||
|
||||
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
|
||||
feedbackToken(winX);
|
||||
feedbackToken(winY);
|
||||
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
|
||||
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
|
||||
if ((feedbackMask & FB_COLOR) != 0) {
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
if ((feedbackMask & FB_TEXTURE) != 0) {
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(0f);
|
||||
feedbackToken(1f);
|
||||
}
|
||||
}
|
||||
|
||||
private static void transformAndEmitVertex(float x, float y, float z) {
|
||||
clipVec.set(x, y, z, 1.0f);
|
||||
mvpMatrix.transform(clipVec);
|
||||
|
||||
final float w = clipVec.w;
|
||||
final float ndcX = clipVec.x / w;
|
||||
final float ndcY = clipVec.y / w;
|
||||
final float ndcZ = clipVec.z / w;
|
||||
|
||||
final ViewportState vp = GLStateManager.getViewportState();
|
||||
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
|
||||
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
|
||||
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ
|
||||
+ (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
|
||||
final float winW = 1.0f / w;
|
||||
|
||||
feedbackVertex(winX, winY, winZ, winW);
|
||||
}
|
||||
|
||||
public static void processDrawArrays(int mode, int first, int count) {
|
||||
computeMVP();
|
||||
int stride = queryPositionStride();
|
||||
if (stride == 0) stride = 12; // tightly packed 3 floats
|
||||
|
||||
final long posOffset = queryPositionOffset();
|
||||
final int byteCount = count * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
|
||||
|
||||
emitAllPrimitives(mode, count, i -> i, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
|
||||
computeMVP();
|
||||
int stride = queryPositionStride();
|
||||
if (stride == 0) stride = 12;
|
||||
|
||||
final int elementSize = elementSizeForType(type);
|
||||
final int indexByteCount = indexCount * elementSize;
|
||||
ensureIndexReadbackCapacity(indexByteCount);
|
||||
indexReadbackBuf.clear().limit(indexByteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
|
||||
final ByteBuffer indexBuf = indexReadbackBuf;
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
final int idx = readIndex(indexBuf, type, i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
final long posOffset = queryPositionOffset();
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
|
||||
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, indexCount, i -> readIndex(indexBuf, type, i) - base, stride);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ByteBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, ShortBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, IntBuffer indices) {
|
||||
final int basePos = indices.position();
|
||||
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
|
||||
}
|
||||
|
||||
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
|
||||
final int pos = indices.position();
|
||||
final IndexReader reader = switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
|
||||
default -> i -> 0;
|
||||
};
|
||||
processDrawElementsImpl(mode, count, reader);
|
||||
}
|
||||
|
||||
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
|
||||
computeMVP();
|
||||
int stride = queryPositionStride();
|
||||
if (stride == 0) stride = 12;
|
||||
|
||||
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final int idx = reader.read(i);
|
||||
if (idx < minIdx) minIdx = idx;
|
||||
if (idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
|
||||
readVerticesFromVBO(minIdx, maxIdx, stride);
|
||||
final int base = minIdx;
|
||||
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
|
||||
}
|
||||
|
||||
private static void computeMVP() {
|
||||
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
|
||||
}
|
||||
|
||||
private static int queryPositionStride() {
|
||||
attribBuf.clear();
|
||||
GL20.glGetVertexAttrib(0, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE, attribBuf);
|
||||
return attribBuf.get(0);
|
||||
}
|
||||
|
||||
private static int computeMask(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_2D -> 0;
|
||||
case GL11.GL_3D -> FB_3D;
|
||||
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
|
||||
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
|
||||
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int elementSizeForType(int type) {
|
||||
return switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> 1;
|
||||
case GL11.GL_UNSIGNED_SHORT -> 2;
|
||||
case GL11.GL_UNSIGNED_INT -> 4;
|
||||
default -> 4;
|
||||
};
|
||||
}
|
||||
|
||||
private static int readIndex(ByteBuffer buf, int type, int i) {
|
||||
return switch (type) {
|
||||
case GL11.GL_UNSIGNED_BYTE -> buf.get(i) & 0xFF;
|
||||
case GL11.GL_UNSIGNED_SHORT -> buf.getShort(i * 2) & 0xFFFF;
|
||||
case GL11.GL_UNSIGNED_INT -> buf.getInt(i * 4);
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static void ensureReadbackCapacity(int bytes) {
|
||||
if (readbackBuf.capacity() < bytes) {
|
||||
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureIndexReadbackCapacity(int bytes) {
|
||||
if (indexReadbackBuf.capacity() < bytes) {
|
||||
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
}
|
||||
|
||||
private static long queryPositionOffset() {
|
||||
final ByteBuffer buf = GL20.glGetVertexAttribPointer(0, GL20.GL_VERTEX_ATTRIB_ARRAY_POINTER, 8);
|
||||
return buf == null ? 0L : buf.getLong(0);
|
||||
}
|
||||
|
||||
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
|
||||
final long posOffset = queryPositionOffset();
|
||||
final int vertexCount = maxIdx - minIdx + 1;
|
||||
final int byteCount = vertexCount * stride;
|
||||
ensureReadbackCapacity(byteCount);
|
||||
readbackBuf.clear().limit(byteCount);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
|
||||
}
|
||||
|
||||
private static void emitVertex(int vertexIndex, int stride) {
|
||||
final float x = readbackBuf.getFloat(vertexIndex * stride);
|
||||
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
|
||||
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
|
||||
transformAndEmitVertex(x, y, z);
|
||||
}
|
||||
|
||||
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
|
||||
switch (mode) {
|
||||
case GL11.GL_POINTS:
|
||||
for (int i = 0; i < count; i++) {
|
||||
feedbackToken(GL11.GL_POINT_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINES:
|
||||
for (int i = 0; i + 1 < count; i += 2) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_STRIP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_LINE_LOOP:
|
||||
for (int i = 0; i + 1 < count; i++) {
|
||||
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
if (count > 2) {
|
||||
feedbackToken(GL11.GL_LINE_TOKEN);
|
||||
emitVertex(reader.read(count - 1), stride);
|
||||
emitVertex(reader.read(0), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLES:
|
||||
for (int i = 0; i + 2 < count; i += 3) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_STRIP:
|
||||
for (int i = 0; i + 2 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
if ((i & 1) == 0) {
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
} else {
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
}
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_TRIANGLE_FAN:
|
||||
for (int i = 1; i + 1 < count; i++) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(3);
|
||||
emitVertex(reader.read(0), stride);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
case GL11.GL_QUADS:
|
||||
for (int i = 0; i + 3 < count; i += 4) {
|
||||
feedbackToken(GL11.GL_POLYGON_TOKEN);
|
||||
feedbackToken(4);
|
||||
emitVertex(reader.read(i), stride);
|
||||
emitVertex(reader.read(i + 1), stride);
|
||||
emitVertex(reader.read(i + 2), stride);
|
||||
emitVertex(reader.read(i + 3), stride);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
|
||||
}
|
||||
}
|
||||
|
||||
static void reset() {
|
||||
renderMode = GL11.GL_RENDER;
|
||||
feedbackBuffer = null;
|
||||
feedbackBufferSize = 0;
|
||||
feedbackMask = 0;
|
||||
feedbackCount = 0;
|
||||
}
|
||||
}
|
||||
@@ -620,7 +620,6 @@ public final class GLDebug {
|
||||
private int depth = 0;
|
||||
private static final int maxDepth = GL11.glGetInteger(KHRDebug.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
|
||||
private static final int maxNameLength = GL11.glGetInteger(KHRDebug.GL_MAX_LABEL_LENGTH);
|
||||
private final String[] groupStack = new String[maxDepth + 1];
|
||||
|
||||
@Override
|
||||
public void nameObject(int id, int object, String name) {
|
||||
@@ -629,28 +628,20 @@ public final class GLDebug {
|
||||
|
||||
@Override
|
||||
public void pushGroup(String name) {
|
||||
if (depth + 1 > maxDepth) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
|
||||
for (int i = 0; i < depth; i++) {
|
||||
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
|
||||
}
|
||||
sb.append(" -> ").append(name).append(" (rejected)");
|
||||
throw new RuntimeException(sb.toString());
|
||||
}
|
||||
groupStack[depth] = name;
|
||||
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
|
||||
depth++;
|
||||
if (depth > maxDepth) {
|
||||
throw new RuntimeException("Stack overflow");
|
||||
}
|
||||
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popGroup() {
|
||||
if (depth - 1 < 0) {
|
||||
depth--;
|
||||
if (depth < 0) {
|
||||
throw new RuntimeException("Stack underflow");
|
||||
}
|
||||
KHRDebug.glPopDebugGroup();
|
||||
depth--;
|
||||
groupStack[depth] = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,7 +42,6 @@ import com.gtnewhorizons.angelica.glsm.texture.TextureTracker;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
|
||||
import cpw.mods.fml.relauncher.ReflectionHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntStack;
|
||||
@@ -180,10 +179,6 @@ public class GLStateManager {
|
||||
// Highest texture unit index that has ever had a non-zero binding; limits onDeleteTexture scan range
|
||||
private static int maxBoundTextureUnit = 0;
|
||||
|
||||
// Deferred texture deletion: names kept valid until glGenTextures recycles them.
|
||||
// Emulates Mesa/compat-profile behavior where delete-then-bind doesn't error.
|
||||
private static final IntOpenHashSet deferredDeleteTextures = new IntOpenHashSet();
|
||||
|
||||
@Getter private static Vendor VENDOR;
|
||||
|
||||
// This setting varies depending on driver, so it gets queried at runtime
|
||||
@@ -306,6 +301,10 @@ public class GLStateManager {
|
||||
@Getter protected static final AlphaStateStack alphaState = new AlphaStateStack();
|
||||
@Getter protected static final BooleanStateStack alphaTest = new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true);
|
||||
|
||||
// Texture environment mode (GL_MODULATE default per OpenGL spec)
|
||||
@Getter protected static final IntegerStateStack texEnvMode = new IntegerStateStack(GL11.GL_MODULATE);
|
||||
// Texture environment color (used by GL_BLEND mode)
|
||||
@Getter protected static final Color4Stack texEnvColor = new Color4Stack(new Color4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
@Getter protected static final BooleanStateStack lightingState = new BooleanStateStack(GL11.GL_LIGHTING, false, true);
|
||||
@Getter protected static final BooleanStateStack rescaleNormalState = new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true);
|
||||
@@ -415,10 +414,6 @@ public class GLStateManager {
|
||||
@Getter protected static int boundVBO;
|
||||
@Getter protected static int boundEBO;
|
||||
@Getter protected static int boundVAO;
|
||||
private static final Int2IntOpenHashMap vaoEboMap = new Int2IntOpenHashMap();
|
||||
static {
|
||||
vaoEboMap.defaultReturnValue(0);
|
||||
}
|
||||
@Getter private static int defaultVAO; // Non-zero on core profile
|
||||
|
||||
public static void reset() {
|
||||
@@ -440,7 +435,6 @@ public class GLStateManager {
|
||||
|
||||
modelViewMatrix.clear();
|
||||
projectionMatrix.clear();
|
||||
vaoEboMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -969,12 +963,14 @@ public class GLStateManager {
|
||||
case GL30.GL_DRAW_FRAMEBUFFER_BINDING -> drawFramebuffer;
|
||||
case GL30.GL_READ_FRAMEBUFFER_BINDING -> readFramebuffer;
|
||||
|
||||
default -> switch (pname) {
|
||||
case GL11.GL_FOG_MODE -> fogState.getFogMode();
|
||||
case GL11.GL_LINE_STIPPLE_PATTERN -> lineState.getStipplePattern() & 0xFFFF;
|
||||
case GL11.GL_LINE_STIPPLE_REPEAT -> lineState.getStippleFactor();
|
||||
default -> GL11.glGetInteger(pname);
|
||||
};
|
||||
default -> {
|
||||
yield switch (pname) {
|
||||
case GL11.GL_FOG_MODE -> fogState.getFogMode();
|
||||
case GL11.GL_LINE_STIPPLE_PATTERN -> lineState.getStipplePattern() & 0xFFFF;
|
||||
case GL11.GL_LINE_STIPPLE_REPEAT -> lineState.getStippleFactor();
|
||||
default -> GL11.glGetInteger(pname);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1050,7 +1046,10 @@ public class GLStateManager {
|
||||
params.put((float) viewportState.depthRangeNear);
|
||||
params.put((float) viewportState.depthRangeFar);
|
||||
}
|
||||
case GL14.GL_BLEND_COLOR -> params.put(blendState.getBlendColorR()).put(blendState.getBlendColorG()).put(blendState.getBlendColorB()).put(blendState.getBlendColorA());
|
||||
case GL14.GL_BLEND_COLOR -> {
|
||||
params.put(blendState.getBlendColorR()).put(blendState.getBlendColorG())
|
||||
.put(blendState.getBlendColorB()).put(blendState.getBlendColorA());
|
||||
}
|
||||
case GL11.GL_CURRENT_NORMAL -> {
|
||||
final Vector3f normal = ShaderManager.getCurrentNormal();
|
||||
params.put(normal.x).put(normal.y).put(normal.z);
|
||||
@@ -1642,11 +1641,6 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Rebinding a deferred-delete texture rescues it (Mesa/compat-profile behavior)
|
||||
if (texture != 0) {
|
||||
deferredDeleteTextures.remove(texture);
|
||||
}
|
||||
|
||||
if (target != GL11.GL_TEXTURE_2D) {
|
||||
// We're only supporting 2D textures for now
|
||||
GL11.glBindTexture(target, texture);
|
||||
@@ -1673,6 +1667,46 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels));
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final int texture = getBoundTextureForServerState();
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(texture, target, level, internalformat, width, height);
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, ShortBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromShortBuffer(target, level, internalformat, width, height, border, format, type, pixels));
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final int texture = getBoundTextureForServerState();
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(texture, target, level, internalformat, width, height);
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
@@ -1681,13 +1715,15 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
final int texture = getBoundTextureForServerState();
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(texture, target, level, internalformat, width, height);
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(getBoundTextureForServerState(), target, level, internalformat, width, height, border, format, type, pixels);
|
||||
RenderSystem.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1699,9 +1735,16 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
// FloatBuffer not in DSA interface - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
final int texture = getBoundTextureForServerState();
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(texture, target, level, internalformat, width, height);
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
|
||||
@@ -1712,26 +1755,15 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
// DoubleBuffer not in DSA interface - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordComplexCommand(TexImage2DCmd.fromByteBuffer(target, level, internalformat, width, height, border, format, type, pixels));
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels != null ? pixels.asIntBuffer() : null);
|
||||
final int texture = getBoundTextureForServerState();
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(texture, target, level, internalformat, width, height);
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture - keeps GL binding state unchanged
|
||||
RenderSystem.textureImage2D(getBoundTextureForServerState(), target, level, internalformat, width, height, border, format, type, pixels);
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1739,8 +1771,15 @@ public class GLStateManager {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw new UnsupportedOperationException("glTexImage2D with buffer offset in display lists not yet supported");
|
||||
}
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
final int texture = getBoundTextureForServerState();
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(texture, target, level, internalformat, width, height);
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexCoord1f(float s) {
|
||||
@@ -1809,69 +1848,17 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDeleteTextures(int id) {
|
||||
deferDeleteTexture(id);
|
||||
onDeleteTexture(id);
|
||||
|
||||
GL11.glDeleteTextures(id);
|
||||
}
|
||||
|
||||
public static void glDeleteTextures(IntBuffer ids) {
|
||||
for(int i = 0; i < ids.remaining(); i++) {
|
||||
final int id = ids.get(ids.position() + i);
|
||||
deferDeleteTexture(id);
|
||||
onDeleteTexture(id);
|
||||
}
|
||||
}
|
||||
|
||||
/** Shrink texture to 1x1 to free GPU memory, unbind from all units, but keep the name valid. */
|
||||
private static void deferDeleteTexture(int id) {
|
||||
if (id == 0) return;
|
||||
|
||||
final int savedUnit = GLStateManager.activeTextureUnit.getValue();
|
||||
final int savedBinding = textures.getTextureUnitBindings(savedUnit).getBinding();
|
||||
boolean changedUnit = false;
|
||||
|
||||
// Unbind from all units that have this texture
|
||||
for (int i = 0; i <= maxBoundTextureUnit; i++) {
|
||||
if (textures.getTextureUnitBindings(i).getBinding() == id) {
|
||||
if (i != savedUnit) {
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
changedUnit = true;
|
||||
}
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
onDeleteTexture(ids.get(i));
|
||||
}
|
||||
|
||||
// Restore active unit, then shrink the texture via a temporary bind
|
||||
if (changedUnit) {
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + savedUnit);
|
||||
}
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R8, 1, 1, 0, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
// Restore previous binding on active unit
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, savedBinding == id ? 0 : savedBinding);
|
||||
|
||||
deferredDeleteTextures.add(id);
|
||||
}
|
||||
|
||||
/** Flush deferred deletes so the driver can recycle names. */
|
||||
private static void flushDeferredTextureDeletes() {
|
||||
if (deferredDeleteTextures.isEmpty()) return;
|
||||
final var it = deferredDeleteTextures.iterator();
|
||||
while (it.hasNext()) {
|
||||
GL11.glDeleteTextures(it.nextInt());
|
||||
}
|
||||
deferredDeleteTextures.clear();
|
||||
}
|
||||
|
||||
public static int glGenTextures() {
|
||||
flushDeferredTextureDeletes();
|
||||
return GL11.glGenTextures();
|
||||
}
|
||||
|
||||
public static void glGenTextures(IntBuffer textures) {
|
||||
flushDeferredTextureDeletes();
|
||||
GL11.glGenTextures(textures);
|
||||
GL11.glDeleteTextures(ids);
|
||||
}
|
||||
|
||||
public static void enableTexture() {
|
||||
@@ -2013,10 +2000,6 @@ public class GLStateManager {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
|
||||
}
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.processDrawElements(mode, indices);
|
||||
return;
|
||||
}
|
||||
ShaderManager.getInstance().preDraw();
|
||||
GL11.glDrawElements(mode, indices);
|
||||
}
|
||||
@@ -2025,10 +2008,6 @@ public class GLStateManager {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
|
||||
}
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.processDrawElements(mode, indices);
|
||||
return;
|
||||
}
|
||||
ShaderManager.getInstance().preDraw();
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadElementsAsTriangles(indices);
|
||||
@@ -2041,10 +2020,6 @@ public class GLStateManager {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
|
||||
}
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.processDrawElements(mode, indices);
|
||||
return;
|
||||
}
|
||||
ShaderManager.getInstance().preDraw();
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadElementsAsTriangles(indices);
|
||||
@@ -2057,10 +2032,6 @@ public class GLStateManager {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw new UnsupportedOperationException("glDrawElements in display lists not yet implemented - if you see this, please report!");
|
||||
}
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.processDrawElements(mode, count, type, indices);
|
||||
return;
|
||||
}
|
||||
ShaderManager.getInstance().preDraw();
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
|
||||
@@ -2081,10 +2052,6 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.processDrawElements(mode, indices_count, type, indices_buffer_offset);
|
||||
return;
|
||||
}
|
||||
ShaderManager.getInstance().preDraw();
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
|
||||
@@ -2117,10 +2084,6 @@ public class GLStateManager {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.processDrawArrays(mode, first, count);
|
||||
return;
|
||||
}
|
||||
ShaderManager.getInstance().preDraw();
|
||||
if (mode == GL11.GL_QUADS) {
|
||||
QuadConverter.drawQuadsAsTriangles(first, count);
|
||||
@@ -2141,31 +2104,38 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glVertexPointer(int size, int stride, FloatBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glVertexPointer(int size, int type, int stride, ByteBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer);
|
||||
}
|
||||
|
||||
public static void glVertexPointer(int size, int type, int stride, long pointer_buffer_offset) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer_buffer_offset);
|
||||
}
|
||||
|
||||
public static void glColorPointer(int size, int stride, FloatBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, GL11.GL_FLOAT, Usage.COLOR.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glColorPointer(int size, int type, int stride, ByteBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
|
||||
}
|
||||
|
||||
public static void glColorPointer(int size, boolean unsigned, int stride, ByteBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int type = unsigned ? GL11.GL_UNSIGNED_BYTE : GL11.GL_BYTE;
|
||||
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer);
|
||||
}
|
||||
|
||||
public static void glColorPointer(int size, int type, int stride, long pointer_buffer_offset) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.COLOR.getAttributeLocation(), size, type, Usage.COLOR.isNormalized(), stride, pointer_buffer_offset);
|
||||
}
|
||||
|
||||
@@ -2182,26 +2152,32 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glNormalPointer(int type, int stride, ByteBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer);
|
||||
}
|
||||
|
||||
public static void glNormalPointer(int type, int stride, FloatBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glNormalPointer(int type, int stride, ShortBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_SHORT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glNormalPointer(int type, int stride, IntBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_INT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glNormalPointer(int type, int stride, long pointer_buffer_offset) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
GL20.glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer_buffer_offset);
|
||||
}
|
||||
|
||||
public static void glTexCoordPointer(int size, int type, int stride, ByteBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int loc = texCoordAttributeLocation();
|
||||
if (loc < 0) return;
|
||||
GL20.glVertexAttribPointer(loc, size, type, false, stride, pointer);
|
||||
@@ -2220,30 +2196,35 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glTexCoordPointer(int size, int type, int stride, FloatBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int loc = texCoordAttributeLocation();
|
||||
if (loc < 0) return;
|
||||
GL20.glVertexAttribPointer(loc, size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glTexCoordPointer(int size, int type, int stride, ShortBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int loc = texCoordAttributeLocation();
|
||||
if (loc < 0) return;
|
||||
GL20.glVertexAttribPointer(loc, size, GL11.GL_SHORT, false, stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glTexCoordPointer(int size, int type, int stride, IntBuffer pointer) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int loc = texCoordAttributeLocation();
|
||||
if (loc < 0) return;
|
||||
GL20.glVertexAttribPointer(loc, size, GL11.GL_INT, false, stride, MemoryUtilities.memByteBuffer(pointer));
|
||||
}
|
||||
|
||||
public static void glTexCoordPointer(int size, int type, int stride, long pointer_buffer_offset) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int loc = texCoordAttributeLocation();
|
||||
if (loc < 0) return;
|
||||
GL20.glVertexAttribPointer(loc, size, type, false, stride, pointer_buffer_offset);
|
||||
}
|
||||
|
||||
public static void glEnableClientState(int cap) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int location = clientStateToAttributeLocation(cap);
|
||||
if (location >= 0) GL20.glEnableVertexAttribArray(location);
|
||||
final int flag = clientStateToVertexFlag(cap);
|
||||
@@ -2251,6 +2232,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glDisableClientState(int cap) {
|
||||
if (!ShaderManager.getInstance().isActive()) return;
|
||||
final int location = clientStateToAttributeLocation(cap);
|
||||
if (location >= 0) GL20.glDisableVertexAttribArray(location);
|
||||
final int flag = clientStateToVertexFlag(cap);
|
||||
@@ -2717,10 +2699,12 @@ public class GLStateManager {
|
||||
PBRTextureManager.INSTANCE.onDeleteTexture(id);
|
||||
}
|
||||
|
||||
// Always delete
|
||||
for (int i = 0; i <= maxBoundTextureUnit; i++) {
|
||||
if (textures.getTextureUnitBindings(i).getBinding() == id) {
|
||||
textures.getTextureUnitBindings(i).setBinding(0);
|
||||
// Only update cached texture bindings on main thread
|
||||
if (isCachingEnabled()) {
|
||||
for (int i = 0; i <= maxBoundTextureUnit; i++) {
|
||||
if (textures.getTextureUnitBindings(i).getBinding() == id) {
|
||||
textures.getTextureUnitBindings(i).setBinding(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2815,11 +2799,8 @@ public class GLStateManager {
|
||||
|
||||
public static void glCallList(int list) {
|
||||
GLDebug.pushGroup("glCallList " + list);
|
||||
try {
|
||||
DisplayListManager.glCallList(list);
|
||||
} finally {
|
||||
GLDebug.popGroup();
|
||||
}
|
||||
DisplayListManager.glCallList(list);
|
||||
GLDebug.popGroup();
|
||||
}
|
||||
|
||||
public static void pushState(int mask) {
|
||||
@@ -2935,7 +2916,6 @@ public class GLStateManager {
|
||||
if ((mask & GL11.GL_LIGHTING_BIT) != 0 && lightingGeneration != savedLightingGen[depth]) lightingGeneration++;
|
||||
if ((mask & GL11.GL_FOG_BIT) != 0 && fragmentGeneration != savedFragmentGen[depth]) fragmentGeneration++;
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0 && fragmentGeneration != savedFragmentGen[depth]) fragmentGeneration++; // alpha ref
|
||||
if ((mask & GL11.GL_TEXTURE_BIT) != 0 && fragmentGeneration != savedFragmentGen[depth]) fragmentGeneration++; // texenv state
|
||||
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
|
||||
if (mvGeneration != savedMvGen[depth]) mvGeneration++;
|
||||
if (projGeneration != savedProjGen[depth]) projGeneration++;
|
||||
@@ -2981,12 +2961,9 @@ public class GLStateManager {
|
||||
GLDebug.popGroup();
|
||||
poppingAttributes = true;
|
||||
GLDebug.pushGroup("popState");
|
||||
try {
|
||||
popState();
|
||||
} finally {
|
||||
GLDebug.popGroup();
|
||||
poppingAttributes = false;
|
||||
}
|
||||
popState();
|
||||
GLDebug.popGroup();
|
||||
poppingAttributes = false;
|
||||
}
|
||||
|
||||
// Matrix Operations
|
||||
@@ -3971,12 +3948,31 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
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);
|
||||
RenderSystem.textureSubImage2D(getBoundTextureForServerState(), target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ShortBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromShortBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
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_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3988,12 +3984,49 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
if (shouldUseDSA(target)) {
|
||||
// Use DSA to upload directly to the texture
|
||||
RenderSystem.textureSubImage2D(getBoundTextureForServerState(), target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
// 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_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromFloatBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
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_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, DoubleBuffer pixels) {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
DisplayListManager.recordComplexCommand(TexSubImage2DCmd.fromDoubleBuffer(target, level, xoffset, yoffset, width, height, format, type, pixels));
|
||||
if (mode == RecordMode.COMPILE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final long pixels_buffer_offset = pixels != null ? MemoryUtilities.memAddress(pixels) : 0L;
|
||||
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_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4001,7 +4034,13 @@ public class GLStateManager {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw new UnsupportedOperationException("glTexSubImage2D with buffer offset in display lists not yet supported");
|
||||
}
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
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_buffer_offset);
|
||||
} else {
|
||||
// Non-main thread or proxy texture - use direct GL call
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {
|
||||
@@ -4460,7 +4499,6 @@ public class GLStateManager {
|
||||
boundVBO = buffer;
|
||||
} else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) {
|
||||
boundEBO = buffer;
|
||||
vaoEboMap.put(boundVAO, buffer);
|
||||
}
|
||||
GL15.glBindBuffer(target, buffer);
|
||||
}
|
||||
@@ -4476,7 +4514,6 @@ public class GLStateManager {
|
||||
}
|
||||
if (boundVAO != array) {
|
||||
boundVAO = array;
|
||||
boundEBO = vaoEboMap.get(array);
|
||||
UniversalVAO.bindVertexArray(array);
|
||||
if (ShaderManager.getInstance().isEnabled()) {
|
||||
ShaderManager.getInstance().onBindVertexArray(array);
|
||||
@@ -4486,11 +4523,9 @@ public class GLStateManager {
|
||||
|
||||
public static void glDeleteVertexArrays(int array) {
|
||||
ShaderManager.getInstance().onDeleteVertexArray(array);
|
||||
vaoEboMap.remove(array);
|
||||
if (array == boundVAO) {
|
||||
// Deleting the bound VAO implicitly unbinds it. Rebind the default VAO.
|
||||
boundVAO = defaultVAO;
|
||||
boundEBO = vaoEboMap.get(defaultVAO);
|
||||
UniversalVAO.bindVertexArray(defaultVAO);
|
||||
}
|
||||
UniversalVAO.deleteVertexArrays(array);
|
||||
@@ -4566,24 +4601,11 @@ public class GLStateManager {
|
||||
|
||||
public static void glMultiTexCoord2f(int target, float s, float t) {
|
||||
if (target == GL13.GL_TEXTURE0) {
|
||||
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
|
||||
ImmediateModeRecorder.setTexCoord(s, t);
|
||||
return;
|
||||
}
|
||||
ShaderManager.setCurrentTexCoord(s, t, 0.0f, 1.0f);
|
||||
dirtyTexCoordAttrib = true;
|
||||
} else if (target == GL13.GL_TEXTURE1) {
|
||||
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
|
||||
ImmediateModeRecorder.setLightmapCoord(s, t);
|
||||
return;
|
||||
}
|
||||
setLightmapTextureCoords(target, s, t);
|
||||
}
|
||||
// Units 2+ silently ignored — shader uses v_TexCoord0 for all non-lightmap units
|
||||
}
|
||||
|
||||
public static void glMultiTexCoord2d(int target, double s, double t) {
|
||||
glMultiTexCoord2f(target, (float) s, (float) t);
|
||||
}
|
||||
|
||||
public static void glMultiTexCoord2s(int target, short s, short t) {
|
||||
@@ -4656,6 +4678,8 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TEXENV_COMBINE_MSG = "Texture environment combine mode is not supported in FFP emulation.";
|
||||
|
||||
public static void glTexEnvi(int target, int pname, int param) {
|
||||
handleTexEnvScalar(target, pname, param);
|
||||
}
|
||||
@@ -4667,31 +4691,24 @@ public class GLStateManager {
|
||||
public static void glTexEnv(int target, int pname, FloatBuffer params) {
|
||||
if (target == GL11.GL_TEXTURE_ENV && pname == GL11.GL_TEXTURE_ENV_COLOR && params.remaining() >= 4) {
|
||||
final int pos = params.position();
|
||||
final var envState = textures.getTexEnvState(activeTextureUnit.getValue());
|
||||
envState.envColorR = params.get(pos);
|
||||
envState.envColorG = params.get(pos + 1);
|
||||
envState.envColorB = params.get(pos + 2);
|
||||
envState.envColorA = params.get(pos + 3);
|
||||
texEnvColor.setRed(params.get(pos));
|
||||
texEnvColor.setGreen(params.get(pos + 1));
|
||||
texEnvColor.setBlue(params.get(pos + 2));
|
||||
texEnvColor.setAlpha(params.get(pos + 3));
|
||||
fragmentGeneration++;
|
||||
return;
|
||||
}
|
||||
if (target == GL14.GL_TEXTURE_FILTER_CONTROL) {
|
||||
guardUnsupportedFFP("glTexEnv", "LOD bias via buffer glTexEnv is not supported; use glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value) instead.");
|
||||
return;
|
||||
}
|
||||
// Silently ignore other buffer glTexEnv calls (some mods pass combine params via buffers)
|
||||
guardTexEnvTarget(target, "glTexEnv");
|
||||
}
|
||||
|
||||
public static void glTexEnv(int target, int pname, IntBuffer params) {
|
||||
if (target == GL11.GL_TEXTURE_ENV) {
|
||||
handleTexEnvScalar(target, pname, (float) params.get(params.position()));
|
||||
}
|
||||
guardTexEnvTarget(target, "glTexEnv");
|
||||
}
|
||||
|
||||
/**
|
||||
* Scalar glTexEnvi/glTexEnvf dispatch. glTexEnv is removed in core profile.
|
||||
* GL_TEXTURE_FILTER_CONTROL (LOD bias) is remapped to glTexParameterf.
|
||||
* All GL_TEXTURE_ENV parameters including GL_COMBINE sub-params are tracked in per-unit TexEnvState.
|
||||
* GL_TEXTURE_ENV_MODE values (MODULATE, REPLACE, ADD, DECAL, BLEND) are tracked in cache.
|
||||
*/
|
||||
private static void handleTexEnvScalar(int target, int pname, float param) {
|
||||
if (target == GL14.GL_TEXTURE_FILTER_CONTROL) {
|
||||
@@ -4699,82 +4716,27 @@ public class GLStateManager {
|
||||
glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, param);
|
||||
return;
|
||||
}
|
||||
if (target != GL11.GL_TEXTURE_ENV) return;
|
||||
|
||||
final var envState = textures.getTexEnvState(activeTextureUnit.getValue());
|
||||
final int iparam = (int) param;
|
||||
|
||||
switch (pname) {
|
||||
case GL11.GL_TEXTURE_ENV_MODE -> {
|
||||
envState.mode = iparam;
|
||||
if (target == GL11.GL_TEXTURE_ENV && pname == GL11.GL_TEXTURE_ENV_MODE) {
|
||||
final int mode = (int) param;
|
||||
if (mode == GL11.GL_MODULATE || mode == GL11.GL_REPLACE
|
||||
|| mode == GL11.GL_ADD || mode == GL11.GL_DECAL || mode == GL11.GL_BLEND) {
|
||||
texEnvMode.setValue(mode);
|
||||
fragmentGeneration++;
|
||||
return;
|
||||
}
|
||||
case GL13.GL_COMBINE_RGB -> {
|
||||
envState.combineRgb = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_COMBINE_ALPHA -> {
|
||||
envState.combineAlpha = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_SOURCE0_RGB -> {
|
||||
envState.sourceRgb[0] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_SOURCE1_RGB -> {
|
||||
envState.sourceRgb[1] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_SOURCE2_RGB -> {
|
||||
envState.sourceRgb[2] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_SOURCE0_ALPHA -> {
|
||||
envState.sourceAlpha[0] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_SOURCE1_ALPHA -> {
|
||||
envState.sourceAlpha[1] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_SOURCE2_ALPHA -> {
|
||||
envState.sourceAlpha[2] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_OPERAND0_RGB -> {
|
||||
envState.operandRgb[0] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_OPERAND1_RGB -> {
|
||||
envState.operandRgb[1] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_OPERAND2_RGB -> {
|
||||
envState.operandRgb[2] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_OPERAND0_ALPHA -> {
|
||||
envState.operandAlpha[0] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_OPERAND1_ALPHA -> {
|
||||
envState.operandAlpha[1] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_OPERAND2_ALPHA -> {
|
||||
envState.operandAlpha[2] = iparam;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL13.GL_RGB_SCALE -> {
|
||||
envState.scaleRgb = param;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
case GL11.GL_ALPHA_SCALE -> {
|
||||
envState.scaleAlpha = param;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
// Silently ignore unknown pname — other mods may pass unrecognized params
|
||||
}
|
||||
guardUnsupportedFFP("glTexEnv", TEXENV_COMBINE_MSG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard for buffer glTexEnv variants. glTexEnv is removed in core profile.
|
||||
*/
|
||||
private static void guardTexEnvTarget(int target, String function) {
|
||||
if (target == GL14.GL_TEXTURE_FILTER_CONTROL) {
|
||||
guardUnsupportedFFP(function, "LOD bias via buffer glTexEnv is not supported; use glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value) instead.");
|
||||
return;
|
||||
}
|
||||
guardUnsupportedFFP(function, TEXENV_COMBINE_MSG);
|
||||
}
|
||||
|
||||
// TexGen generation counter for FFP uniform dirty tracking
|
||||
@@ -4871,31 +4833,6 @@ public class GLStateManager {
|
||||
guardUnsupportedFFP("glAccum", "The accumulation buffer is not available in GL 3.3 core profile.");
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
FeedbackManager.glFeedbackBuffer(type, buffer);
|
||||
}
|
||||
|
||||
public static int glRenderMode(int mode) {
|
||||
return FeedbackManager.glRenderMode(mode);
|
||||
}
|
||||
|
||||
public static void glPassThrough(float token) {
|
||||
FeedbackManager.glPassThrough(token);
|
||||
}
|
||||
|
||||
private static boolean selectWarned = false;
|
||||
public static void glSelectBuffer(IntBuffer buffer) {
|
||||
if (!selectWarned) {
|
||||
LOGGER.warn("glSelectBuffer: selection mode not emulated");
|
||||
selectWarned = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void glInitNames() { /* no-op: ignored outside GL_SELECT */ }
|
||||
public static void glPushName(int name) { /* no-op */ }
|
||||
public static void glPopName() { /* no-op */ }
|
||||
public static void glLoadName(int name) { /* no-op */ }
|
||||
|
||||
public static void glLinkProgram(int program) {
|
||||
if (ShaderManager.getInstance().isEnabled()) {
|
||||
generateVertexShaderIfNeeded(program);
|
||||
|
||||
@@ -84,13 +84,11 @@ public final class QuadConverter {
|
||||
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
|
||||
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
|
||||
final int quadCount = vertexCount / 4;
|
||||
final int prevEbo = GLStateManager.getBoundEBO();
|
||||
ensureCapacity(first / 4 + quadCount);
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
|
||||
final long indexOffset = (long) (first / 4) * 6 * 4;
|
||||
GL11.glDrawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
|
||||
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -147,10 +147,7 @@ public class RenderSystem {
|
||||
|
||||
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
@@ -215,20 +212,12 @@ public class RenderSystem {
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -16,26 +16,16 @@ public class DSAARB extends DSAUnsupported {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
|
||||
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
|
||||
// For simplicity, use the fallback which binds, uploads, and restores
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
|
||||
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,10 +8,8 @@ public interface DSAAccess {
|
||||
void generateMipmaps(int texture, int target);
|
||||
|
||||
// Texture image upload - DSA allows specifying texture directly without binding
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
|
||||
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset);
|
||||
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset);
|
||||
|
||||
void texParameteri(int texture, int target, int pname, int param);
|
||||
void texParameterf(int texture, int target, int pname, float param);
|
||||
|
||||
@@ -2,17 +2,9 @@ package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import org.lwjgl.opengl.EXTDirectStateAccess;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class DSAEXT extends DSACore {
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
EXTDirectStateAccess.glTextureImage2DEXT(texture, target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
@@ -24,36 +23,19 @@ public class DSAUnsupported implements DSAAccess {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels_buffer_offset);
|
||||
if(cachedBinding != texture) GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pixels_buffer_offset) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
|
||||
GL11.glBindTexture(target, texture);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GL11.glBindTexture(target, cachedBinding);
|
||||
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels_buffer_offset);
|
||||
if(cachedBinding != texture) GL11.glBindTexture(target, cachedBinding);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.github.bsideup.jabel.Desugar;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
|
||||
/**
|
||||
* DirectTessellator subclass for deferred particle batching. Accumulates vertex data from
|
||||
* multiple interceptDraw() calls, recording per-draw metadata as {@link DrawRange} entries.
|
||||
* Computes a delta modelview matrix at each intercept to pre-transform vertices.
|
||||
*/
|
||||
class DeferredBatchTessellator extends DirectTessellator {
|
||||
|
||||
@Desugar
|
||||
record DrawRange(long stateKey, int byteOffset, int byteLength, int vertexCount, int drawMode, int flags) {}
|
||||
|
||||
private final List<DrawRange> ranges = new ArrayList<>();
|
||||
private final Matrix4f defaultModelview = new Matrix4f();
|
||||
private final Matrix4f defaultModelviewInverse = new Matrix4f();
|
||||
private final Matrix4f deltaMatrix = new Matrix4f(); // reusable scratch
|
||||
private final Vector3f scratch = new Vector3f();
|
||||
private Matrix4fc currentTransform;
|
||||
|
||||
DeferredBatchTessellator(ByteBuffer buffer) {
|
||||
super(buffer, false); // deleteAfter=false: keep buffer across resets for reuse
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long writeVertexData(VertexFormat format, int[] rawBuffer, int rawBufferIndex) {
|
||||
if (currentTransform != null) {
|
||||
return format.writeToBuffer0(writePtr, rawBuffer, rawBufferIndex, currentTransform, scratch);
|
||||
}
|
||||
return super.writeVertexData(format, rawBuffer, rawBufferIndex);
|
||||
}
|
||||
|
||||
/** Snapshot the current modelview as the "default" for this bracket. */
|
||||
void snapshotDefaultModelview() {
|
||||
defaultModelview.set(GLStateManager.getModelViewMatrix());
|
||||
defaultModelview.invert(defaultModelviewInverse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int interceptDraw(Tessellator tess) {
|
||||
// Compute delta: if current MV differs from default, pre-transform vertices
|
||||
final Matrix4f currentMV = GLStateManager.getModelViewMatrix();
|
||||
if (!currentMV.equals(defaultModelview)) {
|
||||
defaultModelviewInverse.mul(currentMV, deltaMatrix);
|
||||
this.currentTransform = deltaMatrix;
|
||||
} else {
|
||||
this.currentTransform = null;
|
||||
}
|
||||
|
||||
final int byteOffsetBefore = bufferLimit();
|
||||
final int vertCount = tess.vertexCount;
|
||||
final int drawMode = tess.drawMode;
|
||||
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
|
||||
final long stateKey = DeferredDrawBatcher.captureStateKey();
|
||||
|
||||
final int result = super.interceptDraw(tess); // repacks int[] → ByteBuffer + transforms if vertexTransform set
|
||||
|
||||
final int byteOffsetAfter = bufferLimit();
|
||||
ranges.add(new DrawRange(stateKey, byteOffsetBefore, byteOffsetAfter - byteOffsetBefore, vertCount, drawMode, flags));
|
||||
this.currentTransform = null; // clear for next draw
|
||||
return result;
|
||||
}
|
||||
|
||||
List<DrawRange> getRanges() { return ranges; }
|
||||
|
||||
void clearRanges() { ranges.clear(); }
|
||||
|
||||
void copyRange(int byteOffset, int byteLength, long destAddress) {
|
||||
memCopy(startPtr + byteOffset, destAddress, byteLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemovedFromStack() {
|
||||
clearRanges();
|
||||
super.onRemovedFromStack(); // calls reset(), keeps baseBuffer (deleteAfter=false)
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
|
||||
/**
|
||||
* Deferred draw batching coordinator. During rendering regions (e.g. particles),
|
||||
* intercepts tessellator.draw() calls via DirectTessellator, captures pre-packed vertex data
|
||||
* + GL state, then flushes all captured batches sorted by state key.
|
||||
* <p>
|
||||
* Vertices are pre-transformed at capture time.
|
||||
*/
|
||||
public class DeferredDrawBatcher {
|
||||
|
||||
private static final int INITIAL_BUFFER_SIZE = 64 * 1024;
|
||||
|
||||
@Getter private static boolean active = false;
|
||||
private static DeferredBatchTessellator batchTessellator;
|
||||
|
||||
private DeferredDrawBatcher() {}
|
||||
|
||||
/**
|
||||
* Begin deferred capture mode. Called after startDrawingQuads().
|
||||
* Pushes a DeferredBatchTessellator onto the DirectTessellator stack so that
|
||||
* subsequent tessellator.draw() calls are intercepted and accumulated.
|
||||
*/
|
||||
public static void enter() {
|
||||
active = true;
|
||||
if (batchTessellator == null) {
|
||||
batchTessellator = new DeferredBatchTessellator(memAlloc(INITIAL_BUFFER_SIZE));
|
||||
}
|
||||
batchTessellator.clearRanges();
|
||||
batchTessellator.snapshotDefaultModelview();
|
||||
TessellatorManager.startCapturingDirect(batchTessellator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit deferred mode and flush all captured batches. Groups entries by state key
|
||||
* and vertex format, issues one draw per unique group. Restores the GL state that
|
||||
* was active for the last group so vanilla sees consistent state.
|
||||
*/
|
||||
public static void exitAndFlush() {
|
||||
active = false;
|
||||
final List<DeferredBatchTessellator.DrawRange> ranges = batchTessellator.getRanges();
|
||||
|
||||
if (!ranges.isEmpty()) {
|
||||
ranges.sort(Comparator.comparingLong(DeferredBatchTessellator.DrawRange::stateKey));
|
||||
flushSorted(ranges);
|
||||
}
|
||||
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
}
|
||||
|
||||
private static void flushSorted(List<DeferredBatchTessellator.DrawRange> ranges) {
|
||||
long currentKey = ranges.get(0).stateKey();
|
||||
int groupStart = 0;
|
||||
|
||||
for (int i = 0; i <= ranges.size(); i++) {
|
||||
final boolean endOfList = (i == ranges.size());
|
||||
final boolean keyChanged = !endOfList && ranges.get(i).stateKey() != currentKey;
|
||||
|
||||
if (endOfList || keyChanged) {
|
||||
applyStateKey(currentKey);
|
||||
flushGroup(ranges, groupStart, i);
|
||||
|
||||
if (!endOfList) {
|
||||
currentKey = ranges.get(i).stateKey();
|
||||
groupStart = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush a group of ranges sharing the same state key.
|
||||
* Subgroups by (drawMode, format flags) since those must match for merged draws.
|
||||
*/
|
||||
private static void flushGroup(List<DeferredBatchTessellator.DrawRange> ranges, int from, int to) {
|
||||
int i = from;
|
||||
while (i < to) {
|
||||
final DeferredBatchTessellator.DrawRange first = ranges.get(i);
|
||||
final int drawMode = first.drawMode();
|
||||
final int flags = first.flags();
|
||||
|
||||
int totalBytes = 0;
|
||||
int totalVertices = 0;
|
||||
int subEnd = i;
|
||||
|
||||
while (subEnd < to) {
|
||||
final DeferredBatchTessellator.DrawRange e = ranges.get(subEnd);
|
||||
if (e.drawMode() != drawMode || e.flags() != flags) break;
|
||||
totalBytes += e.byteLength();
|
||||
totalVertices += e.vertexCount();
|
||||
subEnd++;
|
||||
}
|
||||
|
||||
TessellatorStreamingDrawer.drawPackedBatch(
|
||||
batchTessellator, ranges, i, subEnd, totalBytes, totalVertices, drawMode, flags);
|
||||
|
||||
i = subEnd;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack current GLSM state into a long key for grouping.
|
||||
* Layout: [tex1En:1][tex0En:1][textureId:20][srcRgb:12][dstRgb:12][blendEnabled:1][depthMask:1] = 48 bits
|
||||
* GL blend constants (e.g. GL_SRC_ALPHA=0x0302) need 12 bits.
|
||||
*
|
||||
* NOT captured (uniform within a particle layer bracket — set once before startDrawingQuads,
|
||||
* not changed by individual particles):
|
||||
* - depth test enable (always GL_LEQUAL for particles)
|
||||
* - alpha test func/ref (set per-layer, not per-particle)
|
||||
* - separate blend alpha factors (srcAlpha/dstAlpha) — particles use glBlendFunc not glBlendFuncSeparate
|
||||
* - fog state (mode, color, start/end — constant for the frame)
|
||||
*/
|
||||
static long captureStateKey() {
|
||||
final int textureId = GLStateManager.getTextures().getTextureUnitBindings(0).getBinding();
|
||||
final int srcRgb = GLStateManager.getBlendState().getSrcRgb();
|
||||
final int dstRgb = GLStateManager.getBlendState().getDstRgb();
|
||||
final boolean blendEnabled = GLStateManager.getBlendMode().isEnabled();
|
||||
final boolean depthMask = GLStateManager.getDepthState().isEnabled();
|
||||
final boolean tex0Enabled = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
|
||||
final boolean tex1Enabled = GLStateManager.getTextures().getTextureUnitStates(1).isEnabled();
|
||||
|
||||
return packStateKey(textureId, srcRgb, dstRgb, blendEnabled, depthMask, tex0Enabled, tex1Enabled);
|
||||
}
|
||||
|
||||
static long packStateKey(int textureId, int srcRgb, int dstRgb, boolean blendEnabled, boolean depthMask, boolean tex0Enabled, boolean tex1Enabled) {
|
||||
return ((long) (textureId & 0xFFFFF) << 26)
|
||||
| ((long) (srcRgb & 0xFFF) << 14)
|
||||
| ((long) (dstRgb & 0xFFF) << 2)
|
||||
| (blendEnabled ? 2L : 0L)
|
||||
| (depthMask ? 1L : 0L)
|
||||
| (tex0Enabled ? (1L << 46) : 0L)
|
||||
| (tex1Enabled ? (1L << 47) : 0L);
|
||||
}
|
||||
|
||||
static int unpackTextureId(long key) { return (int) ((key >> 26) & 0xFFFFF); }
|
||||
static int unpackSrcRgb(long key) { return (int) ((key >> 14) & 0xFFF); }
|
||||
static int unpackDstRgb(long key) { return (int) ((key >> 2) & 0xFFF); }
|
||||
static boolean unpackBlendEnabled(long key) { return (key & 2L) != 0; }
|
||||
static boolean unpackDepthMask(long key) { return (key & 1L) != 0; }
|
||||
static boolean unpackTex0Enabled(long key) { return (key & (1L << 46)) != 0; }
|
||||
static boolean unpackTex1Enabled(long key) { return (key & (1L << 47)) != 0; }
|
||||
|
||||
static void applyStateKey(long key) {
|
||||
final int textureId = unpackTextureId(key);
|
||||
final int srcRgb = unpackSrcRgb(key);
|
||||
final int dstRgb = unpackDstRgb(key);
|
||||
final boolean blendEnabled = unpackBlendEnabled(key);
|
||||
final boolean depthMask = unpackDepthMask(key);
|
||||
final boolean tex0Enabled = unpackTex0Enabled(key);
|
||||
final boolean tex1Enabled = unpackTex1Enabled(key);
|
||||
|
||||
GLStateManager.getTextures().getTextureUnitStates(0).setEnabled(tex0Enabled);
|
||||
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(tex1Enabled);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureId);
|
||||
GLStateManager.glBlendFunc(srcRgb, dstRgb);
|
||||
if (blendEnabled) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.glDepthMask(depthMask);
|
||||
}
|
||||
}
|
||||
@@ -1,337 +1,133 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
/**
|
||||
* Packed-long fragment shader permutation key for FFP emulation.
|
||||
*
|
||||
* Layout:
|
||||
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
|
||||
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
|
||||
* Only max(1, nrEnabledUnits) longs are significant.
|
||||
*
|
||||
* Per-unit packing (47 bits):
|
||||
* 0: enabled
|
||||
* 1-3: mode (MODULATE=0..COMBINE=5)
|
||||
* 4: needsEnvColor
|
||||
* 5-8: combineRgb (encoded 0-7)
|
||||
* 9-12: combineAlpha (encoded 0-7)
|
||||
* 13-14: scaleShiftRgb
|
||||
* 15-16: scaleShiftAlpha
|
||||
* 17-21: arg0 RGB (source:3 + operand:2)
|
||||
* 22-26: arg1 RGB
|
||||
* 27-31: arg2 RGB
|
||||
* 32-36: arg0 Alpha
|
||||
* 37-41: arg1 Alpha
|
||||
* 42-46: arg2 Alpha
|
||||
* Compact key capturing the fragment shader permutation axes for FFP emulation.
|
||||
* Packed into a single long for fast hash/compare.
|
||||
*/
|
||||
public final class FragmentKey {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FragmentKey");
|
||||
|
||||
public static final int TEX_ENV_MODULATE = 0;
|
||||
public static final int TEX_ENV_REPLACE = 1;
|
||||
public static final int TEX_ENV_ADD = 2;
|
||||
public static final int TEX_ENV_DECAL = 3;
|
||||
public static final int TEX_ENV_BLEND = 4;
|
||||
public static final int TEX_ENV_COMBINE = 5;
|
||||
private final long packed;
|
||||
|
||||
public static final int COMBINE_REPLACE = 0;
|
||||
public static final int COMBINE_MODULATE = 1;
|
||||
public static final int COMBINE_ADD = 2;
|
||||
public static final int COMBINE_ADD_SIGNED = 3;
|
||||
public static final int COMBINE_INTERPOLATE = 4;
|
||||
public static final int COMBINE_SUBTRACT = 5;
|
||||
public static final int COMBINE_DOT3_RGB = 6;
|
||||
public static final int COMBINE_DOT3_RGBA = 7;
|
||||
|
||||
public static final int SRC_TEXTURE = 0;
|
||||
public static final int SRC_CONSTANT = 1;
|
||||
public static final int SRC_PRIMARY_COLOR = 2;
|
||||
public static final int SRC_PREVIOUS = 3;
|
||||
|
||||
public static final int OP_SRC_COLOR = 0;
|
||||
public static final int OP_ONE_MINUS_SRC_COLOR = 1;
|
||||
public static final int OP_SRC_ALPHA = 2;
|
||||
public static final int OP_ONE_MINUS_SRC_ALPHA = 3;
|
||||
private static final int BIT_FOG_MODE = 0;
|
||||
private static final int BIT_ALPHA_TEST = 2;
|
||||
private static final int BIT_ALPHA_FUNC = 3;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_TEXTURE = 7;
|
||||
private static final int BIT_LIGHTMAP = 8;
|
||||
private static final int BIT_TEX_ENV_MODE = 9; // 3 bits: 0=MODULATE, 1=REPLACE, 2=ADD, 3=DECAL, 4=BLEND
|
||||
|
||||
public static final int FOG_NONE = 0;
|
||||
public static final int FOG_LINEAR = 1;
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private static final int GLOBAL_BITS = 10;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
|
||||
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
|
||||
|
||||
private static final int U_ENABLED = 0;
|
||||
private static final int U_MODE = 1; // 3 bits
|
||||
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
|
||||
private static final int U_COMBINE_RGB = 5; // 4 bits
|
||||
private static final int U_COMBINE_ALPHA = 9; // 4 bits
|
||||
private static final int U_SCALE_RGB = 13; // 2 bits
|
||||
private static final int U_SCALE_ALPHA = 15; // 2 bits
|
||||
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
|
||||
private static final int U_ARG1_RGB = 22; // 5 bits
|
||||
private static final int U_ARG2_RGB = 27; // 5 bits
|
||||
private static final int U_ARG0_ALPHA = 32; // 5 bits
|
||||
private static final int U_ARG1_ALPHA = 37; // 5 bits
|
||||
private static final int U_ARG2_ALPHA = 42; // 5 bits
|
||||
|
||||
static final int MAX_UNITS = 4;
|
||||
|
||||
private final long[] packed;
|
||||
private final int hash;
|
||||
|
||||
private FragmentKey(long[] packed, int hash) {
|
||||
private FragmentKey(long packed) {
|
||||
this.packed = packed;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack current GLSM fragment state into pre-allocated output array.
|
||||
* @param out must be at least MAX_UNITS (4) longs
|
||||
* @return number of significant longs (1..4)
|
||||
*/
|
||||
public static int packFromState(long[] out) {
|
||||
long global = 0;
|
||||
public long pack() { return packed; }
|
||||
|
||||
public int fogMode() { return (int)(packed & 0x3); }
|
||||
public boolean alphaTestEnabled() { return ((packed >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
public int alphaTestFunc() { return (int)((packed >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public boolean textureEnabled() { return ((packed >> BIT_TEXTURE) & 1) != 0; }
|
||||
public boolean lightmapEnabled() { return ((packed >> BIT_LIGHTMAP) & 1) != 0; }
|
||||
public int texEnvMode() { return (int)((packed >> BIT_TEX_ENV_MODE) & 0x7); }
|
||||
|
||||
public static final int TEX_ENV_MODULATE = 0;
|
||||
public static final int TEX_ENV_REPLACE = 1;
|
||||
public static final int TEX_ENV_ADD = 2;
|
||||
public static final int TEX_ENV_DECAL = 3;
|
||||
public static final int TEX_ENV_BLEND = 4;
|
||||
|
||||
public static long packFromState() {
|
||||
long bits = 0;
|
||||
|
||||
// Fog
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
|
||||
final int glFogMode = GLStateManager.getFogState().getFogMode();
|
||||
final int fogMode = switch (glFogMode) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
default -> FOG_NONE;
|
||||
};
|
||||
global |= (long) fogMode;
|
||||
bits |= ((long) fogMode & 0x3);
|
||||
}
|
||||
|
||||
// Alpha test
|
||||
if (GLStateManager.getAlphaTest().isEnabled()) {
|
||||
global |= (1L << BIT_ALPHA_TEST);
|
||||
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
|
||||
bits |= (1L << BIT_ALPHA_TEST);
|
||||
final int func = encodeAlphaFunc(GLStateManager.getAlphaState().getFunction());
|
||||
bits |= ((long) func & 0x7) << BIT_ALPHA_FUNC;
|
||||
}
|
||||
|
||||
// Separate specular
|
||||
if (GLStateManager.getLightingState().isEnabled()
|
||||
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
global |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
bits |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
|
||||
// Per-unit state
|
||||
int highestEnabled = -1;
|
||||
long unit0Bits = 0;
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
|
||||
if (texEnabled) highestEnabled = i;
|
||||
// Texture unit 0
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
|
||||
bits |= (1L << BIT_TEXTURE);
|
||||
|
||||
final long unitBits = packUnit(i, texEnabled);
|
||||
if (i == 0) {
|
||||
unit0Bits = unitBits;
|
||||
} else {
|
||||
out[i] = unitBits;
|
||||
}
|
||||
// Tex env mode only matters when texture is enabled
|
||||
final int envMode = encodeTexEnvMode(GLStateManager.getTexEnvMode().getValue());
|
||||
bits |= ((long) envMode & 0x7) << BIT_TEX_ENV_MODE;
|
||||
}
|
||||
|
||||
final int nrEnabled = highestEnabled + 1;
|
||||
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
|
||||
global |= (unit0Bits << GLOBAL_BITS);
|
||||
out[0] = global;
|
||||
|
||||
return Math.max(1, nrEnabled);
|
||||
}
|
||||
|
||||
private static long packUnit(int unitIndex, boolean texEnabled) {
|
||||
if (!texEnabled) return 0;
|
||||
|
||||
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
|
||||
final int mode = encodeTexEnvMode(envState.mode);
|
||||
|
||||
long bits = 1L; // enabled
|
||||
bits |= ((long) (mode & 0x7)) << U_MODE;
|
||||
|
||||
boolean needsEnvColor = (mode == TEX_ENV_BLEND);
|
||||
|
||||
if (mode == TEX_ENV_COMBINE) {
|
||||
final int combRgb = encodeCombineFunc(envState.combineRgb);
|
||||
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
|
||||
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
|
||||
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
|
||||
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
|
||||
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
|
||||
|
||||
// Only pack args actually used by each channel's combine function.
|
||||
// Unused arg slots stay 0, preventing TexEnvState defaults from inflating the cache.
|
||||
final int numArgsRgb = combineNumArgs(combRgb);
|
||||
final int numArgsAlpha = combineNumArgs(combAlpha);
|
||||
final int maxArgs = Math.max(numArgsRgb, numArgsAlpha);
|
||||
|
||||
for (int a = 0; a < maxArgs; a++) {
|
||||
if (a < numArgsRgb) {
|
||||
final int srcR = encodeSource(envState.sourceRgb[a]);
|
||||
final int opR = encodeOperand(envState.operandRgb[a]);
|
||||
bits |= ((long) ((srcR & 0x7) | ((opR & 0x3) << 3))) << (U_ARG0_RGB + a * 5);
|
||||
if (srcR == SRC_CONSTANT) needsEnvColor = true;
|
||||
}
|
||||
if (a < numArgsAlpha) {
|
||||
final int srcA = encodeSource(envState.sourceAlpha[a]);
|
||||
final int opA = encodeOperand(envState.operandAlpha[a]);
|
||||
bits |= ((long) ((srcA & 0x7) | ((opA & 0x3) << 3))) << (U_ARG0_ALPHA + a * 5);
|
||||
if (srcA == SRC_CONSTANT) needsEnvColor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsEnvColor) {
|
||||
bits |= (1L << U_NEEDS_ENV_COLOR);
|
||||
// Texture unit 1 (lightmap)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHTMAP);
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
/** Create FragmentKey from packed scratch data. Only called on cache miss. */
|
||||
public static FragmentKey fromPacked(long[] scratch, int len) {
|
||||
final long[] copy = Arrays.copyOf(scratch, len);
|
||||
return new FragmentKey(copy, Arrays.hashCode(copy));
|
||||
}
|
||||
|
||||
/** Convenience: pack + create. Allocates. For infrequent paths. */
|
||||
public static FragmentKey fromState() {
|
||||
final long[] scratch = new long[MAX_UNITS];
|
||||
final int len = packFromState(scratch);
|
||||
return fromPacked(scratch, len);
|
||||
return new FragmentKey(packFromState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof FragmentKey other)) return false;
|
||||
if (hash != other.hash) return false;
|
||||
return Arrays.equals(packed, other.packed);
|
||||
}
|
||||
|
||||
public int fogMode() { return (int) (packed[0] & 0x3); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
|
||||
private long unitBits(int i) {
|
||||
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
|
||||
}
|
||||
|
||||
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
|
||||
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
|
||||
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
|
||||
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
|
||||
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
|
||||
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
|
||||
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
|
||||
|
||||
public int unitSourceRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5)) & 0x7); }
|
||||
public int unitOperandRgb(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_RGB + arg * 5 + 3)) & 0x3); }
|
||||
public int unitSourceAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5)) & 0x7); }
|
||||
public int unitOperandAlpha(int i, int arg) { return (int) ((unitBits(i) >> (U_ARG0_ALPHA + arg * 5 + 3)) & 0x3); }
|
||||
|
||||
public boolean textureEnabled() {
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (unitEnabled(i)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean lightmapEnabled() {
|
||||
return nrEnabledUnits() > 1 && unitEnabled(1);
|
||||
static FragmentKey fromPacked(long packed) {
|
||||
return new FragmentKey(packed);
|
||||
}
|
||||
|
||||
private static int encodeAlphaFunc(int glFunc) {
|
||||
return glFunc & 0x7;
|
||||
}
|
||||
|
||||
static int encodeTexEnvMode(int glMode) {
|
||||
private static int encodeTexEnvMode(int glMode) {
|
||||
return switch (glMode) {
|
||||
case GL11.GL_MODULATE -> TEX_ENV_MODULATE;
|
||||
case GL11.GL_REPLACE -> TEX_ENV_REPLACE;
|
||||
case GL11.GL_ADD -> TEX_ENV_ADD;
|
||||
case GL11.GL_DECAL -> TEX_ENV_DECAL;
|
||||
case GL11.GL_BLEND -> TEX_ENV_BLEND;
|
||||
case GL13.GL_COMBINE -> TEX_ENV_COMBINE;
|
||||
default -> TEX_ENV_MODULATE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeCombineFunc(int glFunc) {
|
||||
return switch (glFunc) {
|
||||
case GL11.GL_REPLACE -> COMBINE_REPLACE;
|
||||
case GL11.GL_MODULATE -> COMBINE_MODULATE;
|
||||
case GL11.GL_ADD -> COMBINE_ADD;
|
||||
case GL13.GL_ADD_SIGNED -> COMBINE_ADD_SIGNED;
|
||||
case GL13.GL_INTERPOLATE -> COMBINE_INTERPOLATE;
|
||||
case GL13.GL_SUBTRACT -> COMBINE_SUBTRACT;
|
||||
case GL13.GL_DOT3_RGB -> COMBINE_DOT3_RGB;
|
||||
case GL13.GL_DOT3_RGBA -> COMBINE_DOT3_RGBA;
|
||||
default -> COMBINE_REPLACE;
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeSource(int glSource) {
|
||||
return switch (glSource) {
|
||||
case GL11.GL_TEXTURE -> SRC_TEXTURE;
|
||||
case GL13.GL_CONSTANT -> SRC_CONSTANT;
|
||||
case GL13.GL_PRIMARY_COLOR -> SRC_PRIMARY_COLOR;
|
||||
case GL13.GL_PREVIOUS -> SRC_PREVIOUS;
|
||||
default -> {
|
||||
LOGGER.debug("Unknown TexEnv source 0x{}, falling back to SRC_PREVIOUS", Integer.toHexString(glSource));
|
||||
yield SRC_PREVIOUS;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static int encodeOperand(int glOperand) {
|
||||
return switch (glOperand) {
|
||||
case GL11.GL_SRC_COLOR -> OP_SRC_COLOR;
|
||||
case GL11.GL_ONE_MINUS_SRC_COLOR -> OP_ONE_MINUS_SRC_COLOR;
|
||||
case GL11.GL_SRC_ALPHA -> OP_SRC_ALPHA;
|
||||
case GL11.GL_ONE_MINUS_SRC_ALPHA -> OP_ONE_MINUS_SRC_ALPHA;
|
||||
default -> OP_SRC_COLOR;
|
||||
};
|
||||
}
|
||||
|
||||
/** Number of source/operand args used by the given (encoded) combine function. */
|
||||
public static int combineNumArgs(int func) {
|
||||
return switch (func) {
|
||||
case COMBINE_REPLACE -> 1;
|
||||
case COMBINE_INTERPOLATE -> 3;
|
||||
default -> 2; // MODULATE, ADD, ADD_SIGNED, SUBTRACT, DOT3_*
|
||||
};
|
||||
}
|
||||
|
||||
private static int encodeScale(float scale) {
|
||||
if (scale >= 4.0f) return 2;
|
||||
if (scale >= 2.0f) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int decodeAlphaFunc(int encoded) {
|
||||
return 0x0200 | encoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Long.hashCode(packed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof FragmentKey other)) return false;
|
||||
return packed == other.packed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String fogName = switch (fogMode()) {
|
||||
@@ -341,28 +137,17 @@ public final class FragmentKey {
|
||||
case FOG_EXP2 -> "EXP2";
|
||||
default -> "?";
|
||||
};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
|
||||
fogName, alphaTestEnabled(),
|
||||
final String envName = switch (texEnvMode()) {
|
||||
case TEX_ENV_MODULATE -> "MOD";
|
||||
case TEX_ENV_REPLACE -> "REPL";
|
||||
case TEX_ENV_ADD -> "ADD";
|
||||
case TEX_ENV_DECAL -> "DEC";
|
||||
case TEX_ENV_BLEND -> "BL";
|
||||
default -> "?";
|
||||
};
|
||||
return String.format("FFPFragmentKey[0x%03X: fog=%s alpha=%b(%s) specSep=%b tex=%b(env=%s) lm=%b]",
|
||||
packed, fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
|
||||
separateSpecular(), nrEnabledUnits()));
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
} else {
|
||||
final String modeName = switch (unitMode(i)) {
|
||||
case TEX_ENV_MODULATE -> "MOD";
|
||||
case TEX_ENV_REPLACE -> "REPL";
|
||||
case TEX_ENV_ADD -> "ADD";
|
||||
case TEX_ENV_DECAL -> "DEC";
|
||||
case TEX_ENV_BLEND -> "BL";
|
||||
case TEX_ENV_COMBINE -> "COMB";
|
||||
default -> "?";
|
||||
};
|
||||
sb.append(String.format(" u%d=%s", i, modeName));
|
||||
}
|
||||
}
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
separateSpecular(), textureEnabled(), envName, lightmapEnabled());
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core fragment shaders for FFP emulation.
|
||||
* Supports per-unit texenv chain including GL_COMBINE mode.
|
||||
* Follows Mesa's ff_fragment_shader.c and st_nir_lower_fog.c.
|
||||
*/
|
||||
public final class FragmentShaderGenerator {
|
||||
@@ -12,7 +11,7 @@ public final class FragmentShaderGenerator {
|
||||
private FragmentShaderGenerator() {}
|
||||
|
||||
public static String generate(FragmentKey key) {
|
||||
final StringBuilder sb = new StringBuilder(2048);
|
||||
final StringBuilder sb = new StringBuilder(1024);
|
||||
sb.append("#version 330 core\n\n");
|
||||
|
||||
emitInputs(sb, key);
|
||||
@@ -21,7 +20,7 @@ public final class FragmentShaderGenerator {
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitTextureSampling(sb, key);
|
||||
emitTexEnvChain(sb, key);
|
||||
emitColorCombine(sb, key);
|
||||
emitSpecularAdd(sb, key);
|
||||
emitAlphaTest(sb, key);
|
||||
emitFog(sb, key);
|
||||
@@ -50,20 +49,17 @@ public final class FragmentShaderGenerator {
|
||||
}
|
||||
|
||||
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
|
||||
// Samplers for each enabled unit
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (key.unitEnabled(i)) {
|
||||
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
|
||||
}
|
||||
if (key.textureEnabled()) {
|
||||
sb.append("uniform sampler2D u_Sampler0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("uniform sampler2D u_Sampler1;\n");
|
||||
}
|
||||
if (key.alphaTestEnabled()) {
|
||||
sb.append("uniform float u_AlphaRef;\n");
|
||||
}
|
||||
// Per-unit texEnvColor uniforms
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (key.unitEnabled(i) && key.unitNeedsEnvColor(i)) {
|
||||
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
|
||||
}
|
||||
if (key.textureEnabled() && key.texEnvMode() == FragmentKey.TEX_ENV_BLEND) {
|
||||
sb.append("uniform vec4 u_TexEnvColor;\n");
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("uniform vec4 u_FogParams;\n");
|
||||
@@ -73,191 +69,46 @@ public final class FragmentShaderGenerator {
|
||||
}
|
||||
|
||||
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
// Unit 1 is the lightmap — uses v_TexCoord1.
|
||||
// Units 2-3 intentionally share unit 0's texture coordinates since
|
||||
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
|
||||
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
|
||||
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
|
||||
if (key.textureEnabled()) {
|
||||
sb.append(" vec4 texColor = texture(u_Sampler0, v_TexCoord0.st);\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" vec4 lightmapColor = texture(u_Sampler1, v_TexCoord1.st);\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitTexEnvChain(StringBuilder sb, FragmentKey key) {
|
||||
private static void emitColorCombine(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.textureEnabled()) {
|
||||
sb.append(" // No texture — vertex color only\n");
|
||||
sb.append(" vec4 color = v_Color;\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process units in order; "previous" starts as vertex color
|
||||
boolean firstUnit = true;
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
|
||||
final String texVar = "tex" + i + "Color";
|
||||
final String envColorVar = "u_TexEnvColor" + i;
|
||||
final String prevVar = firstUnit ? "v_Color" : "color";
|
||||
final String assign = firstUnit ? " vec4 color = " : " color = ";
|
||||
|
||||
if (key.unitMode(i) == FragmentKey.TEX_ENV_COMBINE) {
|
||||
emitCombineUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
|
||||
} else {
|
||||
emitSimpleUnit(sb, key, i, texVar, envColorVar, prevVar, assign);
|
||||
}
|
||||
firstUnit = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
switch (key.unitMode(unit)) {
|
||||
case FragmentKey.TEX_ENV_REPLACE -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (REPLACE)\n");
|
||||
sb.append(assign).append(texVar).append(";\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_ADD -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (ADD)\n");
|
||||
sb.append(assign).append("vec4(clamp(").append(prevVar).append(".rgb + ").append(texVar).append(".rgb, 0.0, 1.0), ")
|
||||
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_DECAL -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (DECAL)\n");
|
||||
sb.append(assign).append("vec4(mix(").append(prevVar).append(".rgb, ").append(texVar).append(".rgb, ").append(texVar).append(".a), ")
|
||||
.append(prevVar).append(".a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_BLEND -> {
|
||||
sb.append(" // Unit ").append(unit).append(" (BLEND)\n");
|
||||
sb.append(assign).append("vec4(").append(prevVar).append(".rgb * (vec3(1.0) - ").append(texVar).append(".rgb) + ")
|
||||
.append(envColorVar).append(".rgb * ").append(texVar).append(".rgb, ")
|
||||
.append(prevVar).append(".a * ").append(texVar).append(".a);\n");
|
||||
}
|
||||
default -> { // MODULATE
|
||||
sb.append(" // Unit ").append(unit).append(" (MODULATE)\n");
|
||||
sb.append(assign).append(prevVar).append(" * ").append(texVar).append(";\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Emit a GL_COMBINE texenv unit */
|
||||
private static void emitCombineUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
sb.append(" // Unit ").append(unit).append(" (COMBINE)\n");
|
||||
|
||||
final String rgbResult = "combRgb" + unit;
|
||||
final String alphaResult = "combAlpha" + unit;
|
||||
|
||||
if (key.unitCombineRgb(unit) == FragmentKey.COMBINE_DOT3_RGBA) {
|
||||
sb.append(" vec3 ").append(rgbResult).append(";\n");
|
||||
sb.append(" {\n");
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
|
||||
sb.append(" }\n");
|
||||
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(rgbResult).append(".x);\n");
|
||||
} else {
|
||||
sb.append(" vec3 ").append(rgbResult).append(";\n");
|
||||
sb.append(" float ").append(alphaResult).append(";\n");
|
||||
sb.append(" {\n");
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, true);
|
||||
emitCombineChannel(sb, key, unit, texVar, envColorVar, prevVar, false);
|
||||
sb.append(" }\n");
|
||||
sb.append(assign).append("vec4(").append(rgbResult).append(", ").append(alphaResult).append(");\n");
|
||||
final int envMode = key.texEnvMode();
|
||||
switch (envMode) {
|
||||
case FragmentKey.TEX_ENV_REPLACE -> {
|
||||
sb.append(" // Color combine (REPLACE)\n");
|
||||
sb.append(" vec4 color = texColor;\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_ADD -> {
|
||||
sb.append(" // Color combine (ADD)\n");
|
||||
sb.append(" vec4 color = vec4(clamp(v_Color.rgb + texColor.rgb, 0.0, 1.0), v_Color.a * texColor.a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_DECAL -> {
|
||||
sb.append(" // Color combine (DECAL)\n");
|
||||
sb.append(" vec4 color = vec4(mix(v_Color.rgb, texColor.rgb, texColor.a), v_Color.a);\n");
|
||||
}
|
||||
case FragmentKey.TEX_ENV_BLEND -> {
|
||||
sb.append(" // Color combine (BLEND)\n");
|
||||
sb.append(" vec4 color = vec4(v_Color.rgb * (vec3(1.0) - texColor.rgb) + u_TexEnvColor.rgb * texColor.rgb, v_Color.a * texColor.a);\n");
|
||||
}
|
||||
default -> {
|
||||
sb.append(" // Color combine (MODULATE)\n");
|
||||
sb.append(" vec4 color = v_Color * texColor;\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit one channel (RGB or Alpha) of a GL_COMBINE unit.
|
||||
*/
|
||||
private static void emitCombineChannel(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, boolean isRgb) {
|
||||
final String suffix = isRgb ? "Rgb" : "Alpha";
|
||||
final String type = isRgb ? "vec3" : "float";
|
||||
final int combineFunc = isRgb ? key.unitCombineRgb(unit) : key.unitCombineAlpha(unit);
|
||||
final int scaleShift = isRgb ? key.unitScaleShiftRgb(unit) : key.unitScaleShiftAlpha(unit);
|
||||
|
||||
// Guard: DOT3 is invalid for alpha channel — fallback to REPLACE
|
||||
final int effectiveFunc;
|
||||
if (!isRgb && (combineFunc == FragmentKey.COMBINE_DOT3_RGB || combineFunc == FragmentKey.COMBINE_DOT3_RGBA)) {
|
||||
effectiveFunc = FragmentKey.COMBINE_REPLACE;
|
||||
} else {
|
||||
effectiveFunc = combineFunc;
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" color *= lightmapColor;\n");
|
||||
}
|
||||
|
||||
// Determine how many args we need
|
||||
final int numArgs = combineNumArgs(effectiveFunc);
|
||||
|
||||
// Emit argument resolution
|
||||
for (int a = 0; a < numArgs; a++) {
|
||||
final int source = isRgb ? key.unitSourceRgb(unit, a) : key.unitSourceAlpha(unit, a);
|
||||
final int operand = isRgb ? key.unitOperandRgb(unit, a) : key.unitOperandAlpha(unit, a);
|
||||
final String argName = "arg" + suffix + a + "_" + unit;
|
||||
final String sourceExpr = resolveSource(source, texVar, envColorVar, prevVar);
|
||||
final String operandExpr = applyOperand(sourceExpr, operand, isRgb);
|
||||
sb.append(" ").append(type).append(" ").append(argName).append(" = ").append(operandExpr).append(";\n");
|
||||
}
|
||||
|
||||
// Emit combine operation
|
||||
final String resultName = "comb" + suffix + unit;
|
||||
final String a0 = "arg" + suffix + "0_" + unit;
|
||||
final String a1 = "arg" + suffix + "1_" + unit;
|
||||
final String a2 = "arg" + suffix + "2_" + unit;
|
||||
|
||||
sb.append(" ").append(resultName).append(" = ");
|
||||
switch (effectiveFunc) {
|
||||
case FragmentKey.COMBINE_REPLACE -> sb.append(a0);
|
||||
case FragmentKey.COMBINE_MODULATE -> sb.append(a0).append(" * ").append(a1);
|
||||
case FragmentKey.COMBINE_ADD -> sb.append(a0).append(" + ").append(a1);
|
||||
case FragmentKey.COMBINE_ADD_SIGNED ->
|
||||
sb.append(a0).append(" + ").append(a1).append(" - ").append(isRgb ? "vec3(0.5)" : "0.5");
|
||||
case FragmentKey.COMBINE_SUBTRACT -> sb.append(a0).append(" - ").append(a1);
|
||||
case FragmentKey.COMBINE_INTERPOLATE ->
|
||||
sb.append("mix(").append(a1).append(", ").append(a0).append(", ").append(a2).append(")");
|
||||
case FragmentKey.COMBINE_DOT3_RGB, FragmentKey.COMBINE_DOT3_RGBA ->
|
||||
sb.append("vec3(4.0 * dot(").append(a0).append(" - vec3(0.5), ").append(a1).append(" - vec3(0.5)))");
|
||||
default -> sb.append(a0); // fallback to REPLACE
|
||||
}
|
||||
sb.append(";\n");
|
||||
|
||||
// Apply scale
|
||||
if (scaleShift > 0) {
|
||||
final String scaleFactor = scaleShift == 1 ? "2.0" : "4.0";
|
||||
sb.append(" ").append(resultName).append(" *= ").append(scaleFactor).append(";\n");
|
||||
}
|
||||
|
||||
// Clamp
|
||||
sb.append(" ").append(resultName).append(" = clamp(").append(resultName).append(", ")
|
||||
.append(isRgb ? "vec3(0.0), vec3(1.0)" : "0.0, 1.0").append(");\n");
|
||||
}
|
||||
|
||||
private static String resolveSource(int source, String texVar, String envColorVar, String prevVar) {
|
||||
return switch (source) {
|
||||
case FragmentKey.SRC_TEXTURE -> texVar;
|
||||
case FragmentKey.SRC_CONSTANT -> envColorVar;
|
||||
case FragmentKey.SRC_PRIMARY_COLOR -> "v_Color";
|
||||
case FragmentKey.SRC_PREVIOUS -> prevVar;
|
||||
default -> prevVar;
|
||||
};
|
||||
}
|
||||
|
||||
private static String applyOperand(String sourceExpr, int operand, boolean isRgb) {
|
||||
if (isRgb) {
|
||||
return switch (operand) {
|
||||
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".rgb";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(vec3(1.0) - " + sourceExpr + ".rgb)";
|
||||
case FragmentKey.OP_SRC_ALPHA -> "vec3(" + sourceExpr + ".a)";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "vec3(1.0 - " + sourceExpr + ".a)";
|
||||
default -> sourceExpr + ".rgb";
|
||||
};
|
||||
} else {
|
||||
return switch (operand) {
|
||||
case FragmentKey.OP_SRC_ALPHA -> sourceExpr + ".a";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_ALPHA -> "(1.0 - " + sourceExpr + ".a)";
|
||||
// Per GL spec, alpha channel operands are SRC_ALPHA or ONE_MINUS_SRC_ALPHA only, but handle color operands gracefully
|
||||
case FragmentKey.OP_SRC_COLOR -> sourceExpr + ".a";
|
||||
case FragmentKey.OP_ONE_MINUS_SRC_COLOR -> "(1.0 - " + sourceExpr + ".a)";
|
||||
default -> sourceExpr + ".a";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static int combineNumArgs(int func) {
|
||||
return FragmentKey.combineNumArgs(func);
|
||||
}
|
||||
|
||||
private static void emitSpecularAdd(StringBuilder sb, FragmentKey key) {
|
||||
|
||||
@@ -10,15 +10,12 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* A compiled and linked FFP emulation program (vertex + fragment shader pair). Owns the GL program handle and caches uniform locations.
|
||||
*/
|
||||
public class Program {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPProgram");
|
||||
private static final AtomicInteger PROGRAM_COUNTER = new AtomicInteger();
|
||||
|
||||
@Getter private final int programId;
|
||||
@Getter private final VertexKey vertexKey;
|
||||
@@ -83,9 +80,10 @@ public class Program {
|
||||
public int locClipPlanes = -1;
|
||||
|
||||
// Fragment uniforms
|
||||
public final int[] locSampler = { -1, -1, -1, -1 };
|
||||
public int locSampler0 = -1;
|
||||
public int locSampler1 = -1;
|
||||
public int locAlphaRef = -1;
|
||||
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
|
||||
public int locTexEnvColor = -1;
|
||||
public int locFogParams = -1;
|
||||
public int locFogColor = -1;
|
||||
|
||||
@@ -152,13 +150,10 @@ public class Program {
|
||||
locClipPlanes = loc("u_ClipPlane[0]");
|
||||
|
||||
// Fragment
|
||||
for (int i = 0; i < 4; i++) {
|
||||
locSampler[i] = loc("u_Sampler" + i);
|
||||
}
|
||||
locSampler0 = loc("u_Sampler0");
|
||||
locSampler1 = loc("u_Sampler1");
|
||||
locAlphaRef = loc("u_AlphaRef");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
|
||||
}
|
||||
locTexEnvColor = loc("u_TexEnvColor");
|
||||
locFogParams = loc("u_FogParams");
|
||||
locFogColor = loc("u_FogColor");
|
||||
}
|
||||
@@ -175,9 +170,8 @@ public class Program {
|
||||
* Compile a shader, link it into a program, and return the FFPProgram.
|
||||
*/
|
||||
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc) {
|
||||
final int id = PROGRAM_COUNTER.getAndIncrement();
|
||||
final int vs = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + Long.toHexString(vk.pack()));
|
||||
final int fs = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
|
||||
final int fs = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + Long.toHexString(fk.pack()));
|
||||
|
||||
final int program = GL20.glCreateProgram();
|
||||
GL20.glAttachShader(program, vs);
|
||||
@@ -186,7 +180,7 @@ public class Program {
|
||||
|
||||
final String log = RenderSystem.getProgramInfoLog(program);
|
||||
if (!log.isEmpty()) {
|
||||
LOGGER.warn("FFP program link log (vk=0x{}, fk={}): {}", Long.toHexString(vk.pack()), fk, log);
|
||||
LOGGER.warn("FFP program link log (vk=0x{}, fk=0x{}): {}", Long.toHexString(vk.pack()), Long.toHexString(fk.pack()), log);
|
||||
}
|
||||
|
||||
final int linkStatus = GL20.glGetProgrami(program, GL20.GL_LINK_STATUS);
|
||||
@@ -194,10 +188,10 @@ public class Program {
|
||||
GL20.glDeleteProgram(program);
|
||||
GL20.glDeleteShader(vs);
|
||||
GL20.glDeleteShader(fs);
|
||||
throw new RuntimeException("FFP shader link failed (vk=0x" + Long.toHexString(vk.pack()) + ", fk=" + fk + "): " + log);
|
||||
throw new RuntimeException("FFP shader link failed (vk=0x" + Long.toHexString(vk.pack()) + ", fk=0x" + Long.toHexString(fk.pack()) + "): " + log);
|
||||
}
|
||||
|
||||
final String debugName = "FFP(v=0x" + Long.toHexString(vk.pack()) + ",f=" + id + ")";
|
||||
final String debugName = "FFP(v=0x" + Long.toHexString(vk.pack()) + ",f=0x" + Long.toHexString(fk.pack()) + ")";
|
||||
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
|
||||
|
||||
// Detach and delete individual shaders — they're linked into the program
|
||||
@@ -210,9 +204,8 @@ public class Program {
|
||||
|
||||
final int previousProgram = GLStateManager.getActiveProgram();
|
||||
GL20.glUseProgram(program);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (ffpProgram.locSampler[i] != -1) GL20.glUniform1i(ffpProgram.locSampler[i], i);
|
||||
}
|
||||
if (ffpProgram.locSampler0 != -1) GL20.glUniform1i(ffpProgram.locSampler0, 0);
|
||||
if (ffpProgram.locSampler1 != -1) GL20.glUniform1i(ffpProgram.locSampler1, 1);
|
||||
GL20.glUseProgram(previousProgram);
|
||||
|
||||
return ffpProgram;
|
||||
|
||||
@@ -9,218 +9,75 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
|
||||
/**
|
||||
* Open-addressed cache for FFP shader program variants.
|
||||
* Keyed on (vertexKey, fragmentKey) pairs stored inline as raw longs — zero allocation on cache hits.
|
||||
* Converges on Mesa's prog_cache.c: hash+memcmp over packed bytes, open addressing.
|
||||
*
|
||||
* <p>Layout per slot in {@code keys[]}: {@link #SLOT_STRIDE}=6 longs
|
||||
* <pre>
|
||||
* [0]: vertexKeyPacked
|
||||
* [1]: (fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFF) — never 0 for valid entries (fkLen >= 1)
|
||||
* [2..5]: fk0..fk3
|
||||
* </pre>
|
||||
* Cache for FFP shader program variants. Keyed on (vertexKey, fragmentKey) pairs.
|
||||
*/
|
||||
public class ShaderCache {
|
||||
private static final Logger LOGGER = LogManager.getLogger("FFPShaderCache");
|
||||
private static final int SLOT_STRIDE = 6;
|
||||
private static final int INITIAL_CAPACITY = 64; // power of 2
|
||||
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
|
||||
|
||||
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectOpenHashMap<String> fragmentSourceCache = new Long2ObjectOpenHashMap<>();
|
||||
private final Long2ObjectOpenHashMap<Program> programCache = new Long2ObjectOpenHashMap<>();
|
||||
|
||||
// Open-addressed table — parallel arrays
|
||||
private long[] keys;
|
||||
private Program[] programs;
|
||||
private String[] fragSources; // fragment source per slot; reused across VK variants via findFragSource
|
||||
private int capacity;
|
||||
private int count;
|
||||
private int uniqueFragCount;
|
||||
|
||||
// Fast-path: last-used key (raw longs, no objects)
|
||||
private long lastVK = Long.MIN_VALUE;
|
||||
private int lastFKLen;
|
||||
private long lastFK0, lastFK1, lastFK2, lastFK3;
|
||||
private Program lastProgram;
|
||||
|
||||
private int fragmentDumpCounter;
|
||||
private long lastCombinedKey = Long.MIN_VALUE;
|
||||
private Program lastProgram = null;
|
||||
|
||||
@Setter private Path dumpDir = null;
|
||||
|
||||
public ShaderCache() {
|
||||
capacity = INITIAL_CAPACITY;
|
||||
keys = new long[capacity * SLOT_STRIDE];
|
||||
programs = new Program[capacity];
|
||||
fragSources = new String[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create an FFP program for the given state keys.
|
||||
*
|
||||
* @param vkPacked packed vertex key
|
||||
* @param fkPacked pre-allocated scratch array with packed fragment key longs
|
||||
* @param fkLen number of significant longs in fkPacked (1..4)
|
||||
* Get or create an FFP program for the given packed state keys.
|
||||
*/
|
||||
public Program getOrCreate(long vkPacked, long[] fkPacked, int fkLen) {
|
||||
// Fast-path: compare raw longs inline
|
||||
if (vkPacked == lastVK && fkLen == lastFKLen && lastProgram != null
|
||||
&& fkPacked[0] == lastFK0
|
||||
&& (fkLen < 2 || fkPacked[1] == lastFK1)
|
||||
&& (fkLen < 3 || fkPacked[2] == lastFK2)
|
||||
&& (fkLen < 4 || fkPacked[3] == lastFK3)) {
|
||||
public Program getOrCreate(long vkPacked, long fkPacked) {
|
||||
final long combinedKey = (fkPacked << 32) | (vkPacked & 0xFFFFFFFFL);
|
||||
|
||||
if (combinedKey == lastCombinedKey && lastProgram != null) {
|
||||
return lastProgram;
|
||||
}
|
||||
|
||||
final long hash = hashKey(vkPacked, fkPacked, fkLen);
|
||||
final long storedWord = ((long) fkLen << 48) | (hash & 0x0000_FFFF_FFFF_FFFFL);
|
||||
final int mask = capacity - 1;
|
||||
int idx = (int) (hash & mask);
|
||||
while (true) {
|
||||
final int base = idx * SLOT_STRIDE;
|
||||
final long slot1 = keys[base + 1];
|
||||
if (slot1 == EMPTY_MARKER) {
|
||||
break;
|
||||
}
|
||||
if (slot1 == storedWord && keys[base] == vkPacked && keysMatch(base + 2, fkPacked, fkLen)) {
|
||||
final Program p = programs[idx];
|
||||
setLastUsed(vkPacked, fkPacked, fkLen, p);
|
||||
return p;
|
||||
}
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
Program program = programCache.get(combinedKey);
|
||||
if (program != null) {
|
||||
lastCombinedKey = combinedKey;
|
||||
lastProgram = program;
|
||||
|
||||
if (count >= (capacity * 3) / 4) {
|
||||
grow();
|
||||
return getOrCreate(vkPacked, fkPacked, fkLen);
|
||||
return program;
|
||||
}
|
||||
|
||||
final VertexKey vk = VertexKey.fromPacked(vkPacked);
|
||||
final FragmentKey fk = FragmentKey.fromPacked(fkPacked);
|
||||
|
||||
final String vertSrc = getOrGenerateVertex(vk);
|
||||
final String fragSrc = getOrGenerateFragment(fk);
|
||||
|
||||
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
|
||||
String fragSrc = findFragSource(fkPacked, fkLen);
|
||||
if (fragSrc == null) {
|
||||
fragSrc = FragmentShaderGenerator.generate(fk);
|
||||
uniqueFragCount++;
|
||||
}
|
||||
program = Program.create(vk, fk, vertSrc, fragSrc);
|
||||
programCache.put(combinedKey, program);
|
||||
lastCombinedKey = combinedKey;
|
||||
lastProgram = program;
|
||||
|
||||
final Program program = Program.create(vk, fk, vertSrc, fragSrc);
|
||||
|
||||
final int insertIdx = findEmptySlot(hash);
|
||||
final int base = insertIdx * SLOT_STRIDE;
|
||||
keys[base] = vkPacked;
|
||||
keys[base + 1] = storedWord;
|
||||
for (int i = 0; i < fkLen; i++) keys[base + 2 + i] = fkPacked[i];
|
||||
programs[insertIdx] = program;
|
||||
fragSources[insertIdx] = fragSrc;
|
||||
count++;
|
||||
|
||||
setLastUsed(vkPacked, fkPacked, fkLen, program);
|
||||
|
||||
LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, count);
|
||||
LOGGER.debug("FFP variant compiled: {} + {} (total: {} programs)", vk, fk, programCache.size());
|
||||
|
||||
// Debug dump
|
||||
if (dumpDir != null) {
|
||||
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
|
||||
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
|
||||
dumpShader(vkPacked, ".vert.glsl", vertSrc);
|
||||
dumpShader(fkPacked, ".frag.glsl", fragSrc);
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
private String findFragSource(long[] fkPacked, int fkLen) {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
final int base = i * SLOT_STRIDE;
|
||||
if (keys[base + 1] == EMPTY_MARKER) continue;
|
||||
final int storedLen = (int) (keys[base + 1] >>> 48);
|
||||
if (storedLen != fkLen) continue;
|
||||
if (keysMatch(base + 2, fkPacked, fkLen)) {
|
||||
return fragSources[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean keysMatch(int keysOffset, long[] fkPacked, int fkLen) {
|
||||
for (int i = 0; i < fkLen; i++) {
|
||||
if (keys[keysOffset + i] != fkPacked[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setLastUsed(long vkPacked, long[] fkPacked, int fkLen, Program program) {
|
||||
lastVK = vkPacked;
|
||||
lastFKLen = fkLen;
|
||||
lastFK0 = fkPacked[0];
|
||||
lastFK1 = fkLen >= 2 ? fkPacked[1] : 0;
|
||||
lastFK2 = fkLen >= 3 ? fkPacked[2] : 0;
|
||||
lastFK3 = fkLen >= 4 ? fkPacked[3] : 0;
|
||||
lastProgram = program;
|
||||
}
|
||||
|
||||
private int findEmptySlot(long hash) {
|
||||
final int mask = capacity - 1;
|
||||
int idx = (int) (hash & mask);
|
||||
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash over (vk + fk longs). Adapted from Mesa's hash_key — mix-rotate-multiply.
|
||||
*/
|
||||
private static long hashKey(long vkPacked, long[] fkPacked, int fkLen) {
|
||||
long h = vkPacked * 0x9E3779B97F4A7C15L;
|
||||
for (int i = 0; i < fkLen; i++) {
|
||||
h ^= fkPacked[i] * 0x517CC1B727220A95L;
|
||||
h = Long.rotateLeft(h, 31) * 0x9E3779B97F4A7C15L;
|
||||
}
|
||||
return h | 1L; // ensure nonzero lower bits
|
||||
}
|
||||
|
||||
private void grow() {
|
||||
final int oldCap = capacity;
|
||||
final long[] oldKeys = keys;
|
||||
final Program[] oldPrograms = programs;
|
||||
final String[] oldFragSources = fragSources;
|
||||
|
||||
capacity = oldCap * 2;
|
||||
keys = new long[capacity * SLOT_STRIDE];
|
||||
programs = new Program[capacity];
|
||||
fragSources = new String[capacity];
|
||||
count = 0;
|
||||
|
||||
final int mask = capacity - 1;
|
||||
for (int i = 0; i < oldCap; i++) {
|
||||
final int oldBase = i * SLOT_STRIDE;
|
||||
if (oldKeys[oldBase + 1] == EMPTY_MARKER) continue;
|
||||
|
||||
final long storedHash = oldKeys[oldBase + 1] & 0x0000_FFFF_FFFF_FFFFL;
|
||||
int idx = (int) (storedHash & mask);
|
||||
while (keys[idx * SLOT_STRIDE + 1] != EMPTY_MARKER) {
|
||||
idx = (idx + 1) & mask;
|
||||
}
|
||||
final int newBase = idx * SLOT_STRIDE;
|
||||
System.arraycopy(oldKeys, oldBase, keys, newBase, SLOT_STRIDE);
|
||||
programs[idx] = oldPrograms[i];
|
||||
fragSources[idx] = oldFragSources[i];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private String getOrGenerateVertex(VertexKey vk) {
|
||||
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
|
||||
}
|
||||
|
||||
private void dumpShader(String name, String suffix, String source) {
|
||||
private String getOrGenerateFragment(FragmentKey fk) {
|
||||
return fragmentSourceCache.computeIfAbsent(fk.pack(), k -> FragmentShaderGenerator.generate(fk));
|
||||
}
|
||||
|
||||
private void dumpShader(long key, String suffix, String source) {
|
||||
if (dumpDir == null) return;
|
||||
try {
|
||||
Files.createDirectories(dumpDir);
|
||||
final Path file = dumpDir.resolve("ffp_" + name + suffix);
|
||||
final Path file = dumpDir.resolve("ffp_" + Long.toHexString(key) + suffix);
|
||||
if (!Files.exists(file)) {
|
||||
Files.writeString(file, source, StandardCharsets.UTF_8);
|
||||
}
|
||||
@@ -229,48 +86,41 @@ public class ShaderCache {
|
||||
}
|
||||
}
|
||||
|
||||
/** Dump all cached shaders to disk. */
|
||||
/**
|
||||
* Dump all cached shaders to disk. Useful for debugging.
|
||||
*/
|
||||
public void dumpAll(Path outputDir) {
|
||||
try {
|
||||
Files.createDirectories(outputDir);
|
||||
for (var entry : vertexSourceCache.entrySet()) {
|
||||
dumpShader(Long.toHexString(entry.getKey()), ".vert.glsl", entry.getValue());
|
||||
dumpShader(entry.getKey(), ".vert.glsl", entry.getValue());
|
||||
}
|
||||
final IdentityHashMap<String, Integer> seen = new IdentityHashMap<>();
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (fragSources[i] != null && !seen.containsKey(fragSources[i])) {
|
||||
final int idx = seen.size();
|
||||
seen.put(fragSources[i], idx);
|
||||
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
|
||||
}
|
||||
for (var entry : fragmentSourceCache.entrySet()) {
|
||||
dumpShader(entry.getKey(), ".frag.glsl", entry.getValue());
|
||||
}
|
||||
LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}",
|
||||
vertexSourceCache.size(), seen.size(), outputDir);
|
||||
LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}", vertexSourceCache.size(), fragmentSourceCache.size(), outputDir);
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroy all cached programs and clear caches. */
|
||||
/**
|
||||
* Destroy all cached programs and clear caches.
|
||||
*/
|
||||
public void destroy() {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (programs[i] != null) {
|
||||
programs[i].destroy();
|
||||
}
|
||||
for (Program program : programCache.values()) {
|
||||
program.destroy();
|
||||
}
|
||||
Arrays.fill(keys, 0);
|
||||
Arrays.fill(programs, null);
|
||||
Arrays.fill(fragSources, null);
|
||||
count = 0;
|
||||
uniqueFragCount = 0;
|
||||
programCache.clear();
|
||||
vertexSourceCache.clear();
|
||||
fragmentSourceCache.clear();
|
||||
lastProgram = null;
|
||||
lastVK = Long.MIN_VALUE;
|
||||
lastCombinedKey = Long.MIN_VALUE;
|
||||
LOGGER.debug("FFP shader cache cleared");
|
||||
}
|
||||
|
||||
public int getProgramCount() {
|
||||
return count;
|
||||
return programCache.size();
|
||||
}
|
||||
|
||||
public int getVertexVariantCount() {
|
||||
@@ -278,6 +128,6 @@ public class ShaderCache {
|
||||
}
|
||||
|
||||
public int getFragmentVariantCount() {
|
||||
return uniqueFragCount;
|
||||
return fragmentSourceCache.size();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
@@ -37,9 +35,7 @@ public class ShaderManager {
|
||||
private boolean active = false;
|
||||
private Program currentProgram = null;
|
||||
private long currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
|
||||
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
|
||||
private int currentFKLen = 0;
|
||||
private long currentFragmentKeyPacked = Long.MIN_VALUE;
|
||||
|
||||
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
@@ -71,7 +67,10 @@ public class ShaderManager {
|
||||
|
||||
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
|
||||
currentVertexFlags = format.getVertexFlags();
|
||||
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
|
||||
final int boundVao = GLStateManager.getBoundVAO();
|
||||
if (boundVao != 0) {
|
||||
vaoVertexFlags.put(boundVao, currentVertexFlags);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -92,7 +91,7 @@ public class ShaderManager {
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
currentVertexKeyPacked = Long.MIN_VALUE;
|
||||
currentFKLen = 0;
|
||||
currentFragmentKeyPacked = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,10 +114,14 @@ public class ShaderManager {
|
||||
}
|
||||
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
final long fkPacked = FragmentKey.packFromState();
|
||||
|
||||
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
|
||||
commitVariant(vkPacked, fkLen);
|
||||
if (vkPacked != currentVertexKeyPacked || fkPacked != currentFragmentKeyPacked) {
|
||||
currentVertexKeyPacked = vkPacked;
|
||||
currentFragmentKeyPacked = fkPacked;
|
||||
|
||||
currentProgram = cache.getOrCreate(vkPacked, fkPacked);
|
||||
GL20.glUseProgram(currentProgram.getProgramId());
|
||||
}
|
||||
|
||||
uploadUniforms();
|
||||
@@ -139,16 +142,10 @@ public class ShaderManager {
|
||||
}
|
||||
|
||||
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
currentVertexKeyPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
currentFragmentKeyPacked = FragmentKey.packFromState();
|
||||
|
||||
private void commitVariant(long vkPacked, int fkLen) {
|
||||
currentVertexKeyPacked = vkPacked;
|
||||
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
|
||||
currentFKLen = fkLen;
|
||||
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
|
||||
currentProgram = cache.getOrCreate(currentVertexKeyPacked, currentFragmentKeyPacked);
|
||||
GL20.glUseProgram(currentProgram.getProgramId());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,10 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.QuadConverter;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer.UploadStrategy;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -47,7 +45,6 @@ public class TessellatorStreamingDrawer {
|
||||
private static int repackCapacity;
|
||||
|
||||
private static boolean initialized = false;
|
||||
private static UploadStrategy orphanStrategy = UploadStrategy.BUFFER_SUB_DATA;
|
||||
|
||||
static {
|
||||
// Initial repack buffer: 64KB
|
||||
@@ -59,14 +56,7 @@ public class TessellatorStreamingDrawer {
|
||||
private static void init() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
// Select orphan upload strategy from config
|
||||
if (AngelicaMod.options() != null && AngelicaMod.options().advanced.useMapBufferRange) {
|
||||
orphanStrategy = UploadStrategy.MAP_BUFFER_RANGE;
|
||||
}
|
||||
LOGGER.info("Orphan upload strategy: {}", orphanStrategy);
|
||||
|
||||
if (RenderSystem.supportsBufferStorage() && !Boolean.getBoolean("angelica.forceOrphanStreaming")) {
|
||||
if (RenderSystem.supportsBufferStorage()) {
|
||||
try {
|
||||
persistentBuffer = new PersistentStreamingBuffer();
|
||||
LOGGER.info("Persistent streaming buffer created ({}MB)", PersistentStreamingBuffer.DEFAULT_CAPACITY / (1024 * 1024));
|
||||
@@ -106,7 +96,26 @@ public class TessellatorStreamingDrawer {
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit((int)(writePtr - repackAddress));
|
||||
|
||||
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
ensureVAO(flags, format);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(repackBuffer, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
orphanBuffers[flags].upload(repackBuffer, vertexSize);
|
||||
firstVertex = 0;
|
||||
}
|
||||
|
||||
ShaderManager.getInstance().preDraw(flags);
|
||||
drawWithQuadConversion(tess.drawMode, firstVertex, vertexCount);
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
// Shrink rawBuffer if oversized
|
||||
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
|
||||
@@ -134,22 +143,31 @@ public class TessellatorStreamingDrawer {
|
||||
final ByteBuffer buffer = dt.getWriteBuffer();
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
uploadAndDraw(buffer, flags, format, vertexSize, drawMode, vertexCount);
|
||||
}
|
||||
ensureVAO(flags, format);
|
||||
|
||||
private static String cachedDebugInfo = "Stream: not initialized";
|
||||
private static long lastDebugUpdateNanos;
|
||||
private static final long DEBUG_UPDATE_INTERVAL_NS = 500_000_000L; // 500ms
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(buffer, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
orphanBuffers[flags].upload(buffer, vertexSize);
|
||||
firstVertex = 0;
|
||||
}
|
||||
|
||||
ShaderManager.getInstance().preDraw(flags);
|
||||
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static String getDebugInfo() {
|
||||
if (!initialized) return "Stream: not initialized";
|
||||
|
||||
final long now = System.nanoTime();
|
||||
if (now - lastDebugUpdateNanos < DEBUG_UPDATE_INTERVAL_NS) {
|
||||
return cachedDebugInfo;
|
||||
}
|
||||
lastDebugUpdateNanos = now;
|
||||
|
||||
int orphanCount = 0;
|
||||
int orphanBytes = 0;
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
@@ -160,83 +178,23 @@ public class TessellatorStreamingDrawer {
|
||||
}
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
cachedDebugInfo = String.format("Stream: Persistent %s (%s free) + %d orphan (%s)",
|
||||
return String.format("Stream: Persistent %s (%s free) + %d orphan (%s)",
|
||||
formatBytes(persistentBuffer.getCapacity()), formatBytes(persistentBuffer.getRemaining()),
|
||||
orphanCount, formatBytes(orphanBytes));
|
||||
} else {
|
||||
cachedDebugInfo = String.format("Stream: Orphan (%d bufs, %s)", orphanCount, formatBytes(orphanBytes));
|
||||
}
|
||||
return cachedDebugInfo;
|
||||
return String.format("Stream: Orphan (%d bufs, %s)", orphanCount, formatBytes(orphanBytes));
|
||||
}
|
||||
|
||||
private static String formatBytes(int bytes) {
|
||||
if (bytes >= 1024 * 1024) return String.format("%5.1fMB", bytes / (1024.0 * 1024.0));
|
||||
if (bytes >= 1024) return String.format("%5.1fKB", bytes / 1024.0);
|
||||
return String.format("%5dB", bytes);
|
||||
if (bytes >= 1024 * 1024) return String.format("%dMB", bytes / (1024 * 1024));
|
||||
if (bytes >= 1024) return String.format("%dKB", bytes / 1024);
|
||||
return bytes + "B";
|
||||
}
|
||||
|
||||
public static void endFrame() {
|
||||
if (persistentBuffer != null) {
|
||||
persistentBuffer.postDraw();
|
||||
}
|
||||
for (int i = 0; i < FORMAT_COUNT; i++) {
|
||||
if (orphanBuffers[i] != null) {
|
||||
orphanBuffers[i].postDraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a merged batch of pre-packed vertex data from a DeferredBatchTessellator.
|
||||
* Copies byte ranges into repackBuffer, then uploads and draws.
|
||||
*/
|
||||
static void drawPackedBatch(DeferredBatchTessellator source,
|
||||
java.util.List<DeferredBatchTessellator.DrawRange> ranges,
|
||||
int from, int to,
|
||||
int totalBytes, int totalVertices,
|
||||
int drawMode, int flags) {
|
||||
if (totalVertices == 0) return;
|
||||
|
||||
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
|
||||
final int vertexSize = format.getVertexSize();
|
||||
|
||||
ensureRepackCapacity(totalBytes);
|
||||
|
||||
long writePos = repackAddress;
|
||||
for (int j = from; j < to; j++) {
|
||||
final DeferredBatchTessellator.DrawRange r = ranges.get(j);
|
||||
source.copyRange(r.byteOffset(), r.byteLength(), writePos);
|
||||
writePos += r.byteLength();
|
||||
}
|
||||
repackBuffer.position(0);
|
||||
repackBuffer.limit(totalBytes);
|
||||
|
||||
uploadAndDraw(repackBuffer, flags, format, vertexSize, drawMode, totalVertices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload packed vertex data to a streaming buffer and issue the draw call.
|
||||
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
|
||||
*/
|
||||
private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
|
||||
ensureVAO(flags, format);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
if (persistentBuffer != null) {
|
||||
firstVertex = persistentBuffer.upload(packed, vertexSize);
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
firstVertex = orphanBuffers[flags].upload(packed, vertexSize);
|
||||
}
|
||||
|
||||
ShaderManager.getInstance().preDraw(flags);
|
||||
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
|
||||
@@ -265,7 +223,7 @@ public class TessellatorStreamingDrawer {
|
||||
init();
|
||||
|
||||
if (orphanVAOs[flags] == 0) {
|
||||
orphanBuffers[flags] = new OrphanStreamingBuffer(orphanStrategy);
|
||||
orphanBuffers[flags] = new OrphanStreamingBuffer();
|
||||
|
||||
orphanVAOs[flags] = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
|
||||
|
||||
@@ -398,18 +398,16 @@ public class Uniforms {
|
||||
GL20.glUniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
|
||||
}
|
||||
|
||||
// Per-unit tex env color
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (program.locTexEnvColor[i] != -1) {
|
||||
final var envState = GLStateManager.getTextures().getTexEnvState(i);
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(envState.envColorR);
|
||||
vec4Buf.put(envState.envColorG);
|
||||
vec4Buf.put(envState.envColorB);
|
||||
vec4Buf.put(envState.envColorA);
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locTexEnvColor[i], vec4Buf);
|
||||
}
|
||||
// Tex env color (only used by GL_BLEND mode)
|
||||
if (program.locTexEnvColor != -1) {
|
||||
final var envColor = GLStateManager.getTexEnvColor();
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(envColor.getRed());
|
||||
vec4Buf.put(envColor.getGreen());
|
||||
vec4Buf.put(envColor.getBlue());
|
||||
vec4Buf.put(envColor.getAlpha());
|
||||
vec4Buf.flip();
|
||||
GL20.glUniform4(program.locTexEnvColor, vec4Buf);
|
||||
}
|
||||
|
||||
// Fog
|
||||
|
||||
@@ -59,14 +59,6 @@ public final class ImmediateModeRecorder {
|
||||
tessellator().setTextureUV(s, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current lightmap coordinate as packed brightness.
|
||||
* Called by GLStateManager.glMultiTexCoord2f for unit 1 during recording/drawing.
|
||||
*/
|
||||
public static void setLightmapCoord(float s, float t) {
|
||||
tessellator().setBrightness(((int) t << 16) | ((int) s & 0xFFFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current normal vector.
|
||||
* Called by GLStateManager.glNormal*.
|
||||
|
||||
+32
-14
@@ -11,6 +11,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
|
||||
@@ -26,18 +27,35 @@ public record TexImage2DCmd(
|
||||
int border,
|
||||
int format,
|
||||
int type,
|
||||
@Nullable byte[] pixelData
|
||||
byte @Nullable [] pixelData
|
||||
) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create command from ShortBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromShortBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ShortBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 2; // 2 bytes per short
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asShortBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from IntBuffer
|
||||
*/
|
||||
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per int
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 4; // 4 bytes per int
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asIntBuffer().put(pixels);
|
||||
@@ -52,9 +70,9 @@ public record TexImage2DCmd(
|
||||
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per float
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 4; // 4 bytes per float
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asFloatBuffer().put(pixels);
|
||||
@@ -69,9 +87,9 @@ public record TexImage2DCmd(
|
||||
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 8; // 8 bytes per double
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 8; // 8 bytes per double
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asDoubleBuffer().put(pixels);
|
||||
@@ -86,9 +104,9 @@ public record TexImage2DCmd(
|
||||
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = limit - pos;
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = limit - pos;
|
||||
data = new byte[size];
|
||||
pixels.get(data);
|
||||
pixels.position(pos); // Restore position
|
||||
@@ -109,7 +127,7 @@ public record TexImage2DCmd(
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
final String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
|
||||
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
|
||||
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
|
||||
|
||||
+32
-14
@@ -11,6 +11,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
/**
|
||||
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
|
||||
@@ -26,18 +27,35 @@ public record TexSubImage2DCmd(
|
||||
int height,
|
||||
int format,
|
||||
int type,
|
||||
@Nullable byte[] pixelData
|
||||
byte @Nullable [] pixelData
|
||||
) implements DisplayListCommand {
|
||||
|
||||
/**
|
||||
* Create command from ShortBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromShortBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ShortBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 2; // 2 bytes per short
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asShortBuffer().put(pixels);
|
||||
pixels.position(pos); // Restore position
|
||||
}
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command from IntBuffer
|
||||
*/
|
||||
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per int
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 4; // 4 bytes per int
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asIntBuffer().put(pixels);
|
||||
@@ -52,9 +70,9 @@ public record TexSubImage2DCmd(
|
||||
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 4; // 4 bytes per float
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 4; // 4 bytes per float
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asFloatBuffer().put(pixels);
|
||||
@@ -69,9 +87,9 @@ public record TexSubImage2DCmd(
|
||||
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = (limit - pos) * 8; // 8 bytes per double
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = (limit - pos) * 8; // 8 bytes per double
|
||||
data = new byte[size];
|
||||
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
|
||||
bb.asDoubleBuffer().put(pixels);
|
||||
@@ -86,9 +104,9 @@ public record TexSubImage2DCmd(
|
||||
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
byte[] data = null;
|
||||
if (pixels != null) {
|
||||
int pos = pixels.position();
|
||||
int limit = pixels.limit();
|
||||
int size = limit - pos;
|
||||
final int pos = pixels.position();
|
||||
final int limit = pixels.limit();
|
||||
final int size = limit - pos;
|
||||
data = new byte[size];
|
||||
pixels.get(data);
|
||||
pixels.position(pos); // Restore position
|
||||
@@ -109,7 +127,7 @@ public record TexSubImage2DCmd(
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
final String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
|
||||
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
|
||||
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
|
||||
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
|
||||
|
||||
@@ -29,7 +29,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public final Vector3f spotDirection;
|
||||
public float spotExponent;
|
||||
public float spotCutoff;
|
||||
public float spotCosCutoff;
|
||||
public float constantAttenuation;
|
||||
public float linearAttenuation;
|
||||
public float quadraticAttenuation;
|
||||
@@ -61,7 +60,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
this.specular = specular;
|
||||
this.spotExponent = spotExponent;
|
||||
this.spotCutoff = spotCutoff;
|
||||
this.spotCosCutoff = (float) Math.cos(Math.toRadians(spotCutoff));
|
||||
this.constantAttenuation = constantAttenuation;
|
||||
this.linearAttenuation = linearAttenuation;
|
||||
this.quadraticAttenuation = quadraticAttenuation;
|
||||
@@ -182,7 +180,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
public void setSpotCutoff(float f) {
|
||||
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
|
||||
this.spotCutoff = f;
|
||||
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +246,6 @@ public class LightState implements ISettableState<LightState> {
|
||||
this.spotDirection.set(state.spotDirection);
|
||||
this.spotExponent = state.spotExponent;
|
||||
this.spotCutoff = state.spotCutoff;
|
||||
this.spotCosCutoff = state.spotCosCutoff;
|
||||
this.constantAttenuation = state.constantAttenuation;
|
||||
this.linearAttenuation = state.linearAttenuation;
|
||||
this.quadraticAttenuation = state.quadraticAttenuation;
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
/**
|
||||
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
|
||||
*/
|
||||
public class TexEnvState implements IStateStack<TexEnvState> {
|
||||
|
||||
public int mode;
|
||||
|
||||
public int combineRgb;
|
||||
public int combineAlpha;
|
||||
|
||||
public final int[] sourceRgb = new int[3];
|
||||
public final int[] sourceAlpha = new int[3];
|
||||
|
||||
public final int[] operandRgb = new int[3];
|
||||
public final int[] operandAlpha = new int[3];
|
||||
|
||||
public float scaleRgb;
|
||||
public float scaleAlpha;
|
||||
|
||||
public float envColorR;
|
||||
public float envColorG;
|
||||
public float envColorB;
|
||||
public float envColorA;
|
||||
|
||||
private final TexEnvState[] stack;
|
||||
private int pointer;
|
||||
private final boolean isStackEntry;
|
||||
|
||||
public TexEnvState() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
private TexEnvState(boolean isStackEntry) {
|
||||
this.isStackEntry = isStackEntry;
|
||||
if (isStackEntry) {
|
||||
stack = null;
|
||||
} else {
|
||||
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new TexEnvState(true);
|
||||
}
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
public void copyFrom(TexEnvState other) {
|
||||
this.mode = other.mode;
|
||||
this.combineRgb = other.combineRgb;
|
||||
this.combineAlpha = other.combineAlpha;
|
||||
System.arraycopy(other.sourceRgb, 0, this.sourceRgb, 0, 3);
|
||||
System.arraycopy(other.sourceAlpha, 0, this.sourceAlpha, 0, 3);
|
||||
System.arraycopy(other.operandRgb, 0, this.operandRgb, 0, 3);
|
||||
System.arraycopy(other.operandAlpha, 0, this.operandAlpha, 0, 3);
|
||||
this.scaleRgb = other.scaleRgb;
|
||||
this.scaleAlpha = other.scaleAlpha;
|
||||
this.envColorR = other.envColorR;
|
||||
this.envColorG = other.envColorG;
|
||||
this.envColorB = other.envColorB;
|
||||
this.envColorA = other.envColorA;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mode = GL11.GL_MODULATE;
|
||||
combineRgb = GL11.GL_MODULATE;
|
||||
combineAlpha = GL11.GL_MODULATE;
|
||||
sourceRgb[0] = GL11.GL_TEXTURE; sourceRgb[1] = GL13.GL_PREVIOUS; sourceRgb[2] = GL13.GL_CONSTANT;
|
||||
sourceAlpha[0] = GL11.GL_TEXTURE; sourceAlpha[1] = GL13.GL_PREVIOUS; sourceAlpha[2] = GL13.GL_CONSTANT;
|
||||
operandRgb[0] = GL11.GL_SRC_COLOR; operandRgb[1] = GL11.GL_SRC_COLOR; operandRgb[2] = GL11.GL_SRC_ALPHA;
|
||||
operandAlpha[0] = GL11.GL_SRC_ALPHA; operandAlpha[1] = GL11.GL_SRC_ALPHA; operandAlpha[2] = GL11.GL_SRC_ALPHA;
|
||||
scaleRgb = 1.0f;
|
||||
scaleAlpha = 1.0f;
|
||||
envColorR = 0.0f; envColorG = 0.0f; envColorB = 0.0f; envColorA = 0.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TexEnvState push() {
|
||||
if (stack == null) throw new IllegalStateException("Cannot push stack entry");
|
||||
if (pointer >= stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].copyFrom(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TexEnvState pop() {
|
||||
if (stack == null) throw new IllegalStateException("Cannot pop stack entry");
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
copyFrom(stack[--pointer]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
public boolean isCombineMode() {
|
||||
return mode == GL13.GL_COMBINE;
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ public class TextureUnitArray {
|
||||
private final TextureUnitBooleanStateStack[] texGenRStates;
|
||||
private final TextureUnitBooleanStateStack[] texGenQStates;
|
||||
private final TexGenState[] texGenStates;
|
||||
private final TexEnvState[] texEnvStates;
|
||||
@Getter
|
||||
public final Matrix4fStack[] textureMatricies;
|
||||
|
||||
@@ -32,7 +31,6 @@ public class TextureUnitArray {
|
||||
texGenRStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
texGenQStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
texGenStates = new TexGenState[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
texEnvStates = new TexEnvState[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
textureMatricies = new Matrix4fStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
|
||||
for (int i = 0; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
@@ -45,7 +43,6 @@ public class TextureUnitArray {
|
||||
texGenRStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i);
|
||||
texGenQStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i);
|
||||
texGenStates[i] = new TexGenState();
|
||||
texEnvStates[i] = new TexEnvState();
|
||||
textureMatricies[i] = new Matrix4fStack(GLStateManager.MAX_TEXTURE_STACK_DEPTH);
|
||||
}
|
||||
}
|
||||
@@ -86,10 +83,6 @@ public class TextureUnitArray {
|
||||
return texGenStates[index];
|
||||
}
|
||||
|
||||
public TexEnvState getTexEnvState(int index) {
|
||||
return texEnvStates[index];
|
||||
}
|
||||
|
||||
public Matrix4fStack getTextureUnitMatrix(int index) {
|
||||
return textureMatricies[index];
|
||||
}
|
||||
|
||||
@@ -1,111 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
* Streaming buffer using the classic orphan pattern with sub-allocation.
|
||||
* <p>
|
||||
* Supports two upload strategies:
|
||||
* <ul>
|
||||
* <li>{@link UploadStrategy#BUFFER_SUB_DATA} — glBufferData orphan + glBufferSubData (default)</li>
|
||||
* <li>{@link UploadStrategy#MAP_BUFFER_RANGE} — glMapBufferRange with INVALIDATE hints </li>
|
||||
* </ul>
|
||||
* Streaming buffer using the classic orphan pattern
|
||||
*/
|
||||
public class OrphanStreamingBuffer implements StreamingBuffer {
|
||||
|
||||
public enum UploadStrategy {
|
||||
BUFFER_SUB_DATA,
|
||||
MAP_BUFFER_RANGE
|
||||
}
|
||||
|
||||
private static final int MAP_WRITE_INVALIDATE_BUFFER = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT;
|
||||
private static final int MAP_WRITE_INVALIDATE_RANGE = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT;
|
||||
|
||||
private int bufferId;
|
||||
private int capacity;
|
||||
private int writePos;
|
||||
private boolean needsOrphan = true;
|
||||
private final UploadStrategy strategy;
|
||||
|
||||
public OrphanStreamingBuffer() {
|
||||
this(UploadStrategy.BUFFER_SUB_DATA);
|
||||
}
|
||||
|
||||
public OrphanStreamingBuffer(UploadStrategy strategy) {
|
||||
this.strategy = strategy;
|
||||
this.bufferId = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int upload(ByteBuffer data, int vertexStride) {
|
||||
final int dataSize = data.remaining();
|
||||
|
||||
final int alignUnit = vertexStride * 4;
|
||||
int alignedPos = ((writePos + alignUnit - 1) / alignUnit) * alignUnit;
|
||||
|
||||
final int requiredBytes = data.remaining();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
|
||||
boolean freshAllocation = false;
|
||||
if (alignedPos + dataSize > capacity) {
|
||||
// Doesn't fit — grow if needed, allocate new storage, reset to offset 0
|
||||
growIfNeeded(dataSize);
|
||||
allocateBuffer();
|
||||
alignedPos = 0;
|
||||
freshAllocation = true;
|
||||
} else if (needsOrphan) {
|
||||
// First upload of the frame — need fresh storage
|
||||
if (strategy == UploadStrategy.BUFFER_SUB_DATA) {
|
||||
// SubData strategy: orphan via glBufferData to get new backing store
|
||||
allocateBuffer();
|
||||
}
|
||||
}
|
||||
if (strategy == UploadStrategy.BUFFER_SUB_DATA) {
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, alignedPos, data);
|
||||
if (requiredBytes > capacity) {
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
|
||||
capacity = requiredBytes;
|
||||
} else {
|
||||
// MapRange: choose flags based on whether this is the first upload of the frame
|
||||
final int flags;
|
||||
if (freshAllocation) {
|
||||
// Fresh allocation — nothing to invalidate, just write
|
||||
flags = MAP_WRITE_INVALIDATE_RANGE;
|
||||
} else if (needsOrphan) {
|
||||
// First upload of frame on existing buffer — invalidate entire buffer
|
||||
flags = MAP_WRITE_INVALIDATE_BUFFER;
|
||||
} else {
|
||||
// Subsequent upload — invalidate only our range
|
||||
flags = MAP_WRITE_INVALIDATE_RANGE;
|
||||
}
|
||||
final ByteBuffer mapped = LWJGL.glMapBufferRange(GL15.GL_ARRAY_BUFFER, alignedPos, dataSize, flags);
|
||||
memCopy(memAddress0(data), memAddress0(mapped), dataSize);
|
||||
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, capacity, GL15.GL_STREAM_DRAW);
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
|
||||
}
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
needsOrphan = false;
|
||||
writePos = alignedPos + dataSize;
|
||||
return alignedPos / vertexStride;
|
||||
}
|
||||
|
||||
private static final int MIN_CAPACITY = 64 * 1024;
|
||||
|
||||
private void growIfNeeded(int needed) {
|
||||
if (needed > capacity) {
|
||||
int newCapacity = Math.max(capacity, MIN_CAPACITY);
|
||||
while (newCapacity < needed) {
|
||||
newCapacity *= 2;
|
||||
}
|
||||
capacity = newCapacity;
|
||||
}
|
||||
}
|
||||
|
||||
private void allocateBuffer() {
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, capacity, GL15.GL_STREAM_DRAW);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,8 +43,7 @@ public class OrphanStreamingBuffer implements StreamingBuffer {
|
||||
|
||||
@Override
|
||||
public void postDraw() {
|
||||
writePos = 0;
|
||||
needsOrphan = true;
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,12 +53,4 @@ public class OrphanStreamingBuffer implements StreamingBuffer {
|
||||
bufferId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int getWritePos() {
|
||||
return writePos;
|
||||
}
|
||||
|
||||
UploadStrategy getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@ package com.gtnewhorizons.angelica.glsm.texture;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class TextureInfoCache {
|
||||
@@ -40,19 +38,9 @@ public class TextureInfoCache {
|
||||
}
|
||||
}
|
||||
|
||||
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable Buffer pixels) {
|
||||
public void onTexImage2D(int texture, int target, int level, int internalformat, int width, int height) {
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.internalFormat = internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
if (target == GL11.GL_TEXTURE_2D && level == 0) {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
final TextureInfo info = getInfo(texture);
|
||||
if (info == null) return;
|
||||
info.internalFormat = internalformat;
|
||||
info.width = width;
|
||||
|
||||
-6
@@ -30,12 +30,6 @@ public class CeleritasBlockTransformer implements IClassTransformer {
|
||||
if (transformedName.startsWith(exclusion)) return basicClass;
|
||||
}
|
||||
|
||||
// Here could be a `inner.shouldTransform(basicClass)` check to exit early from transforming,
|
||||
// but we also need to call `inner.trackBlockSubclasses` in this case and somehow pass
|
||||
// the name of a class and a superclass, which we can do only after parsing the whole constant pool.
|
||||
// This transformer is merely a fallback when FRB / lwjgl3ify is not present, so we can afford
|
||||
// it being not fully optimized
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
|
||||
+4
@@ -48,6 +48,10 @@ public class RFBAngelicaRedirector implements RfbClassTransformer {
|
||||
if (!classNode.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
if (!classNode.isOriginal()) {
|
||||
// If a class is already a transformed ClassNode, conservatively continue processing.
|
||||
return true;
|
||||
}
|
||||
return inner.shouldTransform(classNode.getOriginalBytes());
|
||||
}
|
||||
|
||||
|
||||
+1
-18
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.loading.rfb.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.CeleritasBlockTransform;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
|
||||
@@ -45,23 +44,7 @@ public class RFBCeleritasBlockTransformer implements RfbClassTransformer {
|
||||
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
if (!classNode.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
|
||||
if (metadata == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inner.shouldTransform(classNode.getOriginalBytes())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Track possible block subclasses even if we don't need to transform this class
|
||||
inner.trackBlockSubclasses(metadata.binaryThisName, metadata.binarySuperName);
|
||||
|
||||
return false;
|
||||
return classNode.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
-14
@@ -67,7 +67,7 @@ public final class AngelicaRedirector {
|
||||
"initializeTextures", "func_77474_a"
|
||||
);
|
||||
|
||||
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>(32);
|
||||
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>();
|
||||
private static final Map<String, Map<String, String>> interfaceRedirects = new HashMap<>();
|
||||
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
|
||||
private static final ClassConstantPoolParser cstPoolParser;
|
||||
@@ -106,7 +106,6 @@ public final class AngelicaRedirector {
|
||||
.add("glColorMaterial")
|
||||
.add("glDeleteLists")
|
||||
.add("glGenLists")
|
||||
.add("glGenTextures")
|
||||
.add("glIsList")
|
||||
.add("glDeleteTextures")
|
||||
.add("glDepthFunc")
|
||||
@@ -239,14 +238,6 @@ public final class AngelicaRedirector {
|
||||
.add("glPushClientAttrib")
|
||||
.add("glPopClientAttrib")
|
||||
.add("glViewport")
|
||||
.add("glFeedbackBuffer")
|
||||
.add("glRenderMode")
|
||||
.add("glPassThrough")
|
||||
.add("glSelectBuffer")
|
||||
.add("glInitNames")
|
||||
.add("glPushName")
|
||||
.add("glPopName")
|
||||
.add("glLoadName")
|
||||
);
|
||||
methodRedirects.put(GL12, RedirectMap.newMap()
|
||||
.add("glTexImage3D")
|
||||
@@ -258,7 +249,6 @@ public final class AngelicaRedirector {
|
||||
.add("glSampleCoverage")
|
||||
.add("glClientActiveTexture")
|
||||
.add("glMultiTexCoord2f")
|
||||
.add("glMultiTexCoord2d")
|
||||
.add("glMultiTexCoord2s")
|
||||
);
|
||||
methodRedirects.put(GL14, RedirectMap.newMap()
|
||||
@@ -408,12 +398,22 @@ public final class AngelicaRedirector {
|
||||
.add("glBindVertexArray")
|
||||
);
|
||||
|
||||
final List<String> stringsToSearch = new ArrayList<>(32);
|
||||
final List<String> stringsToSearch = new ArrayList<>();
|
||||
stringsToSearch.add(GL11);
|
||||
stringsToSearch.add(GL12);
|
||||
stringsToSearch.add(GL13);
|
||||
stringsToSearch.add(GL14);
|
||||
stringsToSearch.add(GL20);
|
||||
stringsToSearch.add(OpenGlHelper);
|
||||
stringsToSearch.add(EXTBlendFunc);
|
||||
stringsToSearch.add(ARBMultiTexture);
|
||||
stringsToSearch.add(ARBShaderObjects);
|
||||
stringsToSearch.add(Project);
|
||||
stringsToSearch.add(UniversalVAO);
|
||||
stringsToSearch.add(VaoFunctions);
|
||||
stringsToSearch.add(LWJGLService);
|
||||
final String glPrefix = "org/lwjgl/opengl/GL";
|
||||
for (var entry : new HashMap<>(methodRedirects).entrySet()) {
|
||||
stringsToSearch.add(entry.getKey());
|
||||
if (entry.getKey().startsWith(glPrefix)) {
|
||||
methodRedirects.put(entry.getKey() + "C", entry.getValue());
|
||||
stringsToSearch.add(entry.getKey() + "C");
|
||||
@@ -427,12 +427,13 @@ public final class AngelicaRedirector {
|
||||
"org.lwjgl",
|
||||
"com.gtnewhorizons.angelica.glsm.",
|
||||
"com.gtnewhorizons.angelica.transform",
|
||||
"com.gtnewhorizon.gtnhlib.asm.",
|
||||
"me.eigenraven.lwjgl3ify"
|
||||
};
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] basicClass) {
|
||||
return cstPoolParser.find(basicClass);
|
||||
return cstPoolParser.find(basicClass, true);
|
||||
}
|
||||
|
||||
/** @return Was the class changed? */
|
||||
|
||||
+15
-23
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.loading.shared.transformers;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -18,6 +17,7 @@ import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class CeleritasBlockTransform {
|
||||
|
||||
private final ClassConstantPoolParser cstPoolParser;
|
||||
private final List<String> blockFieldNames = new ArrayList<>();
|
||||
private final Map<String, String> blockFieldRedirects = new HashMap<>();
|
||||
|
||||
public CeleritasBlockTransform(boolean isObf) {
|
||||
@@ -41,10 +41,9 @@ public final class CeleritasBlockTransform {
|
||||
);
|
||||
for (Pair<String, String> pair : mappings) {
|
||||
final String name = isObf ? pair.getRight() : pair.getLeft();
|
||||
this.blockFieldNames.add(name);
|
||||
this.blockFieldRedirects.put(name, pair.getLeft());
|
||||
}
|
||||
|
||||
this.cstPoolParser = new ClassConstantPoolParser(this.blockFieldRedirects.keySet().toArray(new String[0]));
|
||||
}
|
||||
|
||||
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
|
||||
@@ -107,34 +106,22 @@ public final class CeleritasBlockTransform {
|
||||
"org.lwjgl",
|
||||
"com.gtnewhorizons.angelica.glsm.",
|
||||
"com.gtnewhorizons.angelica.transform",
|
||||
"com.gtnewhorizon.gtnhlib.asm.",
|
||||
"me.eigenraven.lwjgl3ify"
|
||||
};
|
||||
}
|
||||
|
||||
public void trackBlockSubclasses(String className, String classSuperName) {
|
||||
if (!isVanillaBlockSubclass(className) && isBlockSubclass(classSuperName)) {
|
||||
moddedBlockSubclasses.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] classBytes) {
|
||||
return cstPoolParser.find(classBytes);
|
||||
}
|
||||
|
||||
/** @return Was the class changed? */
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn) {
|
||||
if (!isCeleritasEnabled()) {
|
||||
return false;
|
||||
if ("net.minecraft.block.Block".equals(transformedName) && isCeleritasEnabled()) {
|
||||
cn.fields.removeIf(field -> blockFieldNames.stream().anyMatch(name -> field.name.equals(name)));
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
|
||||
if ("net.minecraft.block.Block".equals(transformedName)) {
|
||||
changed = cn.fields.removeIf(field -> blockFieldRedirects.containsKey(field.name));
|
||||
// Track subclasses of Block
|
||||
if (!isVanillaBlockSubclass(cn.name) && isBlockSubclass(cn.superName)) {
|
||||
moddedBlockSubclasses.add(cn.name);
|
||||
}
|
||||
|
||||
trackBlockSubclasses(cn.name, cn.superName);
|
||||
|
||||
// Check if this class shadows any fields of the parent class
|
||||
if (moddedBlockSubclasses.contains(cn.name)) {
|
||||
// If a superclass shadows, then so do we, because JVM will resolve a reference on our class to that
|
||||
@@ -145,7 +132,7 @@ public final class CeleritasBlockTransform {
|
||||
} else {
|
||||
// Check if we declare any known field names
|
||||
for (FieldNode field : cn.fields) {
|
||||
if (blockFieldRedirects.containsKey(field.name)) {
|
||||
if (blockFieldNames.contains(field.name)) {
|
||||
doWeShadow = true;
|
||||
break;
|
||||
}
|
||||
@@ -157,6 +144,11 @@ public final class CeleritasBlockTransform {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCeleritasEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
for (MethodNode mn : cn.methods) {
|
||||
for (AbstractInsnNode node : mn.instructions.toArray()) {
|
||||
if ((node.getOpcode() == Opcodes.GETFIELD || node.getOpcode() == Opcodes.PUTFIELD) && node instanceof FieldInsnNode fNode) {
|
||||
|
||||
@@ -108,11 +108,6 @@ public enum Mixins implements IMixins {
|
||||
.setPhase(Phase.EARLY)
|
||||
.addClientMixins("angelica.optimizations.MixinGLAllocation")),
|
||||
|
||||
ANGELICA_DEFERRED_TESSELLATOR_BATCH(new MixinBuilder("Deferred tessellator batching for particles to reduce draw calls")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableCeleritas)
|
||||
.addClientMixins("angelica.particles.MixinEffectRenderer_DeferredBatch")),
|
||||
|
||||
// Not compatible with the lwjgl debug callbacks, so disable if that's enabled
|
||||
ARCHAIC_SPLASH(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
@@ -352,11 +347,6 @@ public enum Mixins implements IMixins {
|
||||
.addClientMixins("angelica.textures.MixinTextureUtil_OptimizeMipmap")
|
||||
.setApplyIf(() -> AngelicaConfig.optimizeTextureLoading)),
|
||||
|
||||
REPLACE_FFP(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.addClientMixins("angelica.vbo.MixinFramebuffer")
|
||||
.setApplyIf(() -> AngelicaConfig.replaceFFPUploads)),
|
||||
|
||||
//From NotFine
|
||||
NOTFINE_BASE_MOD(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
|
||||
+1
-12
@@ -142,7 +142,6 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
final int worldZ = originZ + blockZ;
|
||||
|
||||
int stateIdx = 0;
|
||||
int facesAtBaseMaterial = 0;
|
||||
|
||||
for (int quadIdx = 0; quadIdx < numQuads; quadIdx++) {
|
||||
float uSum = 0, vSum = 0;
|
||||
@@ -219,17 +218,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
}
|
||||
}
|
||||
|
||||
final int faceBit = 1 << facing.ordinal();
|
||||
final Material correctMaterial;
|
||||
if ((facesAtBaseMaterial & faceBit) != 0) {
|
||||
// Face already has a non-demoted quad — skip selectMaterial entirely
|
||||
correctMaterial = material;
|
||||
} else {
|
||||
correctMaterial = selectMaterial(material, sprite, isShaderPackOverride);
|
||||
if (correctMaterial == material) {
|
||||
facesAtBaseMaterial |= faceBit;
|
||||
}
|
||||
}
|
||||
final Material correctMaterial = selectMaterial(material, sprite, isShaderPackOverride);
|
||||
final var builder = buffers.get(correctMaterial);
|
||||
|
||||
if (correctMaterial != material && builder.getEncoder() instanceof IrisExtendedChunkVertexEncoder iris) {
|
||||
|
||||
+3
-8
@@ -4,24 +4,19 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraftforge.client.ForgeHooksClient;
|
||||
|
||||
public final class RenderPassHelper {
|
||||
|
||||
private static final class PassIntHolder {
|
||||
int pass = -1;
|
||||
}
|
||||
|
||||
private static final ThreadLocal<PassIntHolder> threadLocalPass = ThreadLocal.withInitial(PassIntHolder::new);
|
||||
private static final ThreadLocal<Integer> threadLocalPass = ThreadLocal.withInitial(() -> -1);
|
||||
|
||||
private RenderPassHelper() {}
|
||||
|
||||
public static int getWorldRenderPass() {
|
||||
return GLStateManager.isMainThread() ? ForgeHooksClient.worldRenderPass : threadLocalPass.get().pass;
|
||||
return GLStateManager.isMainThread() ? ForgeHooksClient.worldRenderPass : threadLocalPass.get();
|
||||
}
|
||||
|
||||
public static void setWorldRenderPass(int pass) {
|
||||
if (GLStateManager.isMainThread()) {
|
||||
ForgeHooksClient.worldRenderPass = pass;
|
||||
} else {
|
||||
threadLocalPass.get().pass = pass;
|
||||
threadLocalPass.set(pass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,13 +71,13 @@ public class MipmapHelper {
|
||||
}
|
||||
|
||||
static void setupTexture(int width, int height, boolean blur, boolean clamp, String textureName) {
|
||||
final int mipmaps = useMipmapsForTexture(textureName) ? getMipmapLevels(width, height, 1) : 0;
|
||||
int mipmaps = useMipmapsForTexture(textureName) ? getMipmapLevels(width, height, 1) : 0;
|
||||
logger.finer("setupTexture(%s) %dx%d %d mipmaps", textureName, width, height, mipmaps);
|
||||
final int magFilter = blur ? GL11.GL_LINEAR : GL11.GL_NEAREST;
|
||||
final int minFilter = mipmaps > 0 ? GL11.GL_LINEAR_MIPMAP_LINEAR : magFilter;
|
||||
final int wrap = clamp ? GL11.GL_CLAMP : GL11.GL_REPEAT;
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmaps);
|
||||
int magFilter = blur ? GL11.GL_LINEAR : GL11.GL_NEAREST;
|
||||
int minFilter = mipmaps > 0 ? GL11.GL_LINEAR_MIPMAP_LINEAR : magFilter;
|
||||
int wrap = clamp ? GL11.GL_CLAMP : GL11.GL_REPEAT;
|
||||
if (mipmaps > 0) {
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmaps);
|
||||
checkGLError("%s: set GL_TEXTURE_MAX_LEVEL = %d", textureName, mipmaps);
|
||||
if (anisoSupported && anisoLevel > 1) {
|
||||
GL11.glTexParameterf(
|
||||
@@ -125,12 +125,12 @@ public class MipmapHelper {
|
||||
mipmapType.clear();
|
||||
mipmapType.put("terrain", true);
|
||||
mipmapType.put("items", false);
|
||||
final PropertiesFile properties = PropertiesFile.get(logger, MIPMAP_PROPERTIES);
|
||||
PropertiesFile properties = PropertiesFile.get(logger, MIPMAP_PROPERTIES);
|
||||
if (properties != null) {
|
||||
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||
final String key = entry.getKey()
|
||||
String key = entry.getKey()
|
||||
.trim();
|
||||
final boolean value = Boolean.parseBoolean(
|
||||
boolean value = Boolean.parseBoolean(
|
||||
entry.getValue()
|
||||
.trim()
|
||||
.toLowerCase());
|
||||
@@ -172,7 +172,7 @@ public class MipmapHelper {
|
||||
}
|
||||
|
||||
static int getMipmapLevelsForCurrentTexture() {
|
||||
final int filter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
|
||||
int filter = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
|
||||
if (filter != GL11.GL_NEAREST_MIPMAP_LINEAR && filter != GL11.GL_NEAREST_MIPMAP_NEAREST) {
|
||||
return 0;
|
||||
}
|
||||
@@ -195,7 +195,7 @@ public class MipmapHelper {
|
||||
static void scaleHalf(IntBuffer in, int w, int h, IntBuffer out, int rotate) {
|
||||
for (int i = 0; i < w / 2; i++) {
|
||||
for (int j = 0; j < h / 2; j++) {
|
||||
final int k = w * 2 * j + 2 * i;
|
||||
int k = w * 2 * j + 2 * i;
|
||||
int pixel00 = in.get(k);
|
||||
int pixel01 = in.get(k + 1);
|
||||
int pixel10 = in.get(k + w);
|
||||
@@ -216,10 +216,10 @@ public class MipmapHelper {
|
||||
}
|
||||
|
||||
private static int average4RGBA(int pixel00, int pixel01, int pixel10, int pixel11) {
|
||||
final int a00 = pixel00 & 0xff;
|
||||
final int a01 = pixel01 & 0xff;
|
||||
final int a10 = pixel10 & 0xff;
|
||||
final int a11 = pixel11 & 0xff;
|
||||
int a00 = pixel00 & 0xff;
|
||||
int a01 = pixel01 & 0xff;
|
||||
int a10 = pixel10 & 0xff;
|
||||
int a11 = pixel11 & 0xff;
|
||||
switch ((a00 << 24) | (a01 << 16) | (a10 << 8) | a11) {
|
||||
case 0xff000000:
|
||||
return pixel00;
|
||||
@@ -256,10 +256,10 @@ public class MipmapHelper {
|
||||
return average2RGBA(average2RGBA(pixel00, pixel11), average2RGBA(pixel01, pixel10));
|
||||
|
||||
default:
|
||||
final int a = a00 + a01 + a10 + a11;
|
||||
int a = a00 + a01 + a10 + a11;
|
||||
int pixel = a >> 2;
|
||||
for (int i = 8; i < 32; i += 8) {
|
||||
final int average = (a00 * ((pixel00 >> i) & 0xff) + a01 * ((pixel01 >> i) & 0xff)
|
||||
int average = (a00 * ((pixel00 >> i) & 0xff) + a01 * ((pixel01 >> i) & 0xff)
|
||||
+ a10 * ((pixel10 >> i) & 0xff)
|
||||
+ a11 * ((pixel11 >> i) & 0xff)) / a;
|
||||
pixel |= (average << i);
|
||||
@@ -273,9 +273,9 @@ public class MipmapHelper {
|
||||
}
|
||||
|
||||
private static void checkGLError(String format, Object... params) {
|
||||
final int error = GL11.glGetError();
|
||||
int error = GL11.glGetError();
|
||||
if (error != 0) {
|
||||
final String message = GLU.gluErrorString(error) + ": " + String.format(format, params);
|
||||
String message = GLU.gluErrorString(error) + ": " + String.format(format, params);
|
||||
new RuntimeException(message).printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,21 +281,6 @@ public class SodiumGameOptionPages {
|
||||
.setImpact(OptionImpact.VARIES)
|
||||
.setEnabled(GLStateManager.capabilities != null)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
|
||||
.setName(I18n.format("sodium.options.use_map_buffer_range.name"))
|
||||
.setTooltip(I18n.format("sodium.options.use_map_buffer_range.tooltip"))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding((opts, value) -> opts.advanced.useMapBufferRange = value, opts -> opts.advanced.useMapBufferRange)
|
||||
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
.setImpact(OptionImpact.VARIES)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
|
||||
.setName(I18n.format("sodium.options.enable_deferred_batching.name"))
|
||||
.setTooltip(I18n.format("sodium.options.enable_deferred_batching.tooltip"))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding((opts, value) -> opts.advanced.enableDeferredBatching = value, opts -> opts.advanced.enableDeferredBatching)
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
|
||||
@@ -33,8 +33,6 @@ public class SodiumGameOptions {
|
||||
public boolean useParticleCulling = true;
|
||||
public boolean allowDirectMemoryAccess = true;
|
||||
public boolean ignoreDriverBlacklist = false;
|
||||
public boolean useMapBufferRange = false;
|
||||
public boolean enableDeferredBatching = true;
|
||||
}
|
||||
|
||||
public static class PerformanceSettings {
|
||||
@@ -91,10 +89,6 @@ public class SodiumGameOptions {
|
||||
}
|
||||
} else {
|
||||
config = new SodiumGameOptions();
|
||||
// Lwjgl2 doesn't expose multidraw and actually does individual, so try indirect if available
|
||||
if (isLwjgl2() && !isMacOS()) {
|
||||
config.advanced.multiDrawMode = MultiDrawMode.INDIRECT;
|
||||
}
|
||||
}
|
||||
|
||||
config.configPath = path;
|
||||
@@ -104,7 +98,6 @@ public class SodiumGameOptions {
|
||||
config.performance.cpuRenderAheadLimit = 0;
|
||||
}
|
||||
|
||||
// Downgrade INDIRECT if hardware doesn't support it
|
||||
if (config.advanced.multiDrawMode == MultiDrawMode.INDIRECT && (GLStateManager.capabilities == null || (!GLStateManager.capabilities.OpenGL43 && !GLStateManager.capabilities.GL_ARB_multi_draw_indirect))) {
|
||||
config.advanced.multiDrawMode = MultiDrawMode.DIRECT;
|
||||
}
|
||||
@@ -119,14 +112,6 @@ public class SodiumGameOptions {
|
||||
return config;
|
||||
}
|
||||
|
||||
private static boolean isLwjgl2() {
|
||||
return SodiumGameOptions.class.getClassLoader().getResource("org/lwjgl/Sys.class") != null;
|
||||
}
|
||||
|
||||
private static boolean isMacOS() {
|
||||
return System.getProperty("os.name", "").toLowerCase().contains("mac");
|
||||
}
|
||||
|
||||
public void writeChanges() throws IOException {
|
||||
final Path dir = this.configPath.getParent();
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.gtnewhorizons.angelica.compat.mojang.InteractionHand;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
@@ -27,7 +26,7 @@ public class HandRenderer {
|
||||
public static final HandRenderer INSTANCE = new HandRenderer();
|
||||
|
||||
private boolean ACTIVE;
|
||||
private @Getter boolean renderingSolid;
|
||||
private boolean renderingSolid;
|
||||
public static final float DEPTH = 0.125F;
|
||||
|
||||
private void setupGlState(RenderGlobal gameRenderer, Camera camera, float tickDelta) {
|
||||
@@ -183,4 +182,8 @@ public class HandRenderer {
|
||||
public boolean isActive() {
|
||||
return ACTIVE;
|
||||
}
|
||||
|
||||
public boolean isRenderingSolid() {
|
||||
return renderingSolid;
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,10 @@ public class ExternallyManagedUniforms {
|
||||
|
||||
public static void addExternallyManagedUniforms(UniformHolder uniformHolder) {
|
||||
addMat4(uniformHolder, "iris_ModelViewMatrix");
|
||||
addMat4(uniformHolder, "iris_ModelViewMatrixInverse");
|
||||
addMat4(uniformHolder, "iris_ProjectionMatrix");
|
||||
addMat4(uniformHolder, "iris_ProjectionMatrixInverse");
|
||||
addMat4(uniformHolder, "iris_LightmapTextureMatrix");
|
||||
addMat4(uniformHolder, "u_ModelViewProjectionMatrix");
|
||||
addMat3(uniformHolder, "iris_NormalMatrix");
|
||||
uniformHolder.externallyManagedUniform("heavyFog", UniformType.BOOL);
|
||||
uniformHolder.externallyManagedUniform("angelica_ClipPlanesEnabled", UniformType.BOOL);
|
||||
uniformHolder.externallyManagedUniform("angelica_ClipPlane[0]", UniformType.VEC4);
|
||||
}
|
||||
|
||||
public static void addExternallyManagedUniforms116(UniformHolder uniformHolder) {
|
||||
@@ -26,7 +21,6 @@ public class ExternallyManagedUniforms {
|
||||
|
||||
uniformHolder.externallyManagedUniform("u_ModelScale", UniformType.VEC3);
|
||||
uniformHolder.externallyManagedUniform("u_TextureScale", UniformType.VEC2);
|
||||
uniformHolder.externallyManagedUniform("u_RegionOffset", UniformType.VEC3);
|
||||
}
|
||||
|
||||
private static void addMat3(UniformHolder uniformHolder, String name) {
|
||||
|
||||
@@ -18,8 +18,8 @@ public class BuiltinReplacementUniforms {
|
||||
|
||||
public static void addBuiltinReplacementUniforms(UniformHolder uniforms) {
|
||||
uniforms.uniformMatrix(UniformUpdateFrequency.ONCE, "iris_LightmapTextureMatrix", () -> {
|
||||
Iris.logger.debug("A shader appears to require the lightmap texture matrix even after transformations have occurred");
|
||||
Iris.logger.debug("Iris handles this correctly but it indicates that the shader is doing weird things with lightmap coordinates");
|
||||
Iris.logger.warn("A shader appears to require the lightmap texture matrix even after transformations have occurred");
|
||||
Iris.logger.warn("Iris handles this correctly but it indicates that the shader is doing weird things with lightmap coordinates");
|
||||
|
||||
return lightmapTextureMatrix;
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ name=Angelica
|
||||
version=${version}
|
||||
additionalVersions=
|
||||
className=com.gtnewhorizons.angelica.loading.rfb.AngelicaRfbPlugin
|
||||
transformerExclusions=com.gtnewhorizons.angelica.loading.shared;com.gtnewhorizon.gtnhlib.asm
|
||||
transformerExclusions=com.gtnewhorizons.angelica.loading.shared
|
||||
versionConstraints=
|
||||
loadBefore=
|
||||
loadAfter=mixin; lwjgl3ify
|
||||
|
||||
@@ -232,7 +232,3 @@ sodium.options.multidraw_mode.tooltip=Controls how draw commands are dispatched
|
||||
sodium.options.multidraw_mode.direct=Direct
|
||||
sodium.options.multidraw_mode.indirect=Indirect
|
||||
sodium.options.multidraw_mode.individual=Individual
|
||||
sodium.options.use_map_buffer_range.name=Use MapBufferRange
|
||||
sodium.options.use_map_buffer_range.tooltip=Uses glMapBufferRange instead of glBufferSubData for streaming vertex uploads. May improve performance on some drivers.
|
||||
sodium.options.enable_deferred_batching.name=Deferred Particle Batching
|
||||
sodium.options.enable_deferred_batching.tooltip=Batches particle draw calls into fewer merged draws per layer. Disable if particles render incorrectly.
|
||||
+2
-7
@@ -6,7 +6,6 @@ import org.lwjgl.opengl.ContextAttribs;
|
||||
import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.PixelFormat;
|
||||
import org.lwjgl.LWJGLUtil;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
@@ -52,12 +51,9 @@ public abstract class MixinForgeHooksClient_CoreProfile {
|
||||
throw new RuntimeException("Failed to obtain ContextAttribs version setters", e);
|
||||
}
|
||||
|
||||
final int startMajor = 4;
|
||||
final int startMaxMinor = (LWJGLUtil.getPlatform() == LWJGLUtil.PLATFORM_MACOSX) ? 1 : 6;
|
||||
|
||||
Exception cur_exception = null;
|
||||
for (int major = startMajor; major >= 3; --major) {
|
||||
final int maxMinor = (major == 4) ? startMaxMinor : 3;
|
||||
for (int major = 4; major >= 3; --major) {
|
||||
final int maxMinor = (major == 4) ? 6 : 3;
|
||||
final int minMinor = (major == 3) ? 3 : 0;
|
||||
for (int minor = maxMinor; minor >= minMinor; --minor) {
|
||||
try {
|
||||
@@ -70,7 +66,6 @@ public abstract class MixinForgeHooksClient_CoreProfile {
|
||||
Display.create(format, attribs);
|
||||
return;
|
||||
} catch (LWJGLException | RuntimeException e) {
|
||||
try { Display.destroy(); } catch (Exception ignored) {}
|
||||
cur_exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.particles;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.DeferredDrawBatcher;
|
||||
import net.minecraft.client.particle.EffectRenderer;
|
||||
import net.minecraft.entity.Entity;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Captures individual particle draw() calls and merges them into a single draw call per unique GL state.
|
||||
*/
|
||||
@Mixin(EffectRenderer.class)
|
||||
public class MixinEffectRenderer_DeferredBatch {
|
||||
|
||||
@Inject(method = "renderParticles",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V",
|
||||
shift = At.Shift.AFTER))
|
||||
private void angelica$enterDeferredMode(Entity player, float partialTickTime, CallbackInfo ci) {
|
||||
if (AngelicaMod.options() != null && AngelicaMod.options().advanced.enableDeferredBatching) {
|
||||
DeferredDrawBatcher.enter();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "renderParticles",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/Tessellator;draw()I",
|
||||
shift = At.Shift.BEFORE))
|
||||
private void angelica$exitDeferredAndFlush(Entity player, float partialTickTime, CallbackInfo ci) {
|
||||
if (DeferredDrawBatcher.isActive()) {
|
||||
DeferredDrawBatcher.exitAndFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
-9
@@ -1,13 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.textures;
|
||||
|
||||
import net.minecraft.client.renderer.texture.TextureUtil;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(TextureUtil.class)
|
||||
public class MixinTextureUtil {
|
||||
@@ -16,9 +12,4 @@ public class MixinTextureUtil {
|
||||
private static void angelica$dontDeleteTexture(int textureId) {
|
||||
// NO-OP - Not sure why it's deleting a texture that was just generated and subsequently being bound...
|
||||
}
|
||||
|
||||
@Inject(method = "allocateTextureImpl", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/TextureUtil;bindTexture(I)V", shift = At.Shift.AFTER))
|
||||
private static void angelica$setMaxLevel(int textureId, int mipmapLevels, int width, int height, float anisotropicFiltering, CallbackInfo ci) {
|
||||
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.vbo;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.postprocessing.PostProcessingHelper;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Framebuffer.class)
|
||||
public abstract class MixinFramebuffer {
|
||||
|
||||
@Shadow
|
||||
public int framebufferTexture;
|
||||
|
||||
|
||||
/**
|
||||
* @author Sisyphus
|
||||
* @reason Replace FFP with statically allocated Buffers.
|
||||
*/
|
||||
@Overwrite
|
||||
public void framebufferRender(int width, int height) {
|
||||
GLStateManager.glColorMask(true, true, true, false);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glViewport(0, 0, width, height);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, framebufferTexture);
|
||||
PostProcessingHelper.bindFullscreenVAO();
|
||||
PostProcessingHelper.drawFullscreenQuad();
|
||||
PostProcessingHelper.unbindVAO();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
}
|
||||
}
|
||||
+12
-17
@@ -32,41 +32,36 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
|
||||
celeritas$processTransparentImages(this.framesTextureData.get(0)[0], level > 0 && !iconName.contains("leaves"));
|
||||
}
|
||||
|
||||
// Ordinals: OPAQUE=0, TRANSPARENT=1, TRANSLUCENT=2
|
||||
@Unique private static final int ORDINAL_OPAQUE = SpriteTransparencyLevel.OPAQUE.ordinal();
|
||||
@Unique private static final int ORDINAL_TRANSPARENT = SpriteTransparencyLevel.TRANSPARENT.ordinal();
|
||||
@Unique private static final int ORDINAL_TRANSLUCENT = SpriteTransparencyLevel.TRANSLUCENT.ordinal();
|
||||
@Unique private static final SpriteTransparencyLevel[] LEVELS_BY_ORDINAL = SpriteTransparencyLevel.values();
|
||||
|
||||
@Unique
|
||||
private void celeritas$processTransparentImages(int[] nativeImage, boolean shouldRewriteColors) {
|
||||
float r = 0.0f, g = 0.0f, b = 0.0f;
|
||||
float totalWeight = 0.0f;
|
||||
// Use primitive int instead of loop-carried enum reference to avoid JDK 25 C2 SuperWord non-convergence bug
|
||||
int maxLevel = ORDINAL_OPAQUE;
|
||||
SpriteTransparencyLevel level = SpriteTransparencyLevel.OPAQUE;
|
||||
|
||||
for (int y = 0; y < nativeImage.length; y++) {
|
||||
final int color = nativeImage[y];
|
||||
final int alpha = ColorABGR.unpackAlpha(color);
|
||||
int color = nativeImage[y];
|
||||
int alpha = ColorABGR.unpackAlpha(color);
|
||||
|
||||
if (alpha > 0) {
|
||||
if (alpha < 255) {
|
||||
maxLevel = Math.max(maxLevel, ORDINAL_TRANSLUCENT);
|
||||
level = level.chooseNextLevel(SpriteTransparencyLevel.TRANSLUCENT);
|
||||
} else {
|
||||
level = level.chooseNextLevel(SpriteTransparencyLevel.OPAQUE);
|
||||
}
|
||||
|
||||
if (shouldRewriteColors) {
|
||||
final float weight = (float) alpha;
|
||||
float weight = (float) alpha;
|
||||
r += ColorSRGB.srgbToLinear(ColorABGR.unpackRed(color)) * weight;
|
||||
g += ColorSRGB.srgbToLinear(ColorABGR.unpackGreen(color)) * weight;
|
||||
b += ColorSRGB.srgbToLinear(ColorABGR.unpackBlue(color)) * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
} else {
|
||||
maxLevel = Math.max(maxLevel, ORDINAL_TRANSPARENT);
|
||||
level = level.chooseNextLevel(SpriteTransparencyLevel.TRANSPARENT);
|
||||
}
|
||||
}
|
||||
|
||||
this.celeritas$transparencyLevel = LEVELS_BY_ORDINAL[maxLevel];
|
||||
this.celeritas$transparencyLevel = level;
|
||||
|
||||
if (!shouldRewriteColors || totalWeight == 0.0f) {
|
||||
return;
|
||||
@@ -76,11 +71,11 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
|
||||
g /= totalWeight;
|
||||
b /= totalWeight;
|
||||
|
||||
final int averageColor = ColorSRGB.linearToSrgb(r, g, b, 0);
|
||||
int averageColor = ColorSRGB.linearToSrgb(r, g, b, 0);
|
||||
|
||||
for (int y = 0; y < nativeImage.length; y++) {
|
||||
final int color = nativeImage[y];
|
||||
final int alpha = ColorABGR.unpackAlpha(color);
|
||||
int color = nativeImage[y];
|
||||
int alpha = ColorABGR.unpackAlpha(color);
|
||||
if (alpha == 0) {
|
||||
nativeImage[y] = averageColor;
|
||||
}
|
||||
|
||||
+10
-5
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.InteractionHand;
|
||||
import net.coderbot.iris.pipeline.HandRenderer;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
@@ -16,13 +15,19 @@ public class MixinItemRendererBackhand {
|
||||
@Inject(method = "renderItemInFirstPerson", at = @At("HEAD"), cancellable = true)
|
||||
private void iris$skipTranslucentHands(float partialTicks, CallbackInfo ci) {
|
||||
if (IrisApi.getInstance().isShaderPackInUse()) {
|
||||
final boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.MAIN_HAND);
|
||||
boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.MAIN_HAND);
|
||||
if (HandRenderer.INSTANCE.isRenderingSolid() && isHandTranslucent) {
|
||||
ci.cancel();
|
||||
BackhandReflectionCompat.renderOffhand(partialTicks);
|
||||
if (ModStatus.isBackhandLoaded){
|
||||
iris$skipTranslucentHandsBackhand(partialTicks, ci);
|
||||
ModStatus.backhandCompat.renderOffhand(partialTicks);
|
||||
}
|
||||
} else if (!HandRenderer.INSTANCE.isRenderingSolid() && !isHandTranslucent) {
|
||||
ci.cancel();
|
||||
BackhandReflectionCompat.renderOffhand(partialTicks);
|
||||
if (ModStatus.isBackhandLoaded){
|
||||
iris$skipTranslucentHandsBackhand(partialTicks, ci);
|
||||
ModStatus.backhandCompat.renderOffhand(partialTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +36,7 @@ public class MixinItemRendererBackhand {
|
||||
@Inject(method = "renderItemInFirstPerson", at = @At("RETURN"), cancellable = true, order = 900)
|
||||
private void iris$skipTranslucentHandsBackhand(float partialTicks, CallbackInfo ci) {
|
||||
if (ModStatus.isBackhandLoaded && IrisApi.getInstance().isShaderPackInUse()) {
|
||||
final boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.OFF_HAND);
|
||||
boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.OFF_HAND);
|
||||
if (HandRenderer.INSTANCE.isRenderingSolid() && isHandTranslucent) {
|
||||
ci.cancel();
|
||||
} else if (!HandRenderer.INSTANCE.isRenderingSolid() && !isHandTranslucent) {
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaExtension;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(AngelicaExtension.class)
|
||||
class FeedbackManagerTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
FeedbackManager.reset();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
// Ensure we're back in render mode
|
||||
if (FeedbackManager.isFeedbackMode()) {
|
||||
FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
}
|
||||
FeedbackManager.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStateMachine_noDraws_returnsZero() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
|
||||
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
|
||||
assertTrue(FeedbackManager.isFeedbackMode());
|
||||
|
||||
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
assertEquals(0, result, "No draws should produce 0 tokens");
|
||||
assertFalse(FeedbackManager.isFeedbackMode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOverflow_returnsNegative() {
|
||||
// Buffer with only 2 floats — any triangle will overflow
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(2);
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
|
||||
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
|
||||
// Set up identity matrices and a viewport
|
||||
GLStateManager.getModelViewMatrix().identity();
|
||||
GLStateManager.getProjectionMatrix().identity();
|
||||
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
|
||||
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
|
||||
|
||||
// Upload 3 vertices to a VBO and draw
|
||||
final int vao = GL30.glGenVertexArrays();
|
||||
GL30.glBindVertexArray(vao);
|
||||
|
||||
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
|
||||
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
|
||||
vertBuf.put(verts).flip();
|
||||
|
||||
final int vbo = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
|
||||
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
|
||||
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
assertEquals(-1, result, "Overflow should return -1");
|
||||
|
||||
// Cleanup
|
||||
GL20.glDisableVertexAttribArray(0);
|
||||
GL30.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GL30.glDeleteVertexArrays(vao);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPassThrough() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
|
||||
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
|
||||
FeedbackManager.glPassThrough(42.0f);
|
||||
|
||||
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
assertEquals(2, result, "PassThrough should write 2 floats");
|
||||
assertEquals(GL11.GL_PASS_THROUGH_TOKEN, buf.get(0), "First token should be PASS_THROUGH_TOKEN");
|
||||
assertEquals(42.0f, buf.get(1), "Second token should be the pass-through value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTriangleFeedback_GL2D() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
|
||||
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
|
||||
// Identity MV/proj, viewport (0,0,800,600)
|
||||
GLStateManager.getModelViewMatrix().identity();
|
||||
GLStateManager.getProjectionMatrix().identity();
|
||||
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
|
||||
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
|
||||
|
||||
// Upload 3 vertices forming a triangle
|
||||
final int vao = GL30.glGenVertexArrays();
|
||||
GL30.glBindVertexArray(vao);
|
||||
|
||||
// NDC vertices: (0,0,0), (1,0,0), (0,1,0)
|
||||
// With identity MVP and viewport 800x600:
|
||||
// NDC(0,0) → window(400, 300)
|
||||
// NDC(1,0) → window(800, 300)
|
||||
// NDC(0,1) → window(400, 600)
|
||||
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
|
||||
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
|
||||
vertBuf.put(verts).flip();
|
||||
|
||||
final int vbo = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
|
||||
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
|
||||
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
|
||||
// GL_2D: POLYGON_TOKEN, 3, x0, y0, x1, y1, x2, y2 = 8 floats
|
||||
assertEquals(8, count, "Triangle in GL_2D should produce 8 floats");
|
||||
|
||||
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
|
||||
assertEquals(3.0f, buf.get(1), 0.001f);
|
||||
|
||||
// Vertex 0: NDC(0,0) → window(400, 300)
|
||||
assertEquals(400.0f, buf.get(2), 0.01f);
|
||||
assertEquals(300.0f, buf.get(3), 0.01f);
|
||||
|
||||
// Vertex 1: NDC(1,0) → window(800, 300)
|
||||
assertEquals(800.0f, buf.get(4), 0.01f);
|
||||
assertEquals(300.0f, buf.get(5), 0.01f);
|
||||
|
||||
// Vertex 2: NDC(0,1) → window(400, 600)
|
||||
assertEquals(400.0f, buf.get(6), 0.01f);
|
||||
assertEquals(600.0f, buf.get(7), 0.01f);
|
||||
|
||||
// Cleanup
|
||||
GL20.glDisableVertexAttribArray(0);
|
||||
GL30.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GL30.glDeleteVertexArrays(vao);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIdentityTransform_viewportMapping() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_3D, buf);
|
||||
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
|
||||
GLStateManager.getModelViewMatrix().identity();
|
||||
GLStateManager.getProjectionMatrix().identity();
|
||||
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
|
||||
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
|
||||
|
||||
final int vao = GL30.glGenVertexArrays();
|
||||
GL30.glBindVertexArray(vao);
|
||||
|
||||
// Single point at NDC origin (0,0,0)
|
||||
final float[] verts = { 0f, 0f, 0f };
|
||||
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(3);
|
||||
vertBuf.put(verts).flip();
|
||||
|
||||
final int vbo = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
|
||||
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
|
||||
GL20.glEnableVertexAttribArray(0);
|
||||
|
||||
FeedbackManager.processDrawArrays(GL11.GL_POINTS, 0, 1);
|
||||
|
||||
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
|
||||
// GL_3D point: POINT_TOKEN, x, y, z = 4 floats
|
||||
assertEquals(4, count);
|
||||
assertEquals(GL11.GL_POINT_TOKEN, buf.get(0), 0.001f);
|
||||
// NDC(0,0,0) → window center (400, 300, 0.5)
|
||||
assertEquals(400.0f, buf.get(1), 0.01f);
|
||||
assertEquals(300.0f, buf.get(2), 0.01f);
|
||||
assertEquals(0.5f, buf.get(3), 0.01f);
|
||||
|
||||
// Cleanup
|
||||
GL20.glDisableVertexAttribArray(0);
|
||||
GL30.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GL30.glDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
@@ -457,23 +457,18 @@ public class GLSM_ServerSideState_UnitTest {
|
||||
|
||||
assertTrue(GL11.glIsTexture(texId), "Texture should exist before delete");
|
||||
|
||||
// SharedDrawable: delete the texture (deferred — name stays valid)
|
||||
// SharedDrawable: delete the texture
|
||||
executeOnSharedDrawable(() -> {
|
||||
GLStateManager.glDeleteTextures(texId);
|
||||
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid (deferred delete)");
|
||||
assertFalse(GL11.glIsTexture(texId), "Texture should not exist after SharedDrawable delete");
|
||||
});
|
||||
|
||||
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid on main thread (deferred delete)");
|
||||
|
||||
// glGenTextures flushes deferred deletes
|
||||
int newTexId = GLStateManager.glGenTextures();
|
||||
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after flush via glGenTextures");
|
||||
GL11.glDeleteTextures(newTexId);
|
||||
assertFalse(GL11.glIsTexture(texId), "Texture should not exist on main thread after SharedDrawable delete");
|
||||
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
if (GL11.glIsTexture(texId)) {
|
||||
GL11.glDeleteTextures(texId);
|
||||
GLStateManager.glDeleteTextures(texId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class DeferredDrawBatcherTest {
|
||||
|
||||
@Test
|
||||
void stateKey_roundtrip_basic() {
|
||||
long key = DeferredDrawBatcher.packStateKey(42, GL11.GL_SRC_ALPHA, GL11.GL_ONE, true, false, true, true);
|
||||
assertEquals(42, DeferredDrawBatcher.unpackTextureId(key));
|
||||
assertEquals(GL11.GL_SRC_ALPHA, DeferredDrawBatcher.unpackSrcRgb(key));
|
||||
assertEquals(GL11.GL_ONE, DeferredDrawBatcher.unpackDstRgb(key));
|
||||
assertTrue(DeferredDrawBatcher.unpackBlendEnabled(key));
|
||||
assertFalse(DeferredDrawBatcher.unpackDepthMask(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateKey_roundtrip_allFlags() {
|
||||
long key = DeferredDrawBatcher.packStateKey(0, 0, 0, false, true, true, true);
|
||||
assertEquals(0, DeferredDrawBatcher.unpackTextureId(key));
|
||||
assertEquals(0, DeferredDrawBatcher.unpackSrcRgb(key));
|
||||
assertEquals(0, DeferredDrawBatcher.unpackDstRgb(key));
|
||||
assertFalse(DeferredDrawBatcher.unpackBlendEnabled(key));
|
||||
assertTrue(DeferredDrawBatcher.unpackDepthMask(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateKey_roundtrip_largeTextureId() {
|
||||
// 20-bit texture ID = max 1048575
|
||||
int texId = (1 << 20) - 1;
|
||||
long key = DeferredDrawBatcher.packStateKey(texId, 0x0302, 0x0303, true, true, true, true);
|
||||
assertEquals(texId, DeferredDrawBatcher.unpackTextureId(key));
|
||||
assertEquals(0x0302, DeferredDrawBatcher.unpackSrcRgb(key));
|
||||
assertEquals(0x0303, DeferredDrawBatcher.unpackDstRgb(key));
|
||||
assertTrue(DeferredDrawBatcher.unpackBlendEnabled(key));
|
||||
assertTrue(DeferredDrawBatcher.unpackDepthMask(key));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateKey_differentKeysNotEqual() {
|
||||
long keyA = DeferredDrawBatcher.packStateKey(42, GL11.GL_SRC_ALPHA, GL11.GL_ONE, true, false, true, true);
|
||||
long keyB = DeferredDrawBatcher.packStateKey(43, GL11.GL_SRC_ALPHA, GL11.GL_ONE, true, false, true, true);
|
||||
long keyC = DeferredDrawBatcher.packStateKey(42, GL11.GL_ONE, GL11.GL_ONE, true, false, true, true);
|
||||
assertNotEquals(keyA, keyB, "Different texture IDs should produce different keys");
|
||||
assertNotEquals(keyA, keyC, "Different blend funcs should produce different keys");
|
||||
}
|
||||
|
||||
@Test
|
||||
void grouping_sameKeyEntriesMerged() {
|
||||
// Verify that entries with the same state key sort together
|
||||
long keyA = DeferredDrawBatcher.packStateKey(1, 0x0302, 0x0001, true, true, true, true);
|
||||
long keyB = DeferredDrawBatcher.packStateKey(2, 0x0302, 0x0001, true, true, true, true);
|
||||
|
||||
// Create entries with interleaved keys: A, B, A, B, A
|
||||
final int flags = VertexFlags.convertToFlags(true, true, false, true);
|
||||
var entries = new java.util.ArrayList<DeferredBatchTessellator.DrawRange>();
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(keyA, 0, 128, 4, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(keyB, 128, 128, 4, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(keyA, 256, 128, 4, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(keyB, 384, 128, 4, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(keyA, 512, 128, 4, GL11.GL_QUADS, flags));
|
||||
|
||||
// Sort by state key (same as exitAndFlush)
|
||||
entries.sort((a, b) -> Long.compare(a.stateKey(), b.stateKey()));
|
||||
|
||||
// Count groups
|
||||
int groupCount = 0;
|
||||
int[] groupSizes = new int[2];
|
||||
long currentKey = entries.get(0).stateKey();
|
||||
int groupSize = 0;
|
||||
|
||||
for (int i = 0; i <= entries.size(); i++) {
|
||||
if (i == entries.size() || entries.get(i).stateKey() != currentKey) {
|
||||
groupSizes[groupCount] = groupSize;
|
||||
groupCount++;
|
||||
if (i < entries.size()) {
|
||||
currentKey = entries.get(i).stateKey();
|
||||
groupSize = 1;
|
||||
}
|
||||
} else {
|
||||
groupSize++;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(2, groupCount, "Should have exactly 2 groups");
|
||||
// keyA < keyB (texture ID 1 vs 2), so keyA group comes first
|
||||
assertEquals(3, groupSizes[0], "Key A group should have 3 entries");
|
||||
assertEquals(2, groupSizes[1], "Key B group should have 2 entries");
|
||||
}
|
||||
|
||||
@Test
|
||||
void grouping_mergedVertexCountCorrect() {
|
||||
long key = DeferredDrawBatcher.packStateKey(1, 0x0302, 0x0001, true, true, true, true);
|
||||
|
||||
final int flags = VertexFlags.convertToFlags(true, true, false, true);
|
||||
var entries = new java.util.ArrayList<DeferredBatchTessellator.DrawRange>();
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(key, 0, 128, 4, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(key, 128, 256, 8, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(key, 384, 128, 4, GL11.GL_QUADS, flags));
|
||||
|
||||
// Simulate merge within a group: sum vertices and byte lengths
|
||||
int totalVertices = 0;
|
||||
int totalBytes = 0;
|
||||
for (var e : entries) {
|
||||
totalVertices += e.vertexCount();
|
||||
totalBytes += e.byteLength();
|
||||
}
|
||||
|
||||
assertEquals(16, totalVertices, "Merged group should have 4+8+4=16 vertices");
|
||||
assertEquals(512, totalBytes, "Merged group should have 128+256+128=512 bytes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void grouping_differentDrawModesFormSubgroups() {
|
||||
long key = DeferredDrawBatcher.packStateKey(1, 0x0302, 0x0001, true, true, true, true);
|
||||
|
||||
final int flags = VertexFlags.convertToFlags(true, true, false, true);
|
||||
var entries = new java.util.ArrayList<DeferredBatchTessellator.DrawRange>();
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(key, 0, 128, 4, GL11.GL_QUADS, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(key, 128, 128, 4, GL11.GL_TRIANGLES, flags));
|
||||
entries.add(new DeferredBatchTessellator.DrawRange(key, 256, 128, 4, GL11.GL_QUADS, flags));
|
||||
|
||||
// Same state key, but different draw modes can't be merged.
|
||||
// The flush logic subgroups by (drawMode, format flags).
|
||||
// Count subgroups: QUADS(entry0), TRIANGLES(entry1), QUADS(entry2)
|
||||
// Since entries aren't further sorted by drawMode, this produces 3 subgroups.
|
||||
int subgroupCount = 0;
|
||||
int i = 0;
|
||||
while (i < entries.size()) {
|
||||
int drawMode = entries.get(i).drawMode();
|
||||
while (i < entries.size() && entries.get(i).drawMode() == drawMode) i++;
|
||||
subgroupCount++;
|
||||
}
|
||||
|
||||
// Two QUADS entries are not contiguous (TRIANGLES between them), so 3 subgroups
|
||||
assertEquals(3, subgroupCount, "Different draw modes should form separate subgroups");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateKey_blendConstants() {
|
||||
// Verify all common GL blend constants roundtrip correctly
|
||||
int[] blendConstants = {
|
||||
GL11.GL_ZERO, // 0x0000
|
||||
GL11.GL_ONE, // 0x0001
|
||||
GL11.GL_SRC_ALPHA, // 0x0302
|
||||
GL11.GL_ONE_MINUS_SRC_ALPHA, // 0x0303
|
||||
GL11.GL_DST_ALPHA, // 0x0304
|
||||
GL11.GL_ONE_MINUS_DST_ALPHA, // 0x0305
|
||||
};
|
||||
|
||||
for (int src : blendConstants) {
|
||||
for (int dst : blendConstants) {
|
||||
long key = DeferredDrawBatcher.packStateKey(1, src, dst, true, true, true, true);
|
||||
assertEquals(src, DeferredDrawBatcher.unpackSrcRgb(key), "srcRgb roundtrip failed for 0x" + Integer.toHexString(src));
|
||||
assertEquals(dst, DeferredDrawBatcher.unpackDstRgb(key), "dstRgb roundtrip failed for 0x" + Integer.toHexString(dst));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void stateKey_textureEnableRoundtrip() {
|
||||
// tex0=true, tex1=false
|
||||
long key1 = DeferredDrawBatcher.packStateKey(42, 0x0302, 0x0001, true, true, true, false);
|
||||
assertTrue(DeferredDrawBatcher.unpackTex0Enabled(key1));
|
||||
assertFalse(DeferredDrawBatcher.unpackTex1Enabled(key1));
|
||||
assertEquals(42, DeferredDrawBatcher.unpackTextureId(key1));
|
||||
|
||||
// tex0=false, tex1=true
|
||||
long key2 = DeferredDrawBatcher.packStateKey(42, 0x0302, 0x0001, true, true, false, true);
|
||||
assertFalse(DeferredDrawBatcher.unpackTex0Enabled(key2));
|
||||
assertTrue(DeferredDrawBatcher.unpackTex1Enabled(key2));
|
||||
assertEquals(42, DeferredDrawBatcher.unpackTextureId(key2));
|
||||
|
||||
// Enable bits don't interfere with existing fields
|
||||
long keyBothOff = DeferredDrawBatcher.packStateKey(42, 0x0302, 0x0001, true, true, false, false);
|
||||
long keyBothOn = DeferredDrawBatcher.packStateKey(42, 0x0302, 0x0001, true, true, true, true);
|
||||
assertEquals(DeferredDrawBatcher.unpackTextureId(keyBothOff), DeferredDrawBatcher.unpackTextureId(keyBothOn));
|
||||
assertEquals(DeferredDrawBatcher.unpackSrcRgb(keyBothOff), DeferredDrawBatcher.unpackSrcRgb(keyBothOn));
|
||||
assertEquals(DeferredDrawBatcher.unpackDstRgb(keyBothOff), DeferredDrawBatcher.unpackDstRgb(keyBothOn));
|
||||
assertEquals(DeferredDrawBatcher.unpackBlendEnabled(keyBothOff), DeferredDrawBatcher.unpackBlendEnabled(keyBothOn));
|
||||
assertEquals(DeferredDrawBatcher.unpackDepthMask(keyBothOff), DeferredDrawBatcher.unpackDepthMask(keyBothOn));
|
||||
|
||||
// Different tex enable states produce different keys
|
||||
assertNotEquals(key1, key2, "Different texture enable states should produce different keys");
|
||||
}
|
||||
|
||||
@Test
|
||||
void drawRange_byteOffsetsNonOverlapping() {
|
||||
long key = DeferredDrawBatcher.packStateKey(1, 0x0302, 0x0001, true, true, true, true);
|
||||
final int flags = VertexFlags.convertToFlags(true, true, false, true);
|
||||
|
||||
// Simulate sequential byte offsets as would be produced by interceptDraw
|
||||
var ranges = new java.util.ArrayList<DeferredBatchTessellator.DrawRange>();
|
||||
int offset = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int byteLen = (i + 1) * 128; // 128, 256, 384, 512, 640
|
||||
ranges.add(new DeferredBatchTessellator.DrawRange(key, offset, byteLen, (i + 1) * 4, GL11.GL_QUADS, flags));
|
||||
offset += byteLen;
|
||||
}
|
||||
|
||||
// Verify ranges are contiguous and non-overlapping
|
||||
for (int i = 1; i < ranges.size(); i++) {
|
||||
var prev = ranges.get(i - 1);
|
||||
var curr = ranges.get(i);
|
||||
assertEquals(prev.byteOffset() + prev.byteLength(), curr.byteOffset(), "Range " + i + " should start where range " + (i - 1) + " ends");
|
||||
}
|
||||
|
||||
// Total bytes = 128+256+384+512+640 = 1920
|
||||
assertEquals(1920, offset, "Total accumulated bytes");
|
||||
}
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaExtension;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Tests for FFP TexEnv COMBINE / multi-unit features:
|
||||
* TexEnvState tracking, FragmentKey packing, FragmentShaderGenerator COMBINE codegen, and glMultiTexCoord routing.
|
||||
*/
|
||||
@ExtendWith(AngelicaExtension.class)
|
||||
class FFPCombineShaderTest {
|
||||
|
||||
private final List<Integer> shadersToDelete = new ArrayList<>();
|
||||
private final List<Integer> programsToDelete = new ArrayList<>();
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
// Delete GL resources
|
||||
for (int p : programsToDelete) GL20.glDeleteProgram(p);
|
||||
for (int s : shadersToDelete) GL20.glDeleteShader(s);
|
||||
programsToDelete.clear();
|
||||
shadersToDelete.clear();
|
||||
|
||||
// Reset texture units to defaults
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
GLStateManager.getTextures().getTexEnvState(i).reset();
|
||||
if (i > 0) {
|
||||
GLStateManager.getTextures().getTextureUnitStates(i).disable();
|
||||
}
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTexEnvStateTracking() {
|
||||
// Unit 0: COMBINE mode, SUBTRACT RGB, REPLACE alpha
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_ONE_MINUS_SRC_COLOR);
|
||||
|
||||
TexEnvState env0 = GLStateManager.getTextures().getTexEnvState(0);
|
||||
assertEquals(GL13.GL_COMBINE, env0.mode);
|
||||
assertEquals(GL13.GL_SUBTRACT, env0.combineRgb);
|
||||
assertEquals(GL11.GL_REPLACE, env0.combineAlpha);
|
||||
assertEquals(GL11.GL_TEXTURE, env0.sourceRgb[0]);
|
||||
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, env0.operandRgb[0]);
|
||||
assertEquals(GL13.GL_PRIMARY_COLOR, env0.sourceRgb[1]);
|
||||
assertEquals(GL11.GL_ONE_MINUS_SRC_COLOR, env0.operandRgb[1]);
|
||||
|
||||
// Unit 2: independent state
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
|
||||
|
||||
TexEnvState env2 = GLStateManager.getTextures().getTexEnvState(2);
|
||||
assertEquals(GL13.GL_COMBINE, env2.mode);
|
||||
assertEquals(GL11.GL_ADD, env2.combineRgb);
|
||||
|
||||
// Verify unit 0 unchanged
|
||||
assertEquals(GL13.GL_SUBTRACT, env0.combineRgb, "Unit 0 should be independent of unit 2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFragmentKeyMultiUnitCombine() {
|
||||
setupXaeroState();
|
||||
|
||||
FragmentKey fk = FragmentKey.fromState();
|
||||
|
||||
assertEquals(4, fk.nrEnabledUnits(), "highestEnabled is unit 3 → nrEnabledUnits=4");
|
||||
assertTrue(fk.unitEnabled(0));
|
||||
assertFalse(fk.unitEnabled(1), "unit 1 should be disabled");
|
||||
assertTrue(fk.unitEnabled(2));
|
||||
assertTrue(fk.unitEnabled(3));
|
||||
|
||||
assertEquals(FragmentKey.TEX_ENV_COMBINE, fk.unitMode(0));
|
||||
assertEquals(FragmentKey.COMBINE_SUBTRACT, fk.unitCombineRgb(0));
|
||||
|
||||
assertEquals(FragmentKey.TEX_ENV_COMBINE, fk.unitMode(2));
|
||||
assertEquals(FragmentKey.COMBINE_ADD, fk.unitCombineRgb(2));
|
||||
|
||||
assertEquals(FragmentKey.TEX_ENV_MODULATE, fk.unitMode(3));
|
||||
|
||||
// Verify source/operand on unit 0
|
||||
assertEquals(FragmentKey.SRC_TEXTURE, fk.unitSourceRgb(0, 0));
|
||||
assertEquals(FragmentKey.OP_SRC_COLOR, fk.unitOperandRgb(0, 0));
|
||||
assertEquals(FragmentKey.SRC_PRIMARY_COLOR, fk.unitSourceRgb(0, 1));
|
||||
assertEquals(FragmentKey.OP_SRC_COLOR, fk.unitOperandRgb(0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingleUnitCombineCompiles() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
|
||||
|
||||
FragmentKey fk = FragmentKey.fromState();
|
||||
String fragSource = FragmentShaderGenerator.generate(fk);
|
||||
int shader = compileShader(fragSource, GL20.GL_FRAGMENT_SHADER);
|
||||
assertTrue(shader > 0, "Single-unit COMBINE fragment shader should compile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testXaeroMultiUnitCombineCompiles() {
|
||||
setupXaeroState();
|
||||
|
||||
FragmentKey fk = FragmentKey.fromState();
|
||||
VertexKey vk = VertexKey.fromState(true, false, true, false);
|
||||
|
||||
String fragSource = FragmentShaderGenerator.generate(fk);
|
||||
String vertSource = VertexShaderGenerator.generate(vk);
|
||||
|
||||
int vs = compileShader(vertSource, GL20.GL_VERTEX_SHADER);
|
||||
int fs = compileShader(fragSource, GL20.GL_FRAGMENT_SHADER);
|
||||
|
||||
int prog = GL20.glCreateProgram();
|
||||
programsToDelete.add(prog);
|
||||
GL20.glAttachShader(prog, vs);
|
||||
GL20.glAttachShader(prog, fs);
|
||||
GL20.glLinkProgram(prog);
|
||||
|
||||
int linkStatus = GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS);
|
||||
if (linkStatus == 0) {
|
||||
String log = GL20.glGetProgramInfoLog(prog, 4096);
|
||||
fail("Xaero multi-unit program link failed:\n" + log
|
||||
+ "\n\n--- Vertex Shader ---\n" + vertSource
|
||||
+ "\n\n--- Fragment Shader ---\n" + fragSource);
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Arguments> allCombineFunctions() {
|
||||
return Stream.of(
|
||||
Arguments.of("REPLACE", GL11.GL_REPLACE, 1),
|
||||
Arguments.of("MODULATE", GL11.GL_MODULATE, 2),
|
||||
Arguments.of("ADD", GL11.GL_ADD, 2),
|
||||
Arguments.of("ADD_SIGNED", GL13.GL_ADD_SIGNED, 2),
|
||||
Arguments.of("SUBTRACT", GL13.GL_SUBTRACT, 2),
|
||||
Arguments.of("INTERPOLATE", GL13.GL_INTERPOLATE, 3),
|
||||
Arguments.of("DOT3_RGB", GL13.GL_DOT3_RGB, 2),
|
||||
Arguments.of("DOT3_RGBA", GL13.GL_DOT3_RGBA, 2)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "COMBINE function {0}")
|
||||
@MethodSource("allCombineFunctions")
|
||||
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
|
||||
|
||||
// Set up appropriate sources
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
if (numArgs >= 2) {
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_ALPHA, GL13.GL_PREVIOUS);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
}
|
||||
if (numArgs >= 3) {
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE2_RGB, GL13.GL_PREVIOUS);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE2_ALPHA, GL13.GL_PREVIOUS);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
}
|
||||
|
||||
FragmentKey fk = FragmentKey.fromState();
|
||||
String fragSource = FragmentShaderGenerator.generate(fk);
|
||||
int shader = compileShader(fragSource, GL20.GL_FRAGMENT_SHADER);
|
||||
assertTrue(shader > 0, "COMBINE function " + name + " should compile");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCombineScopeDeclaresColorOutsideBlock() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
|
||||
|
||||
FragmentKey fk = FragmentKey.fromState();
|
||||
String src = FragmentShaderGenerator.generate(fk);
|
||||
|
||||
// Result variables declared outside the scope block
|
||||
assertTrue(src.contains("vec3 combRgb0;"), "combRgb0 should be declared outside scope block");
|
||||
assertTrue(src.contains("float combAlpha0;"), "combAlpha0 should be declared outside scope block");
|
||||
|
||||
// color assignment uses the result variables outside the block
|
||||
assertTrue(src.contains("vec4(combRgb0, combAlpha0)"), "color should be assigned from combine results outside block");
|
||||
|
||||
// The scope block uses { } to contain arg declarations
|
||||
assertTrue(src.contains(" {\n"), "Should have scope block for arg declarations");
|
||||
|
||||
// Bonus: verify it compiles
|
||||
int shader = compileShader(src, GL20.GL_FRAGMENT_SHADER);
|
||||
assertTrue(shader > 0, "Scope-correct COMBINE shader should compile");
|
||||
}
|
||||
|
||||
// ---- Test 7: glMultiTexCoord2f routes to ShaderManager.currentTexCoord ----
|
||||
|
||||
@Test
|
||||
void testMultiTexCoordRoutesToShaderManager() {
|
||||
// Not in immediate mode or display list recording, so should go to ShaderManager.currentTexCoord
|
||||
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.5f, 0.75f);
|
||||
|
||||
var texCoord = ShaderManager.getCurrentTexCoord();
|
||||
assertEquals(0.5f, texCoord.x, 1e-6f, "s coordinate should be 0.5");
|
||||
assertEquals(0.75f, texCoord.y, 1e-6f, "t coordinate should be 0.75");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiTexCoord2dDelegatesToFloat() {
|
||||
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glMultiTexCoord2d(GL13.GL_TEXTURE0, 0.5, 0.75);
|
||||
|
||||
var texCoord = ShaderManager.getCurrentTexCoord();
|
||||
assertEquals(0.5f, texCoord.x, 1e-6f, "double 0.5 should arrive as float 0.5");
|
||||
assertEquals(0.75f, texCoord.y, 1e-6f, "double 0.75 should arrive as float 0.75");
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the 4-unit Xaero-like combine chain:
|
||||
* Unit 0: COMBINE, RGB=SUBTRACT, SRC0=TEXTURE/SRC_COLOR, SRC1=PRIMARY_COLOR/SRC_COLOR
|
||||
* Unit 1: disabled
|
||||
* Unit 2: COMBINE, RGB=ADD, SRC0=TEXTURE/SRC_COLOR, SRC1=PREVIOUS/SRC_COLOR
|
||||
* Unit 3: MODULATE (simple)
|
||||
*/
|
||||
private void setupXaeroState() {
|
||||
// Unit 0: COMBINE / SUBTRACT
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
// Unit 1: disabled (skip — default is disabled)
|
||||
|
||||
// Unit 2: COMBINE / ADD
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PREVIOUS);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_ALPHA, GL11.GL_TEXTURE);
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
|
||||
// Unit 3: simple MODULATE
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
|
||||
|
||||
// Restore active unit
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
private int compileShader(String source, int type) {
|
||||
int shader = GL20.glCreateShader(type);
|
||||
shadersToDelete.add(shader);
|
||||
GL20.glShaderSource(shader, source);
|
||||
GL20.glCompileShader(shader);
|
||||
int status = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS);
|
||||
if (status == 0) {
|
||||
String log = GL20.glGetShaderInfoLog(shader, 4096);
|
||||
fail("Shader compilation failed:\n" + log + "\n\n--- Source ---\n" + source);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,9 @@ package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaExtension;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.OrphanStreamingBuffer.UploadStrategy;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
@@ -20,212 +17,73 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
@ExtendWith(AngelicaExtension.class)
|
||||
class StreamingBufferTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(UploadStrategy.class)
|
||||
void orphan_firstUploadReturnsZero(UploadStrategy strategy) {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
|
||||
@Test
|
||||
void orphan_uploadReturnsZero() {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer();
|
||||
try {
|
||||
ByteBuffer data = memAlloc(128);
|
||||
fillPattern(data);
|
||||
data.flip();
|
||||
assertEquals(0, buf.upload(data, 32), "First upload should return offset 0");
|
||||
assertEquals(0, buf.upload(data, 32));
|
||||
memFree(data);
|
||||
} finally {
|
||||
buf.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(UploadStrategy.class)
|
||||
void orphan_dataIntegrity(UploadStrategy strategy) {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
|
||||
@Test
|
||||
void orphan_dataIntegrity() {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer();
|
||||
try {
|
||||
ByteBuffer data = memAlloc(64);
|
||||
ByteBuffer readBack = null;
|
||||
try {
|
||||
for (int i = 0; i < 64; i++) data.put((byte) i);
|
||||
data.flip();
|
||||
for (int i = 0; i < 64; i++) data.put((byte) i);
|
||||
data.flip();
|
||||
|
||||
buf.upload(data, 16);
|
||||
buf.upload(data, 16);
|
||||
|
||||
// Read back via glGetBufferSubData
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
|
||||
readBack = memAlloc(64);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
// Read back via glGetBufferSubData
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
|
||||
ByteBuffer readBack = memAlloc(64);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
for (int i = 0; i < 64; i++) {
|
||||
assertEquals((byte) i, readBack.get(i), "Mismatch at byte " + i);
|
||||
}
|
||||
} finally {
|
||||
memFree(data);
|
||||
if (readBack != null) memFree(readBack);
|
||||
for (int i = 0; i < 64; i++) {
|
||||
assertEquals((byte) i, readBack.get(i), "Mismatch at byte " + i);
|
||||
}
|
||||
memFree(readBack);
|
||||
} finally {
|
||||
buf.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(UploadStrategy.class)
|
||||
void orphan_multipleUploadsSubAllocate(UploadStrategy strategy) {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
|
||||
@Test
|
||||
void orphan_multipleUploadsGrowCapacity() {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer();
|
||||
try {
|
||||
final int stride = 16;
|
||||
// First upload: small
|
||||
ByteBuffer small = memAlloc(64);
|
||||
fillPattern(small);
|
||||
small.flip();
|
||||
buf.upload(small, 16);
|
||||
memFree(small);
|
||||
|
||||
// Second upload: larger
|
||||
ByteBuffer big = memAlloc(256);
|
||||
ByteBuffer readBack = null;
|
||||
try {
|
||||
// First upload: small
|
||||
fillPattern(small);
|
||||
small.flip();
|
||||
int v1 = buf.upload(small, stride);
|
||||
assertEquals(0, v1, "First upload should be at offset 0");
|
||||
fillPattern(big);
|
||||
big.flip();
|
||||
buf.upload(big, 16);
|
||||
memFree(big);
|
||||
|
||||
// Second upload: larger — sub-allocates after first (64KB min capacity)
|
||||
fillPattern(big);
|
||||
big.flip();
|
||||
int v2 = buf.upload(big, stride);
|
||||
assertTrue(v2 > 0, "Second upload should sub-allocate at non-zero offset");
|
||||
// Verify larger data is present
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
|
||||
ByteBuffer readBack = memAlloc(256);
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Verify second upload's data at its offset
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
|
||||
readBack = memAlloc(buf.getCapacity());
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
int byteOffset = v2 * stride;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
assertEquals((byte) (i & 0xFF), readBack.get(byteOffset + i), "Mismatch at byte " + i);
|
||||
}
|
||||
} finally {
|
||||
memFree(small);
|
||||
memFree(big);
|
||||
if (readBack != null) memFree(readBack);
|
||||
}
|
||||
} finally {
|
||||
buf.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(UploadStrategy.class)
|
||||
void orphan_subAllocationReturnsIncreasingOffsets(UploadStrategy strategy) {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
|
||||
try {
|
||||
final int stride = 16;
|
||||
final int dataSize = 64; // 4 vertices worth
|
||||
|
||||
ByteBuffer data1 = memAlloc(dataSize);
|
||||
ByteBuffer data2 = memAlloc(dataSize);
|
||||
ByteBuffer data3 = memAlloc(dataSize);
|
||||
ByteBuffer readBack = null;
|
||||
try {
|
||||
// First upload: offset 0
|
||||
for (int i = 0; i < dataSize; i++) data1.put((byte) (i + 10));
|
||||
data1.flip();
|
||||
int v1 = buf.upload(data1, stride);
|
||||
assertEquals(0, v1, "First upload should be at vertex 0");
|
||||
|
||||
// Second upload: should be at aligned offset after first
|
||||
for (int i = 0; i < dataSize; i++) data2.put((byte) (i + 20));
|
||||
data2.flip();
|
||||
int v2 = buf.upload(data2, stride);
|
||||
assertTrue(v2 > 0, "Second upload should be at a non-zero vertex offset");
|
||||
assertEquals(0, v2 % 4, "Vertex offset must be quad-aligned (multiple of 4)");
|
||||
|
||||
// Third upload
|
||||
for (int i = 0; i < dataSize; i++) data3.put((byte) (i + 30));
|
||||
data3.flip();
|
||||
int v3 = buf.upload(data3, stride);
|
||||
assertTrue(v3 > v2, "Third upload should be after second");
|
||||
assertEquals(0, v3 % 4, "Vertex offset must be quad-aligned");
|
||||
|
||||
// Verify all three regions have correct data
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buf.getBufferId());
|
||||
readBack = memAlloc(buf.getCapacity());
|
||||
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, 0, readBack);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
assertEquals((byte) (i + 10), readBack.get(v1 * stride + i), "Upload 1 mismatch at byte " + i);
|
||||
assertEquals((byte) (i + 20), readBack.get(v2 * stride + i), "Upload 2 mismatch at byte " + i);
|
||||
assertEquals((byte) (i + 30), readBack.get(v3 * stride + i), "Upload 3 mismatch at byte " + i);
|
||||
}
|
||||
} finally {
|
||||
memFree(data1);
|
||||
memFree(data2);
|
||||
memFree(data3);
|
||||
if (readBack != null) memFree(readBack);
|
||||
}
|
||||
} finally {
|
||||
buf.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(UploadStrategy.class)
|
||||
void orphan_overflowResetsToZero(UploadStrategy strategy) {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
|
||||
try {
|
||||
final int stride = 16;
|
||||
|
||||
// First upload: establishes capacity
|
||||
ByteBuffer data1 = memAlloc(64);
|
||||
try {
|
||||
fillPattern(data1);
|
||||
data1.flip();
|
||||
buf.upload(data1, stride);
|
||||
} finally {
|
||||
memFree(data1);
|
||||
}
|
||||
|
||||
int capacityAfterFirst = buf.getCapacity();
|
||||
|
||||
// Second upload: fills remaining space or overflows
|
||||
ByteBuffer data2 = memAlloc(capacityAfterFirst); // exactly fills or overflows
|
||||
try {
|
||||
fillPattern(data2);
|
||||
data2.flip();
|
||||
int v2 = buf.upload(data2, stride);
|
||||
// Either sub-allocated (if it fit after alignment) or reset to 0 (overflow)
|
||||
assertEquals(0, v2 % 4, "Offset must be quad-aligned");
|
||||
} finally {
|
||||
memFree(data2);
|
||||
}
|
||||
} finally {
|
||||
buf.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(UploadStrategy.class)
|
||||
void orphan_postDrawResetsForNextFrame(UploadStrategy strategy) {
|
||||
OrphanStreamingBuffer buf = new OrphanStreamingBuffer(strategy);
|
||||
try {
|
||||
final int stride = 16;
|
||||
|
||||
ByteBuffer data = memAlloc(64);
|
||||
try {
|
||||
// Frame 1: two uploads
|
||||
fillPattern(data);
|
||||
data.flip();
|
||||
buf.upload(data, stride);
|
||||
data.position(0).limit(64);
|
||||
int v2 = buf.upload(data, stride);
|
||||
assertTrue(v2 > 0, "Second upload in frame should be at non-zero offset");
|
||||
|
||||
// End of frame
|
||||
buf.postDraw();
|
||||
assertEquals(0, buf.getWritePos(), "writePos should reset after postDraw");
|
||||
|
||||
// Frame 2: first upload should be at offset 0 again (after orphan)
|
||||
data.position(0).limit(64);
|
||||
int v3 = buf.upload(data, stride);
|
||||
assertEquals(0, v3, "First upload after postDraw should return 0");
|
||||
} finally {
|
||||
memFree(data);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
assertEquals((byte) (i & 0xFF), readBack.get(i), "Mismatch at byte " + i);
|
||||
}
|
||||
memFree(readBack);
|
||||
} finally {
|
||||
buf.destroy();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user