mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Stop blocking chunk workers on deferred ISBRH blocks (#2136)
This commit is contained in:
1 parent
1babfd968c
commit
762c5661eb
16 files changed
+759
-111
No files matched your search
+3
@@ -2,10 +2,13 @@ package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public interface RenderSectionManagerAccessor {
|
||||
Long2ReferenceMap<RenderSection> angelica$getSectionByPosition();
|
||||
ConcurrentLinkedDeque<Runnable> angelica$getAsyncSubmittedTasks();
|
||||
ConcurrentLinkedDeque<ChunkJobResult<? extends ChunkTaskOutput>> angelica$getBuildResults();
|
||||
}
|
||||
@@ -243,8 +243,7 @@ public final class ClientProxy extends CommonProxy {
|
||||
|
||||
// Register all blocks. Because blockids are unique to a world, this must be done each load
|
||||
GameData.getBlockRegistry().typeSafeIterable().forEach(o -> {
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, true, true);
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, false, true);
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+38
-29
@@ -5,47 +5,56 @@ import com.gtnewhorizons.angelica.api.ThreadSafeISBRHFactory;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderingRegistryExt;
|
||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
||||
import cpw.mods.fml.client.registry.RenderingRegistry;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
public class AngelicaBlockSafetyRegistry {
|
||||
private static final Reference2BooleanMap<Block> BLOCK_SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
|
||||
private static final Reference2BooleanMap<Block> ISBRH_SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
|
||||
private static final Int2IntOpenHashMap ISBRH_SAFETY_MAP = new Int2IntOpenHashMap();
|
||||
private static final StampedLock LOCK = new StampedLock();
|
||||
|
||||
/**
|
||||
* This method is threadsafe to read, and threadsafe to write, but NOT if both could happen at the same time.
|
||||
*/
|
||||
public static boolean canBlockRenderOffThread(Block block, boolean checkISBRH, boolean shouldPopulate) {
|
||||
|
||||
final Reference2BooleanMap<Block> map = checkISBRH ? ISBRH_SAFETY_MAP : BLOCK_SAFETY_MAP;
|
||||
|
||||
if (shouldPopulate)
|
||||
return populateCanRenderOffThread(block, map);
|
||||
return map.getBoolean(block);
|
||||
static {
|
||||
ISBRH_SAFETY_MAP.defaultReturnValue(-1);
|
||||
}
|
||||
|
||||
private static boolean populateCanRenderOffThread(Block block, Reference2BooleanMap<Block> map) {
|
||||
final boolean canBeOffThread;
|
||||
if(map == ISBRH_SAFETY_MAP) {
|
||||
@SuppressWarnings("deprecation")
|
||||
final ISimpleBlockRenderingHandler isbrh = ((IRenderingRegistryExt)RenderingRegistry.instance()).getISBRH(block.getRenderType());
|
||||
canBeOffThread = isbrh != null
|
||||
&& (isbrh.getClass().isAnnotationPresent(ThreadSafeISBRH.class)
|
||||
|| isbrh instanceof ThreadSafeISBRHFactory);
|
||||
} else {
|
||||
// Check blacklist here
|
||||
// !(block.getClass().getName().startsWith("com.github.bartimaeusnek."));
|
||||
canBeOffThread = true;
|
||||
public static boolean canBlockRenderOffThread(Block block) {
|
||||
final int renderType = block.getRenderType();
|
||||
|
||||
long stamp = LOCK.tryOptimisticRead();
|
||||
int value;
|
||||
try {
|
||||
value = ISBRH_SAFETY_MAP.get(renderType);
|
||||
} catch (RuntimeException ignored) {
|
||||
value = -1;
|
||||
}
|
||||
if (LOCK.validate(stamp) && value != -1) {
|
||||
return value == 1;
|
||||
}
|
||||
|
||||
final long stamp = LOCK.writeLock();
|
||||
|
||||
stamp = LOCK.readLock();
|
||||
try {
|
||||
map.put(block, canBeOffThread);
|
||||
value = ISBRH_SAFETY_MAP.get(renderType);
|
||||
} finally {
|
||||
LOCK.unlockRead(stamp);
|
||||
}
|
||||
if (value != -1) {
|
||||
return value == 1;
|
||||
}
|
||||
|
||||
return populateCanRenderOffThread(renderType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static boolean populateCanRenderOffThread(int renderType) {
|
||||
final ISimpleBlockRenderingHandler isbrh = ((IRenderingRegistryExt) RenderingRegistry.instance())
|
||||
.getISBRH(renderType);
|
||||
final boolean canBeOffThread = isbrh != null
|
||||
&& (isbrh.getClass().isAnnotationPresent(ThreadSafeISBRH.class) || isbrh instanceof ThreadSafeISBRHFactory);
|
||||
|
||||
final long stamp = LOCK.writeLock();
|
||||
try {
|
||||
ISBRH_SAFETY_MAP.put(renderType, canBeOffThread ? 1 : 0);
|
||||
} finally {
|
||||
LOCK.unlock(stamp);
|
||||
}
|
||||
|
||||
+8
-1
@@ -52,7 +52,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
private final TextureMapExtension textureAtlas;
|
||||
private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad();
|
||||
@Getter
|
||||
private final WorldSlice worldSlice;
|
||||
private WorldSlice worldSlice;
|
||||
@Getter
|
||||
private final BlockRenderContext blockRenderContext = new BlockRenderContext();
|
||||
private final LightDataCache lightDataCache = new LightDataCache();
|
||||
@@ -60,6 +60,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
private final FlatLightPipeline flatLightPipeline;
|
||||
private final QuadLightData quadLightData = new QuadLightData();
|
||||
@Getter private final FloatArrayList teBoundsScratch = new FloatArrayList();
|
||||
@Getter private final long[] deferredMask = new long[64];
|
||||
private final VertexArrayQuadView quadView;
|
||||
private final boolean hasColoredLight;
|
||||
private final float[] tintResult = new float[3];
|
||||
@@ -97,6 +98,12 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
cachedSkylightSubtracted = Minecraft.getMinecraft().theWorld.skylightSubtracted;
|
||||
}
|
||||
|
||||
public WorldSlice swapWorldSlice(WorldSlice replacement) {
|
||||
final WorldSlice previous = this.worldSlice;
|
||||
this.worldSlice = replacement;
|
||||
return previous;
|
||||
}
|
||||
|
||||
public void setupDynamicLights(int chunkOriginX, int chunkOriginY, int chunkOriginZ) {
|
||||
if (DynamicLights.isEnabled()) {
|
||||
this.dynamicLightsInstance = DynamicLights.get();
|
||||
|
||||
+154
-76
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
|
||||
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.OverridesGetDistanceFrom;
|
||||
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
|
||||
@@ -10,6 +9,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.BlockRenderContext;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.ContextAwareChunkVertexEncoder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.RenderPassHelper;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.utils.NaturalTextureUtils;
|
||||
@@ -39,6 +39,7 @@ import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildContext;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.tasks.ChunkBuilderTask;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.data.BuiltSectionMeshParts;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.occlusion.SectionVisibilityBuilder;
|
||||
@@ -48,26 +49,22 @@ import org.embeddedt.embeddium.impl.util.task.CancellationToken;
|
||||
import org.joml.Vector3d;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<ChunkBuildOutput> {
|
||||
private static final Tracy.ZoneId Z_MESH_SECTION = Tracy.zoneId("meshSection");
|
||||
private static final Tracy.ZoneId Z_MESH_DEFERRED = Tracy.zoneId("meshDeferred");
|
||||
private static final Tracy.ZoneId Z_MESH_PRESCAN = Tracy.zoneId("meshPrescan");
|
||||
private static final Tracy.ZoneId Z_MESH_DEFERRED_MAIN = Tracy.zoneId("meshDeferredMain");
|
||||
private static final Tracy.ZoneId Z_MESH_FINALIZE = Tracy.zoneId("meshFinalize");
|
||||
|
||||
protected final RenderSection render;
|
||||
protected final int buildTime;
|
||||
protected final Vector3d camera;
|
||||
|
||||
private static final long DEFERRED_BLOCK_TIMEOUT_MS = 10_000; // 10 seconds
|
||||
private static final int MAX_RETRIES = 2;
|
||||
|
||||
protected record DeferredBlock(int x, int y, int z, Block block, int meta, int pass, Material materialOverride, boolean isShaderPackOverride) {}
|
||||
private DeferredMeshScheduler scheduler;
|
||||
private boolean important;
|
||||
|
||||
public AngelicaChunkBuilderMeshingTask(RenderSection render, int time, Vector3d camera) {
|
||||
this.render = render;
|
||||
@@ -88,6 +85,15 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean canHandOff() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void bindScheduler(DeferredMeshScheduler s, boolean important) {
|
||||
this.scheduler = s;
|
||||
this.important = important;
|
||||
}
|
||||
|
||||
protected void onEnterExecute() {}
|
||||
|
||||
protected void onExitExecute() {}
|
||||
@@ -96,13 +102,11 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
|
||||
@Override
|
||||
public ChunkBuildOutput execute(ChunkBuildContext context, CancellationToken cancellationToken) {
|
||||
final long start = System.nanoTime();
|
||||
final AngelicaChunkBuildContext buildContext = (AngelicaChunkBuildContext) context;
|
||||
final AngelicaBuiltRenderSectionData renderData = new AngelicaBuiltRenderSectionData();
|
||||
final SectionVisibilityBuilder occluder = new SectionVisibilityBuilder();
|
||||
|
||||
final ChunkBuildBuffers buffers = buildContext.buffers;
|
||||
buffers.init(renderData, this.render.getSectionIndex());
|
||||
|
||||
final int minX = this.render.getOriginX();
|
||||
final int minY = this.render.getOriginY();
|
||||
final int minZ = this.render.getOriginZ();
|
||||
@@ -125,7 +129,35 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
final Tessellator tessellator = getTessellator();
|
||||
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(true);
|
||||
|
||||
ChunkBuildBuffers handoffBuffers = null;
|
||||
|
||||
try {
|
||||
final boolean threaded = isThreaded();
|
||||
final long[] deferredMask = buildContext.getDeferredMask();
|
||||
final boolean handoff;
|
||||
if (threaded) {
|
||||
if (!canHandOff() || scheduler == null) {
|
||||
throw new IllegalStateException("Threaded meshing task cannot hand off deferred blocks for " + this.render);
|
||||
}
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_PRESCAN);
|
||||
try {
|
||||
handoff = prescanDeferred(region, minX, minY, minZ, deferredMask);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
} else {
|
||||
handoff = false;
|
||||
}
|
||||
|
||||
final ChunkBuildBuffers buffers;
|
||||
if (handoff) {
|
||||
handoffBuffers = scheduler.acquireBuffers();
|
||||
buffers = handoffBuffers;
|
||||
} else {
|
||||
buffers = buildContext.buffers;
|
||||
}
|
||||
buffers.init(renderData, this.render.getSectionIndex());
|
||||
|
||||
tessellator.setTranslation(-minX, -minY, -minZ);
|
||||
SmoothBiomeColorCache.setActiveCache(biomeColorCache);
|
||||
|
||||
@@ -137,8 +169,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
final World mcWorld = Minecraft.getMinecraft().theWorld;
|
||||
final long currentTick = mcWorld != null ? mcWorld.getTotalWorldTime() : 0L;
|
||||
|
||||
final boolean threaded = isThreaded();
|
||||
final List<DeferredBlock> deferredBlocks = threaded ? new ArrayList<>() : null;
|
||||
final List<DeferredBlock> deferredBlocks = handoff ? new ArrayList<>() : null;
|
||||
|
||||
final FloatArrayList culledBounds = buildContext.getTeBoundsScratch();
|
||||
culledBounds.clear();
|
||||
@@ -207,7 +238,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
final boolean canRenderOffThread = !threaded || canRenderOffThread(block);
|
||||
final boolean canRenderOffThread = !handoff || !isDeferred(deferredMask, x, y, z);
|
||||
|
||||
// Check for shader pack override
|
||||
final BlockRenderLayer override = blockTypeIds != null ? blockTypeIds.get(block) : null;
|
||||
@@ -245,14 +276,23 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
// Process deferred blocks on main thread if any
|
||||
if (deferredBlocks != null && !deferredBlocks.isEmpty()) {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_DEFERRED);
|
||||
try {
|
||||
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ, teMap, currentTick);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
renderData.visibilityData = occluder.computeVisibilityEncoding();
|
||||
renderData.culledBlockEntityBounds = culledBounds.isEmpty() ? AngelicaBuiltRenderSectionData.EMPTY_BOUNDS : culledBounds.toFloatArray();
|
||||
renderData.maxTeRenderDistSq = maxTeRenderDistSq;
|
||||
|
||||
if (handoff) {
|
||||
if (region != buildContext.getWorldSlice()) {
|
||||
throw new IllegalStateException("Deferred handoff requires the context WorldSlice for " + this.render);
|
||||
}
|
||||
final WorldSlice slice = buildContext.swapWorldSlice(scheduler.acquireSlice());
|
||||
final DeferredSectionMesh mesh = new DeferredSectionMesh(this, cancellationToken, buffers, slice, renderData, deferredBlocks, teMap, currentTick, this.important, System.nanoTime() - start);
|
||||
scheduler.submit(mesh);
|
||||
handoffBuffers = null;
|
||||
|
||||
SmoothBiomeColorCache.clearActiveCache();
|
||||
tessellator.setTranslation(0, 0, 0);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_FINALIZE);
|
||||
@@ -260,7 +300,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
try {
|
||||
meshes = BuiltSectionMeshParts.groupFromBuildBuffers(buffers,
|
||||
(float) camera.x - minX, (float) camera.y - minY, (float) camera.z - minZ);
|
||||
renderData.visibilityData = occluder.computeVisibilityEncoding();
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
@@ -276,9 +315,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
renderData.culledBlockEntityBounds = culledBounds.isEmpty() ? AngelicaBuiltRenderSectionData.EMPTY_BOUNDS : culledBounds.toFloatArray();
|
||||
renderData.maxTeRenderDistSq = maxTeRenderDistSq;
|
||||
|
||||
SmoothBiomeColorCache.clearActiveCache();
|
||||
tessellator.setTranslation(0, 0, 0);
|
||||
|
||||
@@ -291,6 +327,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
} catch (Throwable ex) {
|
||||
throw fillCrashInfo(CrashReport.makeCrashReport(ex, "Encountered exception while building chunk meshes"), region, blockPos);
|
||||
} finally {
|
||||
if (handoffBuffers != null) scheduler.releaseBuffers(handoffBuffers);
|
||||
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(false);
|
||||
SmoothBiomeColorCache.clearActiveCache();
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
@@ -298,6 +335,95 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prescanDeferred(IBlockAccess region, int minX, int minY, int minZ, long[] mask) {
|
||||
Arrays.fill(mask, 0L);
|
||||
boolean any = false;
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
final Block block = region.getBlock(minX + x, minY + y, minZ + z);
|
||||
if (block != Blocks.air && !canRenderOffThread(block)) {
|
||||
final int bit = (y << 8) | (z << 4) | x;
|
||||
mask[bit >>> 6] |= 1L << bit;
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return any;
|
||||
}
|
||||
|
||||
private static boolean isDeferred(long[] mask, int x, int y, int z) {
|
||||
final int bit = ((y & 15) << 8) | ((z & 15) << 4) | (x & 15);
|
||||
return (mask[bit >>> 6] & (1L << bit)) != 0;
|
||||
}
|
||||
|
||||
ChunkJobResult<ChunkBuildOutput> completeDeferred(DeferredSectionMesh m, AngelicaChunkBuildContext ctx) {
|
||||
final long start = System.nanoTime();
|
||||
|
||||
final int minX = this.render.getOriginX();
|
||||
final int minY = this.render.getOriginY();
|
||||
final int minZ = this.render.getOriginZ();
|
||||
|
||||
final WorldSlice slice = m.slice;
|
||||
final BlockPos blockPos = new BlockPos(minX, minY, minZ);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
final List<DeferredBlock> blocks = m.blocks;
|
||||
|
||||
if (Tracy.ENABLED) {
|
||||
Tracy.beginZone(Z_MESH_DEFERRED_MAIN);
|
||||
Tracy.zoneValue(blocks.size());
|
||||
}
|
||||
|
||||
try {
|
||||
ctx.setupLightPipeline(slice, minX, minY, minZ);
|
||||
ctx.setupDynamicLights(minX, minY, minZ);
|
||||
slice.setRenderingBlock(null);
|
||||
|
||||
SmoothBiomeColorCache.setActiveCache(slice.getBiomeColorCache());
|
||||
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(true);
|
||||
tessellator.setTranslation(-minX, -minY, -minZ);
|
||||
|
||||
final RenderBlocks renderBlocks = new RenderBlocks(slice);
|
||||
final BlockRenderContext blockRenderContext = ctx.getBlockRenderContext();
|
||||
|
||||
for (int i = 0, n = blocks.size(); i < n; i++) {
|
||||
final DeferredBlock deferred = blocks.get(i);
|
||||
blockPos.set(deferred.x(), deferred.y(), deferred.z());
|
||||
slice.setRenderingBlock(deferred.block());
|
||||
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
|
||||
tessellator, renderBlocks, m.buffers, ctx, blockRenderContext, minX, minY, minZ,
|
||||
deferred.materialOverride(), deferred.isShaderPackOverride(), m.teMap, m.currentTick);
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_MESH_FINALIZE);
|
||||
final Reference2ReferenceMap<TerrainRenderPass, BuiltSectionMeshParts> meshes;
|
||||
try {
|
||||
meshes = BuiltSectionMeshParts.groupFromBuildBuffers(m.buffers, (float) camera.x - minX, (float) camera.y - minY, (float) camera.z - minZ);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
|
||||
if (!meshes.isEmpty()) {
|
||||
m.renderData.hasBlockGeometry = true;
|
||||
}
|
||||
|
||||
CeleritasDebug.incrementChunkUpdateCounter();
|
||||
|
||||
return new ChunkJobResult.Success<>(new ChunkBuildOutput(this.render, m.renderData, meshes, this.buildTime), m.workerNanos + (System.nanoTime() - start));
|
||||
} catch (ReportedException ex) {
|
||||
return new ChunkJobResult.Failure<>(fillCrashInfo(ex.getCrashReport(), slice, blockPos));
|
||||
} catch (Throwable ex) {
|
||||
return new ChunkJobResult.Failure<>(fillCrashInfo(CrashReport.makeCrashReport(ex, "Encountered exception while building deferred chunk meshes"), slice, blockPos));
|
||||
} finally {
|
||||
((StateAwareTessellator)tessellator).angelica$setCeleritasMeshing(false);
|
||||
tessellator.setTranslation(0, 0, 0);
|
||||
SmoothBiomeColorCache.clearActiveCache();
|
||||
RenderPassHelper.resetWorldRenderPass();
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride, NbtConditionalIdMap<Block> teMap, long currentTick) {
|
||||
|
||||
final var blockMaterial = block.getMaterial();
|
||||
@@ -342,7 +468,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
int teBlockId = BlockRenderingSettings.getCachedTeNbtId(packedPos, currentTick);
|
||||
|
||||
if (teBlockId == BlockRenderingSettings.CACHE_MISS) {
|
||||
final TileEntity te = getBlockAccess().getTileEntity(x, y, z);
|
||||
final TileEntity te = renderBlocks.blockAccess.getTileEntity(x, y, z);
|
||||
if (te != null) {
|
||||
final NBTTagCompound teNbt = new NBTTagCompound();
|
||||
te.writeToNBT(teNbt);
|
||||
@@ -387,54 +513,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ, NbtConditionalIdMap<Block> teMap, long currentTick) {
|
||||
|
||||
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
|
||||
|
||||
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||
final RenderBlocks mainThreadRenderBlocks = new RenderBlocks(region);
|
||||
final Tessellator mainTessellator = Tessellator.instance;
|
||||
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(true);
|
||||
mainTessellator.setTranslation(-minX, -minY, -minZ);
|
||||
|
||||
try {
|
||||
for (DeferredBlock deferred : deferredBlocks) {
|
||||
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
|
||||
mainTessellator, mainThreadRenderBlocks, buffers, buildContext,
|
||||
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride(), teMap, currentTick);
|
||||
}
|
||||
} finally {
|
||||
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(false);
|
||||
mainTessellator.setTranslation(0, 0, 0);
|
||||
}
|
||||
}, AngelicaRenderQueue.executor());
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int retries = 0;
|
||||
while (retries < MAX_RETRIES) {
|
||||
try {
|
||||
future.get(DEFERRED_BLOCK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
||||
return;
|
||||
} catch (TimeoutException e) {
|
||||
retries++;
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
return;
|
||||
}
|
||||
if (retries >= MAX_RETRIES) {
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Error during main thread deferred block rendering", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ReportedException fillCrashInfo(CrashReport report, IBlockAccess slice, BlockPos pos) {
|
||||
final CrashReportCategory crashReportSection = report.makeCategory("Block being rendered");
|
||||
|
||||
|
||||
+20
-1
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskRegistry;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
@@ -20,6 +21,7 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.ChunkUpdateType;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
|
||||
@@ -51,6 +53,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
private static final long P_MESH_DEVICE_USED = Tracy.plotHandle("mesh.deviceUsed", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_DRAW_REGION_BUFFERS = Tracy.plotHandle("draw.regionBuffers");
|
||||
private static final long P_MESH_DEVICE_ALLOCATED = Tracy.plotHandle("mesh.deviceAllocated", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_MESH_DEFERRED_PENDING = Tracy.plotHandle("mesh.deferredPending");
|
||||
|
||||
private static final Tracy.ZoneId Z_BIOME_REBUILDS = Tracy.zoneId("biomeRebuilds", Tracy.COLOR_TERRAIN);
|
||||
private static final Tracy.ZoneId Z_GRAPH_SEARCH = Tracy.zoneId("graphSearch", Tracy.COLOR_TERRAIN);
|
||||
@@ -63,12 +66,14 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
private final ClonedChunkSectionCache sectionCache;
|
||||
private final ChunkTaskProvider taskProvider;
|
||||
private final LongOpenHashSet biomeRebuildColumns = new LongOpenHashSet();
|
||||
private final DeferredMeshScheduler deferredScheduler;
|
||||
|
||||
public AngelicaRenderSectionManager(RenderPassConfiguration<?> configuration, WorldClient world, int renderDistance, CommandList commandList, int minSection, int maxSection, int requestedThreads, ChunkTaskProvider taskProvider) {
|
||||
super(configuration, () -> new AngelicaChunkBuildContext(configuration, world), AngelicaChunkRenderer::new, renderDistance, commandList, minSection, maxSection, requestedThreads, true /* hasShadowPass = true for Iris */);
|
||||
this.world = world;
|
||||
this.sectionCache = new ClonedChunkSectionCache(world);
|
||||
this.taskProvider = taskProvider;
|
||||
this.deferredScheduler = new DeferredMeshScheduler(configuration, () -> new AngelicaChunkBuildContext(configuration, world), () -> new WorldSlice(world), AngelicaRenderQueue::submit, ((RenderSectionManagerAccessor) this).angelica$getBuildResults());
|
||||
}
|
||||
|
||||
public static AngelicaRenderSectionManager create(ChunkVertexType vertexType, WorldClient world, int renderDistance, CommandList commandList) {
|
||||
@@ -168,7 +173,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.taskProvider.createRebuildTask(render, frame, this.cameraPosition, this.sectionCache);
|
||||
final ChunkBuilderTask<ChunkBuildOutput> task = this.taskProvider.createRebuildTask(render, frame, this.cameraPosition, this.sectionCache);
|
||||
if (task instanceof AngelicaChunkBuilderMeshingTask t) {
|
||||
final ChunkUpdateType pending = render.getPendingUpdate();
|
||||
t.bindScheduler(this.deferredScheduler, pending != null && pending.isImportant());
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -217,11 +227,19 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_GRAPH_SEARCH);
|
||||
try {
|
||||
super.updateChunks(updateImmediately);
|
||||
this.deferredScheduler.runImportant();
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.deferredScheduler.markDestroyed();
|
||||
super.destroy();
|
||||
this.deferredScheduler.runImportant();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadChunks() {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_SECTION_UPLOAD);
|
||||
@@ -353,5 +371,6 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
Tracy.plotInt(P_MESH_DEVICE_USED, mem.deviceUsed + mem.indexUsed);
|
||||
Tracy.plotInt(P_MESH_DEVICE_ALLOCATED, mem.deviceAllocated + mem.indexAllocated);
|
||||
Tracy.plotInt(P_DRAW_REGION_BUFFERS, mem.bufferCount);
|
||||
Tracy.plotInt(P_MESH_DEFERRED_PENDING, this.deferredScheduler.pendingCount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.Material;
|
||||
|
||||
public record DeferredBlock(int x, int y, int z, Block block, int meta, int pass, Material materialOverride, boolean isShaderPackOverride) {}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DeferredMeshScheduler {
|
||||
private final Supplier<AngelicaChunkBuildContext> mainContextFactory;
|
||||
private final Consumer<Runnable> mainThreadQueue;
|
||||
private final Queue<ChunkJobResult<? extends ChunkTaskOutput>> results;
|
||||
private final ObjectPool<ChunkBuildBuffers> buffers;
|
||||
private final ObjectPool<WorldSlice> slices;
|
||||
private final ConcurrentLinkedQueue<DeferredSectionMesh> important = new ConcurrentLinkedQueue<>();
|
||||
private final ConcurrentLinkedQueue<DeferredSectionMesh> normal = new ConcurrentLinkedQueue<>();
|
||||
private final Runnable runNext = this::runNext;
|
||||
private final AtomicInteger pending = new AtomicInteger();
|
||||
|
||||
private AngelicaChunkBuildContext mainContext;
|
||||
private boolean destroyed;
|
||||
|
||||
public DeferredMeshScheduler(RenderPassConfiguration<?> config, Supplier<AngelicaChunkBuildContext> mainContextFactory, Supplier<WorldSlice> sliceFactory, Consumer<Runnable> mainThreadQueue, Queue<ChunkJobResult<? extends ChunkTaskOutput>> results) {
|
||||
this.mainContextFactory = mainContextFactory;
|
||||
this.mainThreadQueue = mainThreadQueue;
|
||||
this.results = results;
|
||||
this.buffers = new ObjectPool<>(() -> new ChunkBuildBuffers(config));
|
||||
this.slices = new ObjectPool<>(sliceFactory);
|
||||
}
|
||||
|
||||
public ChunkBuildBuffers acquireBuffers() {
|
||||
return buffers.acquire();
|
||||
}
|
||||
|
||||
public void releaseBuffers(ChunkBuildBuffers buffers) {
|
||||
buffers.destroy();
|
||||
this.buffers.release(buffers);
|
||||
}
|
||||
|
||||
public WorldSlice acquireSlice() {
|
||||
return slices.acquire();
|
||||
}
|
||||
|
||||
public void submit(DeferredSectionMesh m) {
|
||||
pending.incrementAndGet();
|
||||
if (m.important) {
|
||||
important.add(m);
|
||||
} else {
|
||||
normal.add(m);
|
||||
mainThreadQueue.accept(runNext);
|
||||
}
|
||||
}
|
||||
|
||||
public void runImportant() {
|
||||
DeferredSectionMesh m;
|
||||
while ((m = important.poll()) != null) {
|
||||
run(m);
|
||||
}
|
||||
}
|
||||
|
||||
public void markDestroyed() {
|
||||
destroyed = true;
|
||||
if (mainContext != null) {
|
||||
mainContext.cleanup();
|
||||
mainContext = null;
|
||||
}
|
||||
}
|
||||
|
||||
public int pendingCount() {
|
||||
return pending.get();
|
||||
}
|
||||
|
||||
private AngelicaChunkBuildContext mainContext() {
|
||||
if (mainContext == null) {
|
||||
mainContext = mainContextFactory.get();
|
||||
}
|
||||
return mainContext;
|
||||
}
|
||||
|
||||
private void runNext() {
|
||||
final DeferredSectionMesh m = normal.poll();
|
||||
if (m != null) {
|
||||
run(m);
|
||||
}
|
||||
}
|
||||
|
||||
private void run(DeferredSectionMesh m) {
|
||||
try {
|
||||
if (destroyed || m.isObsolete()) return;
|
||||
results.add(m.complete(mainContext()));
|
||||
} finally {
|
||||
releaseBuffers(m.buffers);
|
||||
slices.release(m.slice);
|
||||
pending.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
|
||||
import net.minecraft.block.Block;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
import org.embeddedt.embeddium.impl.util.task.CancellationToken;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeferredSectionMesh {
|
||||
public final AngelicaChunkBuilderMeshingTask task;
|
||||
public final CancellationToken token;
|
||||
public final ChunkBuildBuffers buffers;
|
||||
public final WorldSlice slice;
|
||||
public final AngelicaBuiltRenderSectionData renderData;
|
||||
public final List<DeferredBlock> blocks;
|
||||
public final NbtConditionalIdMap<Block> teMap;
|
||||
public final long currentTick;
|
||||
public final boolean important;
|
||||
public final long workerNanos;
|
||||
|
||||
public DeferredSectionMesh(AngelicaChunkBuilderMeshingTask task, CancellationToken token, ChunkBuildBuffers buffers, WorldSlice slice, AngelicaBuiltRenderSectionData renderData, List<DeferredBlock> blocks, NbtConditionalIdMap<Block> teMap, long currentTick, boolean important, long workerNanos) {
|
||||
this.task = task;
|
||||
this.token = token;
|
||||
this.buffers = buffers;
|
||||
this.slice = slice;
|
||||
this.renderData = renderData;
|
||||
this.blocks = blocks;
|
||||
this.teMap = teMap;
|
||||
this.currentTick = currentTick;
|
||||
this.important = important;
|
||||
this.workerNanos = workerNanos;
|
||||
}
|
||||
|
||||
public boolean isObsolete() {
|
||||
return token.isCancelled() || task.render.isDisposed() || task.render.getLastBuiltFrame() > task.buildTime;
|
||||
}
|
||||
|
||||
public ChunkJobResult<ChunkBuildOutput> complete(AngelicaChunkBuildContext ctx) {
|
||||
return task.completeDeferred(this, ctx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class ObjectPool<T> {
|
||||
private final ConcurrentLinkedQueue<T> free = new ConcurrentLinkedQueue<>();
|
||||
private final Supplier<T> factory;
|
||||
|
||||
ObjectPool(Supplier<T> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
T acquire() {
|
||||
final T t = free.poll();
|
||||
return t != null ? t : factory.get();
|
||||
}
|
||||
|
||||
void release(T t) {
|
||||
free.add(t);
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -94,8 +94,12 @@ public class ThreadedAngelicaChunkBuilderMeshingTask extends AngelicaChunkBuilde
|
||||
@Override
|
||||
protected boolean canRenderOffThread(Block block) {
|
||||
final int renderType = block.getRenderType();
|
||||
return (renderType >= 0 && renderType <= MAX_VANILLA_RENDER_TYPE)
|
||||
|| AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block, true, true);
|
||||
return renderType == -1 || (renderType >= 0 && renderType <= MAX_VANILLA_RENDER_TYPE) || AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canHandOff() {
|
||||
return renderContext != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
@@ -4,6 +4,8 @@ import com.gtnewhorizons.angelica.mixins.interfaces.RenderSectionManagerAccessor
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
@@ -17,6 +19,8 @@ public class MixinRenderSectionManager implements RenderSectionManagerAccessor {
|
||||
|
||||
@Shadow @Final private ConcurrentLinkedDeque<Runnable> asyncSubmittedTasks;
|
||||
|
||||
@Shadow @Final private ConcurrentLinkedDeque<ChunkJobResult<? extends ChunkTaskOutput>> buildResults;
|
||||
|
||||
@Override
|
||||
public Long2ReferenceMap<RenderSection> angelica$getSectionByPosition() {
|
||||
return sectionByPosition;
|
||||
@@ -26,4 +30,9 @@ public class MixinRenderSectionManager implements RenderSectionManagerAccessor {
|
||||
public ConcurrentLinkedDeque<Runnable> angelica$getAsyncSubmittedTasks() {
|
||||
return asyncSubmittedTasks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConcurrentLinkedDeque<ChunkJobResult<? extends ChunkTaskOutput>> angelica$getBuildResults() {
|
||||
return buildResults;
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DeferredMeshSchedulerTest {
|
||||
|
||||
private static DeferredSectionMesh mesh(ChunkBuildBuffers buffers, WorldSlice slice, boolean important, boolean obsolete, ChunkJobResult<ChunkBuildOutput> result) {
|
||||
return new DeferredSectionMesh(null, null, buffers, slice, null, null, null, 0L, important, 0L) {
|
||||
@Override
|
||||
public boolean isObsolete() {
|
||||
return obsolete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkJobResult<ChunkBuildOutput> complete(AngelicaChunkBuildContext ctx) {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ChunkJobResult<ChunkBuildOutput> success() {
|
||||
return new ChunkJobResult.Success<>(null, 0L);
|
||||
}
|
||||
|
||||
private static ChunkJobResult<ChunkBuildOutput> failure() {
|
||||
return new ChunkJobResult.Failure<>(new RuntimeException("x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importantItemRunsOnRunImportant() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), true, false, success());
|
||||
scheduler.submit(m);
|
||||
|
||||
assertEquals(0, queue.size());
|
||||
assertEquals(1, scheduler.pendingCount());
|
||||
|
||||
scheduler.runImportant();
|
||||
|
||||
assertEquals(1, results.size());
|
||||
assertTrue(results.peek() instanceof ChunkJobResult.Success);
|
||||
assertEquals(0, scheduler.pendingCount());
|
||||
assertSame(buffers, scheduler.acquireBuffers());
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalItemRunsThroughMainThreadQueue() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, success());
|
||||
scheduler.submit(m);
|
||||
|
||||
assertEquals(1, queue.size());
|
||||
|
||||
scheduler.runImportant();
|
||||
assertEquals(0, results.size());
|
||||
|
||||
queue.poll().run();
|
||||
|
||||
assertEquals(1, results.size());
|
||||
assertTrue(results.peek() instanceof ChunkJobResult.Success);
|
||||
assertEquals(0, scheduler.pendingCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importantItemAfterDestroyReleasesBuffers() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
WorldSlice slice = scheduler.acquireSlice();
|
||||
DeferredSectionMesh m = mesh(buffers, slice, true, false, success());
|
||||
scheduler.submit(m);
|
||||
scheduler.markDestroyed();
|
||||
scheduler.runImportant();
|
||||
|
||||
assertEquals(0, results.size());
|
||||
assertEquals(0, scheduler.pendingCount());
|
||||
assertSame(buffers, scheduler.acquireBuffers());
|
||||
assertSame(slice, scheduler.acquireSlice());
|
||||
}
|
||||
|
||||
@Test
|
||||
void markDestroyedDiscardsQueuedItemAndReturnsBuffersToPool() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, success());
|
||||
scheduler.submit(m);
|
||||
scheduler.markDestroyed();
|
||||
queue.poll().run();
|
||||
|
||||
assertEquals(0, results.size());
|
||||
assertEquals(0, scheduler.pendingCount());
|
||||
assertSame(buffers, scheduler.acquireBuffers());
|
||||
}
|
||||
|
||||
@Test
|
||||
void obsoleteItemIsDiscarded() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, true, success());
|
||||
scheduler.submit(m);
|
||||
queue.poll().run();
|
||||
|
||||
assertEquals(0, results.size());
|
||||
assertEquals(0, scheduler.pendingCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void failureResultIsRecordedAndCountersStillDecrement() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, failure());
|
||||
scheduler.submit(m);
|
||||
queue.poll().run();
|
||||
|
||||
assertEquals(1, results.size());
|
||||
assertTrue(results.peek() instanceof ChunkJobResult.Failure);
|
||||
assertEquals(0, scheduler.pendingCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
void acquireBuffersAfterCompletedRunReturnsTheSameInstance() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, success());
|
||||
scheduler.submit(m);
|
||||
queue.poll().run();
|
||||
|
||||
assertSame(buffers, scheduler.acquireBuffers());
|
||||
}
|
||||
|
||||
@Test
|
||||
void runReturnsSliceToPool() {
|
||||
ArrayDeque<Runnable> queue = new ArrayDeque<>();
|
||||
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
|
||||
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
|
||||
|
||||
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
|
||||
WorldSlice slice = scheduler.acquireSlice();
|
||||
DeferredSectionMesh m = mesh(buffers, slice, false, false, success());
|
||||
scheduler.submit(m);
|
||||
queue.poll().run();
|
||||
|
||||
assertSame(slice, scheduler.acquireSlice());
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ThreadedAngelicaChunkBuilderMeshingTask;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.util.task.CancellationToken;
|
||||
import org.joml.Vector3d;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DeferredSectionMeshTest {
|
||||
|
||||
private static DeferredSectionMesh mesh(RenderSection section, int buildTime, boolean cancelled) {
|
||||
final ThreadedAngelicaChunkBuilderMeshingTask task = new ThreadedAngelicaChunkBuilderMeshingTask(section, null, buildTime, new Vector3d());
|
||||
final CancellationToken token = new CancellationToken() {
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCancelled() {}
|
||||
};
|
||||
return new DeferredSectionMesh(task, token, null, null, null, null, null, 0L, false, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void freshSectionIsNotObsolete() {
|
||||
final RenderSection section = new RenderSection(null, 0, 0, 0);
|
||||
assertFalse(mesh(section, 5, false).isObsolete());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameBuiltFrameIsNotObsolete() {
|
||||
final RenderSection section = new RenderSection(null, 0, 0, 0);
|
||||
section.setLastBuiltFrame(5);
|
||||
assertFalse(mesh(section, 5, false).isObsolete());
|
||||
}
|
||||
|
||||
@Test
|
||||
void newerBuiltFrameIsObsolete() {
|
||||
final RenderSection section = new RenderSection(null, 0, 0, 0);
|
||||
section.setLastBuiltFrame(6);
|
||||
assertTrue(mesh(section, 5, false).isObsolete());
|
||||
}
|
||||
|
||||
@Test
|
||||
void cancelledTokenIsObsolete() {
|
||||
final RenderSection section = new RenderSection(null, 0, 0, 0);
|
||||
assertTrue(mesh(section, 5, true).isObsolete());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedSectionIsObsolete() {
|
||||
final RenderSection section = new RenderSection(null, 0, 0, 0);
|
||||
section.delete();
|
||||
assertTrue(mesh(section, 5, false).isObsolete());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ObjectPoolTest {
|
||||
|
||||
@Test
|
||||
void acquireOnEmptyPoolCallsFactoryOnce() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
ObjectPool<Object> pool = new ObjectPool<>(() -> {
|
||||
calls.incrementAndGet();
|
||||
return new Object();
|
||||
});
|
||||
|
||||
Object o = pool.acquire();
|
||||
|
||||
assertTrue(o != null);
|
||||
assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseThenAcquireReturnsSameInstanceWithoutFactory() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
ObjectPool<Object> pool = new ObjectPool<>(() -> {
|
||||
calls.incrementAndGet();
|
||||
return new Object();
|
||||
});
|
||||
|
||||
Object o = pool.acquire();
|
||||
pool.release(o);
|
||||
Object reacquired = pool.acquire();
|
||||
|
||||
assertSame(o, reacquired);
|
||||
assertEquals(1, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoReleasesThenTwoAcquiresReturnBothInstances() {
|
||||
AtomicInteger calls = new AtomicInteger();
|
||||
ObjectPool<Object> pool = new ObjectPool<>(() -> {
|
||||
calls.incrementAndGet();
|
||||
return new Object();
|
||||
});
|
||||
|
||||
Object a = pool.acquire();
|
||||
Object b = pool.acquire();
|
||||
pool.release(a);
|
||||
pool.release(b);
|
||||
|
||||
Set<Object> reacquired = new HashSet<>();
|
||||
reacquired.add(pool.acquire());
|
||||
reacquired.add(pool.acquire());
|
||||
|
||||
assertEquals(2, reacquired.size());
|
||||
assertTrue(reacquired.contains(a));
|
||||
assertTrue(reacquired.contains(b));
|
||||
assertEquals(2, calls.get());
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas.threading;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.joml.Vector3d;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ThreadedCanRenderOffThreadTest {
|
||||
|
||||
private static class RenderTypeBlock extends Block {
|
||||
private final int renderType;
|
||||
|
||||
RenderTypeBlock(int renderType) {
|
||||
super(Material.rock);
|
||||
this.renderType = renderType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRenderType() {
|
||||
return renderType;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void invisibleAndVanillaRenderTypesRenderOffThread() {
|
||||
final ThreadedAngelicaChunkBuilderMeshingTask task = new ThreadedAngelicaChunkBuilderMeshingTask(new RenderSection(null, 0, 0, 0), null, 0, new Vector3d());
|
||||
assertTrue(task.canRenderOffThread(new RenderTypeBlock(-1)));
|
||||
assertTrue(task.canRenderOffThread(new RenderTypeBlock(0)));
|
||||
assertTrue(task.canRenderOffThread(new RenderTypeBlock(41)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user