Skip to content

Commit 35fc330

Browse files
committed
Reset graphics, bufferbuilders, and TESR dispatcher after crash
Fixes #78 Fixes #50
1 parent 6dbdcee commit 35fc330

6 files changed

Lines changed: 295 additions & 44 deletions

File tree

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package org.dimdev.utils;
2+
3+
import net.minecraft.client.renderer.GlStateManager;
4+
import net.minecraft.client.renderer.OpenGlHelper;
5+
import net.minecraft.client.renderer.RenderHelper;
6+
import org.lwjgl.opengl.*;
7+
8+
public class GlUtil {
9+
public static void resetState() {
10+
// Clear matrix stack
11+
GlStateManager.matrixMode(GL11.GL_MODELVIEW);
12+
GlStateManager.loadIdentity();
13+
GlStateManager.matrixMode(GL11.GL_PROJECTION);
14+
GlStateManager.loadIdentity();
15+
GlStateManager.matrixMode(GL11.GL_TEXTURE);
16+
GlStateManager.loadIdentity();
17+
GlStateManager.matrixMode(GL11.GL_COLOR);
18+
GlStateManager.loadIdentity();
19+
20+
// Clear attribute stacks TODO: Broken, a stack underflow breaks LWJGL
21+
// try {
22+
// do GL11.glPopAttrib(); while (GlStateManager.glGetError() == 0);
23+
// } catch (Throwable ignored) {}
24+
//
25+
// try {
26+
// do GL11.glPopClientAttrib(); while (GlStateManager.glGetError() == 0);
27+
// } catch (Throwable ignored) {}
28+
29+
// Reset texture
30+
GlStateManager.bindTexture(0);
31+
GlStateManager.disableTexture2D();
32+
33+
// Reset GL lighting
34+
GlStateManager.disableLighting();
35+
GlStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, RenderHelper.setColorBuffer(0.2F, 0.2F, 0.2F, 1.0F));
36+
for (int i = 0; i < 8; ++i) {
37+
GlStateManager.disableLight(i);
38+
GlStateManager.glLight(GL11.GL_LIGHT0 + i, GL11.GL_AMBIENT, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 1.0F));
39+
GlStateManager.glLight(GL11.GL_LIGHT0 + i, GL11.GL_POSITION, RenderHelper.setColorBuffer(0.0F, 0.0F, 1.0F, 0.0F));
40+
41+
if (i == 0) {
42+
GlStateManager.glLight(GL11.GL_LIGHT0 + i, GL11.GL_DIFFUSE, RenderHelper.setColorBuffer(1.0F, 1.0F, 1.0F, 1.0F));
43+
GlStateManager.glLight(GL11.GL_LIGHT0 + i, GL11.GL_SPECULAR, RenderHelper.setColorBuffer(1.0F, 1.0F, 1.0F, 1.0F));
44+
} else {
45+
GlStateManager.glLight(GL11.GL_LIGHT0 + i, GL11.GL_DIFFUSE, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 1.0F));
46+
GlStateManager.glLight(GL11.GL_LIGHT0 + i, GL11.GL_SPECULAR, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 1.0F));
47+
}
48+
}
49+
GlStateManager.disableColorMaterial();
50+
GlStateManager.colorMaterial(1032, 5634);
51+
52+
// Reset depth
53+
GlStateManager.disableDepth();
54+
GlStateManager.depthFunc(513);
55+
GlStateManager.depthMask(true);
56+
57+
// Reset blend mode
58+
GlStateManager.disableBlend();
59+
GlStateManager.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
60+
GlStateManager.tryBlendFuncSeparate(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
61+
GlStateManager.glBlendEquation(GL14.GL_FUNC_ADD);
62+
63+
// Reset fog
64+
GlStateManager.disableFog();
65+
GlStateManager.setFog(GlStateManager.FogMode.LINEAR);
66+
GlStateManager.setFogDensity(1.0F);
67+
GlStateManager.setFogStart(0.0F);
68+
GlStateManager.setFogEnd(1.0F);
69+
GlStateManager.glFog(GL11.GL_FOG_COLOR, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 0.0F));
70+
if (GLContext.getCapabilities().GL_NV_fog_distance) GlStateManager.glFogi(GL11.GL_FOG_MODE, 34140);
71+
72+
// Reset polygon offset
73+
GlStateManager.doPolygonOffset(0.0F, 0.0F);
74+
GlStateManager.disablePolygonOffset();
75+
76+
// Reset color logic
77+
GlStateManager.disableColorLogic();
78+
GlStateManager.colorLogicOp(5379);
79+
80+
// Reset texgen TODO: is this correct?
81+
GlStateManager.disableTexGenCoord(GlStateManager.TexGen.S);
82+
GlStateManager.disableTexGenCoord(GlStateManager.TexGen.T);
83+
GlStateManager.disableTexGenCoord(GlStateManager.TexGen.R);
84+
GlStateManager.disableTexGenCoord(GlStateManager.TexGen.Q);
85+
GlStateManager.texGen(GlStateManager.TexGen.S, 9216);
86+
GlStateManager.texGen(GlStateManager.TexGen.T, 9216);
87+
GlStateManager.texGen(GlStateManager.TexGen.R, 9216);
88+
GlStateManager.texGen(GlStateManager.TexGen.Q, 9216);
89+
GlStateManager.texGen(GlStateManager.TexGen.S, 9474, RenderHelper.setColorBuffer(1.0F, 0.0F, 0.0F, 0.0F));
90+
GlStateManager.texGen(GlStateManager.TexGen.T, 9474, RenderHelper.setColorBuffer(0.0F, 1.0F, 0.0F, 0.0F));
91+
GlStateManager.texGen(GlStateManager.TexGen.R, 9474, RenderHelper.setColorBuffer(0.0F, 0.0F, 1.0F, 0.0F));
92+
GlStateManager.texGen(GlStateManager.TexGen.Q, 9474, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 1.0F));
93+
GlStateManager.texGen(GlStateManager.TexGen.S, 9217, RenderHelper.setColorBuffer(1.0F, 0.0F, 0.0F, 0.0F));
94+
GlStateManager.texGen(GlStateManager.TexGen.T, 9217, RenderHelper.setColorBuffer(0.0F, 1.0F, 0.0F, 0.0F));
95+
GlStateManager.texGen(GlStateManager.TexGen.R, 9217, RenderHelper.setColorBuffer(0.0F, 0.0F, 1.0F, 0.0F));
96+
GlStateManager.texGen(GlStateManager.TexGen.Q, 9217, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 1.0F));
97+
98+
// Disable lightmap
99+
GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit);
100+
GlStateManager.disableTexture2D();
101+
102+
GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit);
103+
104+
// Reset texture parameters
105+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
106+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR);
107+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
108+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
109+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1000);
110+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1000);
111+
GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, -1000);
112+
GlStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0F);
113+
114+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
115+
GlStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, RenderHelper.setColorBuffer(0.0F, 0.0F, 0.0F, 0.0F));
116+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
117+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
118+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL15.GL_SRC0_RGB, GL11.GL_TEXTURE);
119+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL15.GL_SRC1_RGB, GL13.GL_PREVIOUS);
120+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL15.GL_SRC2_RGB, GL13.GL_CONSTANT);
121+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL15.GL_SRC0_ALPHA, GL11.GL_TEXTURE);
122+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL15.GL_SRC1_ALPHA, GL13.GL_PREVIOUS);
123+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL15.GL_SRC2_ALPHA, GL13.GL_CONSTANT);
124+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
125+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
126+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
127+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA);
128+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
129+
GlStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
130+
GlStateManager.glTexEnvf(GL11.GL_TEXTURE_ENV, GL13.GL_RGB_SCALE, 1.0F);
131+
GlStateManager.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_ALPHA_SCALE, 1.0F);
132+
133+
GlStateManager.disableNormalize();
134+
GlStateManager.shadeModel(7425);
135+
GlStateManager.disableRescaleNormal();
136+
GlStateManager.colorMask(true, true, true, true);
137+
GlStateManager.clearDepth(1.0D);
138+
GlStateManager.glLineWidth(1.0F);
139+
GlStateManager.glNormal3f(0.0F, 0.0F, 1.0F);
140+
GlStateManager.glPolygonMode(GL11.GL_FRONT, GL11.GL_FILL);
141+
GlStateManager.glPolygonMode(GL11.GL_BACK, GL11.GL_FILL);
142+
143+
GlStateManager.enableTexture2D();
144+
GlStateManager.shadeModel(7425);
145+
GlStateManager.clearDepth(1.0D);
146+
GlStateManager.enableDepth();
147+
GlStateManager.depthFunc(515);
148+
GlStateManager.enableAlpha();
149+
GlStateManager.alphaFunc(516, 0.1F);
150+
GlStateManager.cullFace(GlStateManager.CullFace.BACK);
151+
GlStateManager.matrixMode(5889);
152+
GlStateManager.loadIdentity();
153+
GlStateManager.matrixMode(5888);
154+
}
155+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.dimdev.vanillafix.crashes;
2+
3+
import java.lang.ref.WeakReference;
4+
import java.util.HashSet;
5+
import java.util.Iterator;
6+
import java.util.Set;
7+
8+
/**
9+
* Allows registering objects to be reset after a crash. Objects registered
10+
* use WeakReferences, so they will be garbage-collected despite still being
11+
* registered here.
12+
*/
13+
public class StateManager {
14+
public interface IResettable {
15+
default void register() {
16+
resettableRefs.add(new WeakReference<>(this));
17+
}
18+
19+
void resetState();
20+
}
21+
22+
// Use WeakReference to allow garbage collection, preventing memory leaks
23+
private static Set<WeakReference<IResettable>> resettableRefs = new HashSet<>();
24+
25+
public static void resetStates() {
26+
Iterator<WeakReference<IResettable>> iterator = resettableRefs.iterator();
27+
while (iterator.hasNext()) {
28+
WeakReference<IResettable> ref = iterator.next();
29+
if (ref.get() != null) {
30+
ref.get().resetState();
31+
} else {
32+
iterator.remove();
33+
}
34+
}
35+
}
36+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.dimdev.vanillafix.crashes.mixins.client;
2+
3+
import net.minecraft.client.renderer.BufferBuilder;
4+
import org.dimdev.vanillafix.crashes.StateManager;
5+
import org.spongepowered.asm.mixin.Mixin;
6+
import org.spongepowered.asm.mixin.Shadow;
7+
import org.spongepowered.asm.mixin.injection.At;
8+
import org.spongepowered.asm.mixin.injection.Inject;
9+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
10+
11+
@Mixin(BufferBuilder.class)
12+
public abstract class MixinBufferBuilder implements StateManager.IResettable {
13+
@Shadow private boolean isDrawing;
14+
@Shadow public abstract void finishDrawing();
15+
16+
@Inject(method = "<init>", at = @At(value = "RETURN"))
17+
public void onInit(int bufferSizeIn, CallbackInfo ci) {
18+
register();
19+
}
20+
21+
@Override
22+
public void resetState() {
23+
if (isDrawing) finishDrawing();
24+
}
25+
}

src/main/java/org/dimdev/vanillafix/crashes/mixins/client/MixinMinecraft.java

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.dimdev.vanillafix.crashes.mixins.client;
22

33
import com.google.common.util.concurrent.ListenableFutureTask;
4+
import net.minecraft.client.LoadingScreenRenderer;
45
import net.minecraft.client.Minecraft;
56
import net.minecraft.client.audio.SoundHandler;
67
import net.minecraft.client.gui.*;
@@ -26,6 +27,7 @@
2627
import net.minecraft.util.text.TextComponentString;
2728
import net.minecraftforge.fml.client.SplashProgress;
2829
import org.apache.logging.log4j.Logger;
30+
import org.dimdev.utils.GlUtil;
2931
import org.dimdev.vanillafix.ModConfig;
3032
import org.dimdev.vanillafix.VanillaFix;
3133
import org.dimdev.vanillafix.VanillaFixLoadingPlugin;
@@ -97,6 +99,7 @@ public abstract class MixinMinecraft implements IThreadListener, ISnooperInfo, I
9799
@Shadow protected abstract void checkGLError(String message);
98100
// @formatter:on
99101

102+
@Shadow public LoadingScreenRenderer loadingScreen;
100103
private boolean crashIntegratedServerNextTick;
101104
private int clientCrashCount = 0;
102105
private int serverCrashCount = 0;
@@ -126,15 +129,15 @@ public void run() {
126129
clientCrashCount++;
127130
addGraphicsAndWorldToCrashReport(e.getCrashReport());
128131
addInfoToCrash(e.getCrashReport());
129-
freeMemory();
132+
resetGameState();
130133
LOGGER.fatal("Reported exception thrown!", e);
131134
displayCrashScreen(e.getCrashReport());
132135
} catch (Throwable e) {
133136
clientCrashCount++;
134137
CrashReport report = new CrashReport("Unexpected error", e);
135138
addGraphicsAndWorldToCrashReport(report);
136139
addInfoToCrash(report);
137-
freeMemory();
140+
resetGameState();
138141
LOGGER.fatal("Unreported exception thrown!", e);
139142
displayCrashScreen(report);
140143
}
@@ -246,7 +249,7 @@ private void runGUILoop(GuiScreen screen) throws IOException {
246249
}
247250
}
248251

249-
public void displayCrashScreen(CrashReport report) { // TODO: Shouldn't the GL state be reset here and on displayInitErrorScreen?
252+
public void displayCrashScreen(CrashReport report) {
250253
try {
251254
CrashUtils.outputReport(report);
252255

@@ -275,61 +278,62 @@ public void displayCrashReport(CrashReport report) {
275278
CrashUtils.outputReport(report);
276279
}
277280

278-
/**
279-
* @reason Disconnect from the current world and free memory, using a memory reserve
280-
* to make sure that an OutOfMemory doesn't happen while doing this.
281-
* <p>
282-
* Bugs Fixed:
283-
* - https://bugs.mojang.com/browse/MC-128953
284-
* - Memory reserve not recreated after out-of memory
285-
*/
286-
@Overwrite
287-
@SuppressWarnings("CallToSystemGC")
288-
public void freeMemory() {
289-
int originalMemoryReserveSize = -1;
281+
public void resetGameState() {
290282
try {
291-
// Separate try in case another mod actually deletes the memoryReserve field
292-
if (memoryReserve != null) {
293-
originalMemoryReserveSize = memoryReserve.length;
294-
memoryReserve = new byte[0];
295-
}
296-
} catch (Throwable ignored) {}
297-
298-
try {
299-
renderGlobal.deleteAllDisplayLists();
300-
} catch (Throwable ignored) {}
283+
// Free up memory such that this works properly in case of an OutOfMemoryError
284+
int originalMemoryReserveSize = -1;
285+
try { // In case another mod actually deletes the memoryReserve field
286+
if (memoryReserve != null) {
287+
originalMemoryReserveSize = memoryReserve.length;
288+
memoryReserve = new byte[0];
289+
}
290+
} catch (Throwable ignored) {}
301291

302-
try {
303-
System.gc();
292+
// Reset registered resettables
293+
StateManager.resetStates();
304294

305-
// Fix: Close the connection to avoid receiving packets from old server
306-
// when playing in another world (MC-128953)
295+
// Close the world
307296
if (getConnection() != null) {
297+
// Fix: Close the connection to avoid receiving packets from old server
298+
// when playing in another world (MC-128953)
308299
getConnection().getNetworkManager().closeChannel(new TextComponentString("[VanillaFix] Client crashed"));
309300
}
310-
311301
loadWorld(null);
302+
if (entityRenderer.isShaderActive()) entityRenderer.stopUseShader();
303+
scheduledTasks.clear(); // TODO: Figure out why this isn't necessary for vanilla disconnect
312304

313-
// TODO: Figure out why this isn't necessary for vanilla disconnect:
314-
scheduledTasks.clear();
305+
// Reset graphics
306+
GlUtil.resetState();
315307

316-
// Fix: Probably not necessary, but do this now rathe than next tick to
317-
// make it identical to a regular disconnect:
318-
if (entityRenderer.isShaderActive()) {
319-
entityRenderer.stopUseShader();
308+
// Re-create memory reserve so that future crashes work well too
309+
if (originalMemoryReserveSize != -1) {
310+
try {
311+
memoryReserve = new byte[originalMemoryReserveSize];
312+
} catch (Throwable ignored) {}
320313
}
321-
} catch (Throwable ignored) {}
322-
323-
System.gc();
324-
325-
// Fix: Re-create memory reserve so that future crashes work well too
326-
if (originalMemoryReserveSize != -1) {
314+
System.gc();
315+
} catch (Throwable t) {
316+
LOGGER.error("Failed to reset state after a crash", t);
327317
try {
328-
memoryReserve = new byte[originalMemoryReserveSize];
318+
StateManager.resetStates();
319+
GlUtil.resetState();
329320
} catch (Throwable ignored) {}
330321
}
331322
}
332323

324+
/**
325+
* @reason Disconnect from the current world and free memory, using a memory reserve
326+
* to make sure that an OutOfMemory doesn't happen while doing this.
327+
* <p>
328+
* Bugs Fixed:
329+
* - https://bugs.mojang.com/browse/MC-128953
330+
* - Memory reserve not recreated after out-of memory
331+
*/
332+
@Overwrite
333+
public void freeMemory() {
334+
resetGameState();
335+
}
336+
333337
/**
334338
* @reason Replaces the vanilla F3 + C logic to immediately crash rather than requiring
335339
* that the buttons are pressed for 6 seconds and add more crash types:
@@ -377,6 +381,10 @@ private long checkForF3C(Minecraft mc) {
377381
return -1;
378382
}
379383

384+
private void breakRendering() {
385+
getTextureManager().bindTexture(Gui.OPTIONS_BACKGROUND);
386+
}
387+
380388
/** @reason Disables the vanilla F3 + C logic. */
381389
@Redirect(method = "runTickKeyboard", at = @At(value = "INVOKE", target = "Lorg/lwjgl/input/Keyboard;isKeyDown(I)Z", ordinal = 0))
382390
private boolean isKeyDownF3(int key) {

0 commit comments

Comments
 (0)