work on async model factory processing

This commit is contained in:
mcrcortex
2025-10-23 20:21:10 +10:00
parent b15b70860b
commit fbe001f559
3 changed files with 92 additions and 30 deletions

View File

@@ -92,6 +92,7 @@ public class ModelBakerySubsystem {
if (totalBudget<(System.nanoTime()-start)) if (totalBudget<(System.nanoTime()-start))
break; break;
} }
this.factory.processUploads();
//TimingStatistics.modelProcess.stop(); //TimingStatistics.modelProcess.stop();
} }

View File

@@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ObjectSet;
import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery; import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream; import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream; import me.cortex.voxy.client.core.rendering.util.UploadStream;
@@ -36,6 +38,7 @@ import net.minecraft.world.chunk.light.LightingProvider;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.lang.invoke.VarHandle;
import java.util.*; import java.util.*;
import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE; import static me.cortex.voxy.client.core.model.ModelStore.MODEL_SIZE;
@@ -118,6 +121,7 @@ public class ModelFactory {
private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream private final RawDownloadStream downstream = new RawDownloadStream(8*1024*1024);//8mb downstream
private final Deque<RawBakeResult> rawBakeResults = new ArrayDeque<>(); private final Deque<RawBakeResult> rawBakeResults = new ArrayDeque<>();
private final Deque<ModelBakeResultUpload> uploadResults = new ArrayDeque<>();
private Object2IntMap<BlockState> customBlockStateIdMapping; private Object2IntMap<BlockState> customBlockStateIdMapping;
@@ -170,6 +174,13 @@ public class ModelFactory {
return false; return false;
} }
VarHandle.loadLoadFence();
//We need to get it twice cause of threading
if (this.idMappings[blockId] != -1) {
return false;
}
var blockState = this.mapper.getBlockStateFromBlockId(blockId); var blockState = this.mapper.getBlockStateFromBlockId(blockId);
//Before we enqueue the baking of this blockstate, we must check if it has a fluid state associated with it //Before we enqueue the baking of this blockstate, we must check if it has a fluid state associated with it
@@ -219,13 +230,69 @@ public class ModelFactory {
} }
} }
result.rawData.free(); result.rawData.free();
this.processTextureBakeResult(result.blockId, result.blockState, textureData); var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData);
if (bakeResult!=null) {
this.uploadResults.add(bakeResult);
}
return !this.rawBakeResults.isEmpty(); return !this.rawBakeResults.isEmpty();
} }
public void processUploads() {
if (this.uploadResults.isEmpty()) return;
//This is glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
private void processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) { glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
while (!this.uploadResults.isEmpty()) {
var bakeResult = this.uploadResults.pop();
bakeResult.upload(this.storage.modelBuffer, this.storage.modelColourBuffer, this.storage.textures);
bakeResult.free();
}
UploadStream.INSTANCE.commit();
}
private static final class ModelBakeResultUpload {
private final MemoryBuffer model = new MemoryBuffer(MODEL_SIZE).zero();
private final MemoryBuffer texture = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
public int modelId = -1;
public int biomeUploadIndex = -1;
public @Nullable MemoryBuffer biomeUpload;
public void upload(GlBuffer modelBuffer, GlBuffer colourBuffer, GlTexture atlas) {//Uploads and resets for reuse
this.model.cpyTo(UploadStream.INSTANCE.upload(modelBuffer, (long) this.modelId * MODEL_SIZE, MODEL_SIZE));
if (this.biomeUploadIndex != -1) {
this.biomeUpload.cpyTo(UploadStream.INSTANCE.upload(colourBuffer, this.biomeUploadIndex * 4L, this.biomeUpload.size));
this.biomeUploadIndex = -1;
this.biomeUpload.free();
this.biomeUpload = null;
}
int X = (this.modelId&0xFF) * MODEL_TEXTURE_SIZE*3;
int Y = ((this.modelId>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
long cAddr = this.texture.address;
for (int lvl = 0; lvl < LAYERS; lvl++) {
nglTextureSubImage2D(atlas.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
}
this.modelId = -1;
}
public void free() {
this.model.free();
this.texture.free();
if (this.biomeUpload != null) {
this.biomeUpload.free();
}
}
}
private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData) {
if (this.idMappings[blockId] != -1) { if (this.idMappings[blockId] != -1) {
//This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time! //This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time!
throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState); throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
@@ -265,7 +332,7 @@ public class ModelFactory {
if (!this.blockStatesInFlight.remove(blockId)) { if (!this.blockStatesInFlight.remove(blockId)) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
return; return null;
} else {//Not a duplicate so create a new entry } else {//Not a duplicate so create a new entry
modelId = this.modelTexture2id.size(); modelId = this.modelTexture2id.size();
//NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur //NOTE: we set the mapping at the very end so that race conditions with this and getMetadata dont occur
@@ -298,8 +365,9 @@ public class ModelFactory {
var colourProvider = getColourProvider(blockState.getBlock()); var colourProvider = getColourProvider(blockState.getBlock());
long uploadPtr = UploadStream.INSTANCE.upload(this.storage.modelBuffer, (long) modelId * MODEL_SIZE, MODEL_SIZE); ModelBakeResultUpload uploadResult = new ModelBakeResultUpload();
uploadResult.modelId = modelId;
long uploadPtr = uploadResult.model.address;
//TODO: implement; //TODO: implement;
// TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour // TODO: if it has a constant colour instead... idk why (apparently for things like spruce leaves)?? but premultiply the texture data by the constant colour
@@ -485,9 +553,9 @@ public class ModelFactory {
int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size(); int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size();
MemoryUtil.memPutInt(uploadPtr, biomeIndex); MemoryUtil.memPutInt(uploadPtr, biomeIndex);
this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState)); this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState));
//NOTE: UploadStream.INSTANCE is called _after_ uploadPtr is finished being used, this is cause the upload pointer
// may be invalidated as soon as another upload stream is invoked uploadResult.biomeUploadIndex = biomeIndex;
long clrUploadPtr = UploadStream.INSTANCE.upload(this.storage.modelColourBuffer, biomeIndex * 4L, 4L * this.biomes.size()); long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * this.biomes.size())).address;
for (var biome : this.biomes) { for (var biome : this.biomes) {
MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4; MemoryUtil.memPutInt(clrUploadPtr, captureColourConstant(colourProvider, blockState, biome)|0xFF000000); clrUploadPtr += 4;
} }
@@ -510,7 +578,7 @@ public class ModelFactory {
//TODO callback to inject extra data into the model data //TODO callback to inject extra data into the model data
this.putTextures(modelId, textureData); this.putTextures(textureData, uploadResult.texture);
//glGenerateTextureMipmap(this.textures.id); //glGenerateTextureMipmap(this.textures.id);
@@ -521,9 +589,7 @@ public class ModelFactory {
throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!"); throw new IllegalStateException("processing a texture bake result but the block state was not in flight!!");
} }
//Upload/commit stream return uploadResult;
//TODO maybe dont do it for every uploaded block?? try to batch it
UploadStream.INSTANCE.commit();
} }
public void addBiome(int id, Biome biome) { public void addBiome(int id, Biome biome) {
@@ -723,14 +789,13 @@ public class ModelFactory {
private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4); private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE); private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
//TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping //TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping
private void putTextures(int id, ColourDepthTextureData[] textures) { private void putTextures(ColourDepthTextureData[] textures, MemoryBuffer into) {
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");} //if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
//TODO: need to use a write mask to see what pixels must be used to contribute to mipping //TODO: need to use a write mask to see what pixels must be used to contribute to mipping
// as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont // as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont
//Copy all textures into scratch final long addr = into.address;
final long addr = SCRATCH_TEX.address;
final int LENGTH_B = MODEL_TEXTURE_SIZE*3; final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
for (int i = 0; i < 6; i++) { for (int i = 0; i < 6; i++) {
int x = (i>>1)*MODEL_TEXTURE_SIZE; int x = (i>>1)*MODEL_TEXTURE_SIZE;
@@ -763,26 +828,17 @@ public class ModelFactory {
} }
} }
/*
int X = (id&0xFF) * MODEL_TEXTURE_SIZE*3; */
int Y = ((id>>8)&0xFF) * MODEL_TEXTURE_SIZE*2;
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
long cAddr = addr;
for (int lvl = 0; lvl < LAYERS; lvl++) {
nglTextureSubImage2D(this.storage.textures.id, lvl, X >> lvl, Y >> lvl, (MODEL_TEXTURE_SIZE*3) >> lvl, (MODEL_TEXTURE_SIZE*2) >> lvl, GL_RGBA, GL_UNSIGNED_BYTE, cAddr);
cAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(lvl<<1);
}
} }
public void free() { public void free() {
while (!this.rawBakeResults.isEmpty()) { while (!this.rawBakeResults.isEmpty()) {
this.rawBakeResults.poll().rawData.free(); this.rawBakeResults.poll().rawData.free();
} }
while (!this.uploadResults.isEmpty()) {
this.uploadResults.poll().free();
}
this.downstream.free(); this.downstream.free();
this.bakery.free(); this.bakery.free();
} }

View File

@@ -87,6 +87,11 @@ public class MemoryBuffer extends TrackedObject {
return new MemoryBuffer(this.tracked, this.address, size, this.freeable); return new MemoryBuffer(this.tracked, this.address, size, this.freeable);
} }
public MemoryBuffer zero() {
MemoryUtil.memSet(this.address, 0, this.size);
return this;
}
public ByteBuffer asByteBuffer() { public ByteBuffer asByteBuffer() {
return MemoryUtil.memByteBuffer(this.address, (int) this.size); return MemoryUtil.memByteBuffer(this.address, (int) this.size);
} }