mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Only animate PBR textures that are visible (#2310)
This commit is contained in:
1 parent
ed6dd12db7
commit
08aa37712f
8 files changed
+165
-27
No files matched your search
@@ -7,6 +7,8 @@ public final class DebugCounters {
|
||||
public static final LongAdder TRANSFER_WAIT_NANOS = new LongAdder();
|
||||
public static final LongAdder PRESENTS = new LongAdder();
|
||||
public static final LongAdder EMPTY_ACQUIRES = new LongAdder();
|
||||
public static final LongAdder PBR_ANIM_UPDATES = new LongAdder();
|
||||
public static final LongAdder PBR_ANIM_DRY_RUNS = new LongAdder();
|
||||
|
||||
private DebugCounters() {}
|
||||
}
|
||||
@@ -138,6 +138,8 @@ public final class FlybyRunner {
|
||||
private long transferWaitStartNs;
|
||||
private long presentsStart;
|
||||
private long emptyAcquiresStart;
|
||||
private long pbrAnimUpdatesStart;
|
||||
private long pbrAnimDryRunsStart;
|
||||
private long allocClientStart;
|
||||
private long allocProcessStart;
|
||||
private int frameCount;
|
||||
@@ -557,6 +559,8 @@ public final class FlybyRunner {
|
||||
this.transferWaitStartNs = DebugCounters.TRANSFER_WAIT_NANOS.sum();
|
||||
this.presentsStart = DebugCounters.PRESENTS.sum();
|
||||
this.emptyAcquiresStart = DebugCounters.EMPTY_ACQUIRES.sum();
|
||||
this.pbrAnimUpdatesStart = DebugCounters.PBR_ANIM_UPDATES.sum();
|
||||
this.pbrAnimDryRunsStart = DebugCounters.PBR_ANIM_DRY_RUNS.sum();
|
||||
this.allocClientStart = AllocBean.allocatedBytes();
|
||||
this.allocProcessStart = AllocBean.totalAllocatedBytes();
|
||||
this.frameCount = 0;
|
||||
@@ -944,6 +948,9 @@ public final class FlybyRunner {
|
||||
sb.append(String.format(", presents %d, empty acquires %d",
|
||||
DebugCounters.PRESENTS.sum() - this.presentsStart,
|
||||
DebugCounters.EMPTY_ACQUIRES.sum() - this.emptyAcquiresStart));
|
||||
sb.append(String.format(", pbr anim updates %d, dry runs %d",
|
||||
DebugCounters.PBR_ANIM_UPDATES.sum() - this.pbrAnimUpdatesStart,
|
||||
DebugCounters.PBR_ANIM_DRY_RUNS.sum() - this.pbrAnimDryRunsStart));
|
||||
}
|
||||
if (this.framesSeen > this.frameCount) sb.append(" (frame samples truncated)");
|
||||
for (int i = 0; i < this.phaseCount; i++) {
|
||||
|
||||
+1
-1
@@ -5,5 +5,5 @@ public interface IPatchedTextureAtlasSprite {
|
||||
void angelica$markNeedsAnimationUpdate();
|
||||
boolean angelica$needsAnimationUpdate();
|
||||
void angelica$unmarkNeedsAnimationUpdate();
|
||||
void angelica$updateAnimationsDryRun();
|
||||
boolean angelica$tickAnimation(boolean onlyVisible);
|
||||
}
|
||||
@@ -2,10 +2,15 @@ package net.coderbot.iris.texture.pbr;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.mojang.AutoClosableAbstractTexture;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.DebugCounters;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ISpriteExt;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import com.gtnewhorizons.angelica.utils.SpritePadding;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.texture.pbr.loader.AtlasPBRLoader;
|
||||
import net.coderbot.iris.texture.util.TextureExporter;
|
||||
import net.coderbot.iris.texture.util.TextureManipulationUtil;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
@@ -20,10 +25,10 @@ import net.minecraft.util.ResourceLocation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class PBRAtlasTexture extends AutoClosableAbstractTexture {
|
||||
protected final TextureMap texMap;
|
||||
@@ -31,7 +36,8 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
|
||||
protected final PBRType type;
|
||||
protected final ResourceLocation id;
|
||||
protected final Map<ResourceLocation, TextureAtlasSprite> sprites = new HashMap<>();
|
||||
protected final Set<TextureAtlasSprite> animatedSprites = new HashSet<>();
|
||||
protected final List<IPatchedTextureAtlasSprite> animatedSprites = new ArrayList<>();
|
||||
protected final List<TextureAtlasSprite> alwaysAnimatedSprites = new ArrayList<>();
|
||||
|
||||
public PBRAtlasTexture(TextureMap textureMap, PBRType type) {
|
||||
this.texMap = textureMap;
|
||||
@@ -44,10 +50,15 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void addSprite(TextureAtlasSprite sprite) {
|
||||
public void addSprite(AtlasPBRLoader.PBRTextureAtlasSprite sprite) {
|
||||
sprites.put(texMap.completeResourceLocation(new ResourceLocation(sprite.getIconName()), 0), sprite);
|
||||
if (sprite.hasAnimationMetadata()) {
|
||||
animatedSprites.add(sprite);
|
||||
if (((ISpriteExt) sprite).isAnimation()) {
|
||||
// Without the animation mixins, or for a base outside listAnimatedSprites, nothing consumes base visibility
|
||||
if (sprite instanceof IPatchedTextureAtlasSprite patched && sprite.getBaseSprite().hasAnimationMetadata()) {
|
||||
animatedSprites.add(patched);
|
||||
} else {
|
||||
alwaysAnimatedSprites.add(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +70,7 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
|
||||
public void clear() {
|
||||
sprites.clear();
|
||||
animatedSprites.clear();
|
||||
alwaysAnimatedSprites.clear();
|
||||
}
|
||||
|
||||
public void upload(int atlasWidth, int atlasHeight, int mipLevel, float anisotropicFiltering) {
|
||||
@@ -78,7 +90,7 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
|
||||
}
|
||||
}
|
||||
|
||||
if (!animatedSprites.isEmpty()) {
|
||||
if (!animatedSprites.isEmpty() || !alwaysAnimatedSprites.isEmpty()) {
|
||||
final PBRAtlasHolder pbrHolder = ((TextureAtlasExtension) texMap).getOrCreatePBRHolder();
|
||||
switch (type) {
|
||||
case NORMAL:
|
||||
@@ -128,10 +140,17 @@ public class PBRAtlasTexture extends AutoClosableAbstractTexture {
|
||||
}
|
||||
|
||||
public void cycleAnimationFrames() {
|
||||
final boolean onlyVisible = ClientProxy.options().performance.animateOnlyVisibleTextures;
|
||||
bind();
|
||||
for (TextureAtlasSprite sprite : animatedSprites) {
|
||||
sprite.updateAnimation();
|
||||
int updated = alwaysAnimatedSprites.size();
|
||||
for (int i = 0; i < animatedSprites.size(); i++) {
|
||||
if (animatedSprites.get(i).angelica$tickAnimation(onlyVisible)) updated++;
|
||||
}
|
||||
for (int i = 0; i < alwaysAnimatedSprites.size(); i++) {
|
||||
alwaysAnimatedSprites.get(i).updateAnimation();
|
||||
}
|
||||
DebugCounters.PBR_ANIM_UPDATES.add(updated);
|
||||
DebugCounters.PBR_ANIM_DRY_RUNS.add(animatedSprites.size() + alwaysAnimatedSprites.size() - updated);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -58,8 +58,8 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
|
||||
PBRAtlasTexture specularAtlas = null;
|
||||
for (TextureAtlasSprite sprite : (Collection<TextureAtlasSprite>) texMap.mapUploadedSprites.values()) {
|
||||
if (!(sprite.getIconName().equals("missingno"))) {
|
||||
TextureAtlasSprite normalSprite = createPBRSprite(sprite, resourceManager, texMap, atlasWidth, atlasHeight, mipLevel, PBRType.NORMAL);
|
||||
TextureAtlasSprite specularSprite = createPBRSprite(sprite, resourceManager, texMap, atlasWidth, atlasHeight, mipLevel, PBRType.SPECULAR);
|
||||
PBRTextureAtlasSprite normalSprite = createPBRSprite(sprite, resourceManager, texMap, atlasWidth, atlasHeight, mipLevel, PBRType.NORMAL);
|
||||
PBRTextureAtlasSprite specularSprite = createPBRSprite(sprite, resourceManager, texMap, atlasWidth, atlasHeight, mipLevel, PBRType.SPECULAR);
|
||||
if (normalSprite != null) {
|
||||
if (normalAtlas == null) {
|
||||
normalAtlas = new PBRAtlasTexture(texMap, PBRType.NORMAL);
|
||||
@@ -115,7 +115,7 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected TextureAtlasSprite createPBRSprite(TextureAtlasSprite sprite, IResourceManager resourceManager, TextureMap texMap, int atlasWidth, int atlasHeight, int mipLevel, PBRType pbrType) {
|
||||
protected PBRTextureAtlasSprite createPBRSprite(TextureAtlasSprite sprite, IResourceManager resourceManager, TextureMap texMap, int atlasWidth, int atlasHeight, int mipLevel, PBRType pbrType) {
|
||||
final ResourceLocation spriteName = new ResourceLocation(sprite.getIconName());
|
||||
final ResourceLocation imageLocation = texMap.completeResourceLocation(spriteName, 0);
|
||||
final ResourceLocation pbrImageLocation = pbrType.appendToFileLocation(imageLocation);
|
||||
@@ -124,7 +124,7 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
|
||||
return null;
|
||||
}
|
||||
|
||||
TextureAtlasSprite pbrSprite = null;
|
||||
PBRTextureAtlasSprite pbrSprite = null;
|
||||
|
||||
try {
|
||||
// This is no longer closable. Not sure about this.
|
||||
@@ -174,7 +174,7 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
|
||||
final int gutter = ((SpriteExtension) sprite).angelica$getGutterWidth();
|
||||
|
||||
pbrSprite = new PBRTextureAtlasSprite(pbrSpriteInfo, animationMetadata, atlasWidth, atlasHeight,
|
||||
sprite.getOriginX(), sprite.getOriginY(), gutter, nativeImage, false, mipLevel);
|
||||
sprite.getOriginX(), sprite.getOriginY(), gutter, nativeImage, false, mipLevel, sprite);
|
||||
syncAnimation(sprite, pbrSprite);
|
||||
} catch (FileNotFoundException e) {
|
||||
//
|
||||
@@ -234,14 +234,21 @@ public class AtlasPBRLoader implements PBRTextureLoader<TextureMap> {
|
||||
}
|
||||
|
||||
public static class PBRTextureAtlasSprite extends TextureAtlasSprite implements CustomMipmapGenerator.Provider {
|
||||
protected final TextureAtlasSprite baseSprite;
|
||||
|
||||
// This feels super janky
|
||||
protected PBRTextureAtlasSprite(TextureAtlasSpriteInfo info, AnimationMetadataSection animationMetaDataSection, int atlasWidth, int atlasHeight, int x, int y, int gutter, NativeImage nativeImage, boolean useAnisotropicFiltering, int miplevel) {
|
||||
protected PBRTextureAtlasSprite(TextureAtlasSpriteInfo info, AnimationMetadataSection animationMetaDataSection, int atlasWidth, int atlasHeight, int x, int y, int gutter, NativeImage nativeImage, boolean useAnisotropicFiltering, int miplevel, TextureAtlasSprite baseSprite) {
|
||||
super(info.name().toString());
|
||||
this.baseSprite = baseSprite;
|
||||
((SpriteExtension) this).angelica$setGutterWidth(gutter);
|
||||
super.loadSprite(Objects.requireNonNull(getMipmapGenerator(info, atlasWidth, atlasHeight)).generateMipLevels(nativeImage, miplevel), animationMetaDataSection, useAnisotropicFiltering);
|
||||
super.initSprite(atlasWidth, atlasHeight, x - gutter, y - gutter, false);
|
||||
}
|
||||
|
||||
public TextureAtlasSprite getBaseSprite() {
|
||||
return baseSprite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomMipmapGenerator getMipmapGenerator(TextureAtlasSpriteInfo info, int atlasWidth, int atlasHeight) {
|
||||
if (info instanceof PBRTextureAtlasSpriteInfo pbrInfo) {
|
||||
|
||||
+24
-1
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.animation;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
|
||||
import net.coderbot.iris.texture.pbr.PBRSpriteHolder;
|
||||
import net.coderbot.iris.texture.pbr.TextureAtlasSpriteExtension;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.data.AnimationMetadataSection;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -18,6 +20,7 @@ public abstract class MixinTextureAtlasSprite implements IPatchedTextureAtlasSpr
|
||||
@Shadow protected int frameCounter;
|
||||
@Shadow private AnimationMetadataSection animationMetadata;
|
||||
@Shadow protected List<?> framesTextureData;
|
||||
@Shadow public abstract void updateAnimation();
|
||||
|
||||
@Unique private boolean angelica$isActive = false;
|
||||
|
||||
@@ -31,6 +34,16 @@ public abstract class MixinTextureAtlasSprite implements IPatchedTextureAtlasSpr
|
||||
public boolean angelica$needsAnimationUpdate() {
|
||||
if (this.angelica$isActive) {
|
||||
this.angelica$isActive = false;
|
||||
// PBR sprites are never UV-sampled; they inherit visibility when the base sprite's mark is consumed
|
||||
if (this instanceof TextureAtlasSpriteExtension extension) {
|
||||
final PBRSpriteHolder pbrHolder = extension.getPBRHolder();
|
||||
if (pbrHolder != null) {
|
||||
final TextureAtlasSprite normalSprite = pbrHolder.getNormalSprite();
|
||||
final TextureAtlasSprite specularSprite = pbrHolder.getSpecularSprite();
|
||||
if (normalSprite != null) ((IPatchedTextureAtlasSprite) normalSprite).angelica$markNeedsAnimationUpdate();
|
||||
if (specularSprite != null) ((IPatchedTextureAtlasSprite) specularSprite).angelica$markNeedsAnimationUpdate();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -42,7 +55,17 @@ public abstract class MixinTextureAtlasSprite implements IPatchedTextureAtlasSpr
|
||||
}
|
||||
|
||||
@Override
|
||||
public void angelica$updateAnimationsDryRun() {
|
||||
public boolean angelica$tickAnimation(boolean onlyVisible) {
|
||||
if (!onlyVisible || angelica$needsAnimationUpdate()) {
|
||||
updateAnimation();
|
||||
return true;
|
||||
}
|
||||
angelica$advanceFramesDryRun();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void angelica$advanceFramesDryRun() {
|
||||
if (animationMetadata == null || framesTextureData == null) return;
|
||||
|
||||
tickCounter++;
|
||||
|
||||
+1
-10
@@ -36,16 +36,7 @@ public abstract class MixinTextureMap extends AbstractTexture {
|
||||
|
||||
final int size = listAnimatedSprites.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final TextureAtlasSprite sprite = listAnimatedSprites.get(i);
|
||||
final IPatchedTextureAtlasSprite patched = (IPatchedTextureAtlasSprite) sprite;
|
||||
|
||||
// needsAnimationUpdate() is one-shot: returns true if marked, then auto-resets
|
||||
if (!renderVisible || patched.angelica$needsAnimationUpdate()) {
|
||||
sprite.updateAnimation();
|
||||
} else {
|
||||
// Keep frame counters in sync for sprites not being updated
|
||||
patched.angelica$updateAnimationsDryRun();
|
||||
}
|
||||
((IPatchedTextureAtlasSprite) listAnimatedSprites.get(i)).angelica$tickAnimation(renderVisible);
|
||||
}
|
||||
Minecraft.getMinecraft().mcProfiler.endSection();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package net.coderbot.iris.texture.pbr;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.DebugCounters;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IPatchedTextureAtlasSprite;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ISpriteExt;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
import net.coderbot.iris.texture.pbr.loader.AtlasPBRLoader;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.withSettings;
|
||||
|
||||
@GLCoreTest
|
||||
class PBRAtlasAnimationTest {
|
||||
static Stream<Arguments> cases() {
|
||||
return Stream.of(
|
||||
Arguments.of("visible tracked sprite updates", true, true, true, true, true, 1, 0, 1, 0),
|
||||
Arguments.of("hidden tracked sprite dry-runs", true, true, true, true, false, 1, 0, 0, 1),
|
||||
Arguments.of("only-visible off reaches the gate", true, true, true, false, true, 1, 0, 1, 0),
|
||||
Arguments.of("base without animation metadata is always animated", true, false, true, true, false, 0, 1, 1, 0),
|
||||
Arguments.of("sprite without the animation mixin is always animated", false, true, true, true, false, 0, 1, 1, 0),
|
||||
Arguments.of("non-animated pbr sprite is never touched", true, true, false, true, false, 0, 0, 0, 0)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("cases")
|
||||
void cycleRoutesAnimationAndCountsUpdates(String name, boolean patchedSprite, boolean baseAnimated, boolean pbrAnimated,
|
||||
boolean onlyVisible, boolean tickReturns, int expectedTicks, int expectedUpdateCalls,
|
||||
long expectedUpdates, long expectedDryRuns) {
|
||||
final SodiumGameOptions previousOptions = Reflect.getStatic(ClientProxy.class, "CONFIG");
|
||||
final SodiumGameOptions options = new SodiumGameOptions();
|
||||
options.performance.animateOnlyVisibleTextures = onlyVisible;
|
||||
|
||||
final TextureMap textureMap = mock(TextureMap.class, withSettings().extraInterfaces(TextureAtlasExtension.class));
|
||||
when(textureMap.completeResourceLocation(any(), anyInt())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
final TextureAtlasSprite base = mock(TextureAtlasSprite.class);
|
||||
when(base.hasAnimationMetadata()).thenReturn(baseAnimated);
|
||||
final AtlasPBRLoader.PBRTextureAtlasSprite sprite = mock(AtlasPBRLoader.PBRTextureAtlasSprite.class, patchedSprite
|
||||
? withSettings().extraInterfaces(IPatchedTextureAtlasSprite.class, ISpriteExt.class)
|
||||
: withSettings().extraInterfaces(ISpriteExt.class));
|
||||
when(sprite.getIconName()).thenReturn("minecraft:stone_n");
|
||||
when(sprite.getBaseSprite()).thenReturn(base);
|
||||
when(((ISpriteExt) sprite).isAnimation()).thenReturn(pbrAnimated);
|
||||
if (patchedSprite) {
|
||||
when(((IPatchedTextureAtlasSprite) sprite).angelica$tickAnimation(anyBoolean())).thenReturn(tickReturns);
|
||||
}
|
||||
|
||||
Reflect.setStatic(ClientProxy.class, "CONFIG", options);
|
||||
final PBRAtlasTexture atlas = new PBRAtlasTexture(textureMap, PBRType.NORMAL);
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
Reflect.set(atlas, "glTextureId", texture);
|
||||
try {
|
||||
atlas.addSprite(sprite);
|
||||
final long updatesBefore = DebugCounters.PBR_ANIM_UPDATES.sum();
|
||||
final long dryRunsBefore = DebugCounters.PBR_ANIM_DRY_RUNS.sum();
|
||||
|
||||
atlas.cycleAnimationFrames();
|
||||
|
||||
if (patchedSprite) {
|
||||
verify((IPatchedTextureAtlasSprite) sprite, times(expectedTicks)).angelica$tickAnimation(anyBoolean());
|
||||
verify((IPatchedTextureAtlasSprite) sprite, times(expectedTicks)).angelica$tickAnimation(onlyVisible);
|
||||
}
|
||||
verify(sprite, times(expectedUpdateCalls)).updateAnimation();
|
||||
assertEquals(expectedUpdates, DebugCounters.PBR_ANIM_UPDATES.sum() - updatesBefore);
|
||||
assertEquals(expectedDryRuns, DebugCounters.PBR_ANIM_DRY_RUNS.sum() - dryRunsBefore);
|
||||
} finally {
|
||||
GLSMTextures.glDeleteTextures(texture);
|
||||
Reflect.setStatic(ClientProxy.class, "CONFIG", previousOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user