Skip to content

Commit c211d90

Browse files
authored
Off thread safe blocks (#177)
* Whitelist carpentersblocks for off main thread rendering * Add bounds checking to WorldSlice, * Add preliminary `IThreadSafeISBRH` support * Update TODO * Adjust min y * remove unnecessary try/catch * * Keep track of what should be rendered on the main thread * Update AngelicaBlockSafetyRegistry to handle vanilla blocks, and ISBHR's * of -> set
1 parent 537eb06 commit c211d90

9 files changed

Lines changed: 141 additions & 54 deletions

File tree

src/main/java/com/gtnewhorizons/angelica/compat/mojang/BlockPos.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ public static long asLong(int x, int y, int z) {
8080
return l;
8181
}
8282

83+
public BlockPos set(long packedPos) {
84+
return set(unpackLongX(packedPos), unpackLongY(packedPos), unpackLongZ(packedPos));
85+
}
8386

8487
public static int unpackLongX(long packedPos) {
8588
return (int)(packedPos << 64 - BIT_SHIFT_X - SIZE_BITS_X >> 64 - SIZE_BITS_X);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.gtnewhorizons.angelica.interfaces;
2+
3+
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
4+
5+
public interface IThreadSafeISBRH extends ISimpleBlockRenderingHandler {
6+
IThreadSafeISBRH getThreadLocal();
7+
8+
}

src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public enum Mixins {
8787

8888
SODIUM(new Builder("Sodium").addTargetedMod(TargetedMod.VANILLA).setSide(Side.CLIENT)
8989
.setPhase(Phase.EARLY).setApplyIf(() -> AngelicaConfig.enableSodium).addMixinClasses(
90-
"sodium.MixinChunkProviderClient"
90+
"sodium.MixinChunkProviderClient"
9191
,"sodium.MixinBlock"
9292
,"sodium.AccessorBiomeColorEvent"
9393
,"sodium.MixinBiomeGenBase"
@@ -113,6 +113,7 @@ public enum Mixins {
113113
,"sodium.MixinTileEntityRendererDispatcher"
114114
,"sodium.MixinLongHashMap"
115115
,"sodium.MixinRender"
116+
,"sodium.MixinRenderingRegistry"
116117
)
117118
),
118119

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.gtnewhorizons.angelica.mixins.early.sodium;
2+
3+
import com.gtnewhorizons.angelica.interfaces.IThreadSafeISBRH;
4+
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderingRegistryExt;
5+
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
6+
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
7+
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
8+
import cpw.mods.fml.client.registry.RenderingRegistry;
9+
import org.spongepowered.asm.mixin.Mixin;
10+
import org.spongepowered.asm.mixin.Shadow;
11+
import org.spongepowered.asm.mixin.injection.At;
12+
13+
import java.util.Map;
14+
15+
@Mixin(value = RenderingRegistry.class, remap = false)
16+
public class MixinRenderingRegistry implements IRenderingRegistryExt {
17+
@Shadow private Map<Integer, ISimpleBlockRenderingHandler> blockRenderers;
18+
19+
@Override
20+
public ISimpleBlockRenderingHandler getISBRH(int modelId) {
21+
return this.blockRenderers.get(modelId);
22+
}
23+
24+
@WrapOperation(method = { "renderWorldBlock", "renderInventoryBlock", "renderItemAsFull3DBlock" }, at = @At(value="INVOKE", target="Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;"))
25+
private Object getWrapped(Map<Integer, ISimpleBlockRenderingHandler> instance, Object modelId, Operation<ISimpleBlockRenderingHandler> original) {
26+
final ISimpleBlockRenderingHandler res = original.call(instance, modelId);
27+
if(res instanceof IThreadSafeISBRH isbhr) {
28+
return isbhr.getThreadLocal();
29+
}
30+
return res;
31+
}
32+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.gtnewhorizons.angelica.mixins.interfaces;
2+
3+
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
4+
5+
public interface IRenderingRegistryExt {
6+
ISimpleBlockRenderingHandler getISBRH(int modelId);
7+
}
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,56 @@
11
package com.gtnewhorizons.angelica.rendering;
22

3+
import com.gtnewhorizons.angelica.interfaces.IThreadSafeISBRH;
4+
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderingRegistryExt;
5+
import cpw.mods.fml.client.registry.RenderingRegistry;
6+
import cpw.mods.fml.common.Loader;
7+
import cpw.mods.fml.common.versioning.ComparableVersion;
38
import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
49
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
510
import net.minecraft.block.Block;
611

712
import java.util.concurrent.locks.StampedLock;
813

914
public class AngelicaBlockSafetyRegistry {
10-
private static final Reference2BooleanMap<Block> SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
15+
private static final Reference2BooleanMap<Block> BLOCK_SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
16+
private static final Reference2BooleanMap<Block> ISBRH_SAFETY_MAP = new Reference2BooleanOpenHashMap<>();
1117
private static final StampedLock LOCK = new StampedLock();
1218

13-
public static boolean canBlockRenderOffThread(Block block) {
19+
public static boolean canBlockRenderOffThread(Block block, boolean checkISBRH) {
1420
final long stamp = LOCK.readLock();
21+
final Reference2BooleanMap<Block> map = checkISBRH ? ISBRH_SAFETY_MAP : BLOCK_SAFETY_MAP;
1522
boolean isOffThread, shouldPopulate;
1623
try {
17-
isOffThread = SAFETY_MAP.getBoolean(block);
24+
isOffThread = map.getBoolean(block);
1825
if (isOffThread) {
1926
return true; // no need to check if 'false' was due to not being populated
2027
}
2128

22-
shouldPopulate = !SAFETY_MAP.containsKey(block);
29+
shouldPopulate = !map.containsKey(block);
2330
} finally {
2431
LOCK.unlock(stamp);
2532
}
2633

2734
if(shouldPopulate) {
28-
isOffThread = populateCanRenderOffThread(block);
35+
isOffThread = populateCanRenderOffThread(block, map);
2936
}
3037

3138
return isOffThread;
3239
}
3340

34-
private static boolean populateCanRenderOffThread(Block block) {
35-
final boolean canBeOffThread = !(block.getClass().getName().startsWith("gregtech."));
41+
private static boolean populateCanRenderOffThread(Block block, Reference2BooleanMap<Block> map) {
42+
@SuppressWarnings("deprecation")
43+
final boolean canBeOffThread = map == ISBRH_SAFETY_MAP ? ((IRenderingRegistryExt)RenderingRegistry.instance()).getISBRH(block.getRenderType()) instanceof IThreadSafeISBRH : !(block.getClass().getName().startsWith("gregtech."));
3644

3745
final long stamp = LOCK.writeLock();
3846

3947
try {
40-
SAFETY_MAP.put(block, canBeOffThread);
48+
map.put(block, canBeOffThread);
4149
} finally {
4250
LOCK.unlock(stamp);
4351
}
4452

4553
return canBeOffThread;
4654
}
55+
4756
}

src/main/java/me/jellysquid/mods/sodium/client/render/chunk/tasks/ChunkRenderRebuildTask.java

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
77
import com.gtnewhorizons.angelica.rendering.AngelicaBlockSafetyRegistry;
88
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
9+
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
910
import me.jellysquid.mods.sodium.client.SodiumClientMod;
1011
import me.jellysquid.mods.sodium.client.render.chunk.ChunkGraphicsState;
1112
import me.jellysquid.mods.sodium.client.render.chunk.ChunkRenderContainer;
@@ -89,7 +90,7 @@ private boolean rendersOutsideBoundingBox(TileEntity entity, int baseX, int base
8990

9091
private boolean rendersOffThread(Block block) {
9192
final int type = block.getRenderType();
92-
return type < 42 && type != 22 && AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block);
93+
return (type < 42 && type != 22 && AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block, false)) || AngelicaBlockSafetyRegistry.canBlockRenderOffThread(block, true);
9394
}
9495

9596
private void handleRenderBlocksTextures(RenderBlocks rb, ChunkRenderData.Builder builder) {
@@ -104,7 +105,6 @@ private void handleRenderBlocksTextures(RenderBlocks rb, ChunkRenderData.Builder
104105

105106
@Override
106107
public ChunkBuildResult<T> performBuild(ChunkRenderCacheLocal cache, ChunkBuildBuffers buffers, CancellationSource cancellationSource) {
107-
// COMPATIBLITY NOTE: Oculus relies on the LVT of this method being unchanged, at least in 16.5
108108
final ChunkRenderData.Builder renderData = new ChunkRenderData.Builder();
109109
final ChunkOcclusionDataBuilder occluder = new ChunkOcclusionDataBuilder();
110110
final ChunkRenderBounds.Builder bounds = new ChunkRenderBounds.Builder();
@@ -120,9 +120,10 @@ public ChunkBuildResult<T> performBuild(ChunkRenderCacheLocal cache, ChunkBuildB
120120
final int baseY = this.render.getOriginY();
121121
final int baseZ = this.render.getOriginZ();
122122

123-
final BlockPos.Mutable pos = new BlockPos.Mutable();
123+
final BlockPos pos = new BlockPos();
124124
final BlockPos renderOffset = this.offset;
125125

126+
final LongArrayFIFOQueue mainThreadBlocks = new LongArrayFIFOQueue();
126127
boolean hasMainThreadBlocks = false;
127128

128129
for (int relY = 0; relY < 16; relY++) {
@@ -160,6 +161,7 @@ public ChunkBuildResult<T> performBuild(ChunkRenderCacheLocal cache, ChunkBuildB
160161
}
161162
}
162163
} else {
164+
mainThreadBlocks.enqueue(pos.asLong());
163165
hasMainThreadBlocks = true;
164166
}
165167

@@ -200,7 +202,7 @@ public ChunkBuildResult<T> performBuild(ChunkRenderCacheLocal cache, ChunkBuildB
200202
if(hasMainThreadBlocks) {
201203
// Render the other blocks on the main thread
202204
try {
203-
CompletableFuture.runAsync(() -> this.performMainBuild(cache, buffers, cancellationSource, bounds, renderData), AngelicaRenderQueue.executor()).get();
205+
CompletableFuture.runAsync(() -> this.performMainBuild(cache, buffers, cancellationSource, bounds, renderData, mainThreadBlocks), AngelicaRenderQueue.executor()).get();
204206
} catch(InterruptedException e) {
205207
Thread.currentThread().interrupt();
206208
return null;
@@ -230,54 +232,51 @@ public ChunkBuildResult<T> performBuild(ChunkRenderCacheLocal cache, ChunkBuildB
230232

231233
/**
232234
* Render the blocks that should be rendered on the main thread.
233-
*
234-
* TODO: Deduplicate this with the main method above.
235235
*/
236-
private void performMainBuild(ChunkRenderCacheLocal cache, ChunkBuildBuffers buffers, CancellationSource cancellationSource, ChunkRenderBounds.Builder bounds, ChunkRenderData.Builder renderData) {
236+
private void performMainBuild(ChunkRenderCacheLocal cache, ChunkBuildBuffers buffers, CancellationSource cancellationSource, ChunkRenderBounds.Builder bounds, ChunkRenderData.Builder renderData, LongArrayFIFOQueue mainThreadBlocks) {
237237
final WorldSlice slice = cache.getWorldSlice();
238-
final BlockPos.Mutable pos = new BlockPos.Mutable();
238+
final BlockPos pos = new BlockPos();
239239
final int baseX = this.render.getOriginX();
240240
final int baseY = this.render.getOriginY();
241241
final int baseZ = this.render.getOriginZ();
242242
final BlockPos renderOffset = this.offset;
243243
final RenderBlocks rb = new RenderBlocks(slice.getWorld());
244-
for (int relY = 0; relY < 16; relY++) {
244+
while(!mainThreadBlocks.isEmpty()) {
245+
final long longPos = mainThreadBlocks.dequeueLong();
245246
if (cancellationSource.isCancelled()) {
246247
return;
247248
}
248-
for (int relZ = 0; relZ < 16; relZ++) {
249-
for (int relX = 0; relX < 16; relX++) {
250-
final Block block = slice.getBlockRelative(relX + 16, relY + 16, relZ + 16);
249+
pos.set(longPos);
250+
final int relX = pos.getX() - baseX;
251+
final int relY = pos.getY() - baseY;
252+
final int relZ = pos.getZ() - baseZ;
253+
final Block block = slice.getBlockRelative(relX + 16, relY + 16, relZ + 16);
254+
255+
// Only render blocks that need main thread assistance
256+
if (block.getMaterial() == Material.air || rendersOffThread(block)) {
257+
continue;
258+
}
251259

252-
// Only render blocks that need main thread assistance
253-
if (block.getMaterial() == Material.air || rendersOffThread(block)) {
254-
continue;
255-
}
260+
// TODO: Collect data on which render types are hitting this code path most often so mods can be updated slowly
256261

257-
// TODO: Collect data on which render types are hitting this code path most often
258-
// so mods can be updated slowly
262+
final int meta = slice.getBlockMetadataRelative(relX + 16, relY + 16, relZ + 16);
259263

260-
final int meta = slice.getBlockMetadataRelative(relX + 16, relY + 16, relZ + 16);
264+
buffers.setRenderOffset(pos.x - renderOffset.getX(), pos.y - renderOffset.getY(), pos.z - renderOffset.getZ());
265+
if(AngelicaConfig.enableIris) buffers.iris$setLocalPos(relX, relY, relZ);
261266

262-
pos.set(baseX + relX, baseY + relY, baseZ + relZ);
263-
buffers.setRenderOffset(pos.x - renderOffset.getX(), pos.y - renderOffset.getY(), pos.z - renderOffset.getZ());
264-
if(AngelicaConfig.enableIris) buffers.iris$setLocalPos(relX, relY, relZ);
267+
// Do regular block rendering
268+
for (BlockRenderPass pass : BlockRenderPass.VALUES) {
269+
if (block.canRenderInPass(pass.ordinal()) && (!AngelicaConfig.enableSodiumFluidRendering || !(block instanceof IFluidBlock))) {
270+
final long seed = MathUtil.hashPos(pos.x, pos.y, pos.z);
271+
if(AngelicaConfig.enableIris) buffers.iris$setMaterialId(block, ExtendedDataHelper.BLOCK_RENDER_TYPE);
265272

266-
// Do regular block rendering
267-
for (BlockRenderPass pass : BlockRenderPass.VALUES) {
268-
if (block.canRenderInPass(pass.ordinal()) && (!AngelicaConfig.enableSodiumFluidRendering || !(block instanceof IFluidBlock))) {
269-
final long seed = MathUtil.hashPos(pos.x, pos.y, pos.z);
270-
if(AngelicaConfig.enableIris) buffers.iris$setMaterialId(block, ExtendedDataHelper.BLOCK_RENDER_TYPE);
271-
272-
if (cache.getBlockRenderer().renderModel(slice.getWorld(), rb, block, meta, pos, buffers.get(pass), true, seed)) {
273-
bounds.addBlock(relX, relY, relZ);
274-
}
275-
}
273+
if (cache.getBlockRenderer().renderModel(slice.getWorld(), rb, block, meta, pos, buffers.get(pass), true, seed)) {
274+
bounds.addBlock(relX, relY, relZ);
276275
}
277-
278-
if(AngelicaConfig.enableIris) buffers.iris$resetBlockContext();
279276
}
280277
}
278+
279+
if(AngelicaConfig.enableIris) buffers.iris$resetBlockContext();
281280
}
282281

283282
handleRenderBlocksTextures(rb, renderData);

src/main/java/me/jellysquid/mods/sodium/client/render/pipeline/FluidRenderer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public boolean render(IBlockAccess world, WorldSlice slice, Block block, BlockPo
9595
int posZ = pos.z;
9696

9797
Fluid fluid = ((IFluidBlock) block).getFluid();
98+
if(fluid == null) return false;
9899

99100
// Check for occluded sides; if everything is occluded, don't render
100101
boolean sfUp = this.isFluidOccluded(world, posX, posY, posZ, ForgeDirection.UP, fluid);

0 commit comments

Comments
 (0)