This commit is contained in:
mcrcortex
2025-08-16 08:09:52 +10:00
parent 3e50c95c91
commit 70a937bcaa
17 changed files with 1387 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
import net.irisshaders.iris.shaderpack.materialmap.WorldRenderingSettings;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL30;
import java.util.List;
import java.util.function.BooleanSupplier;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL45C.*;
public class IrisVoxyRenderPipeline extends AbstractRenderPipeline {
private final IrisVoxyRenderPipelineData data;
private final FullscreenBlit depthBlit = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
public final DepthFramebuffer fb = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
public final DepthFramebuffer fbTranslucent = new DepthFramebuffer(GL_DEPTH24_STENCIL8);
private final GlBuffer shaderUniforms;
public IrisVoxyRenderPipeline(IrisVoxyRenderPipelineData data, AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
super(nodeManager, nodeCleaner, traversal, frexSupplier);
this.data = data;
if (this.data.thePipeline != null) {
throw new IllegalStateException("Pipeline data already bound");
}
this.data.thePipeline = this;
//Bind the drawbuffers
var oDT = this.data.opaqueDrawTargets;
int[] binding = new int[oDT.length];
for (int i = 0; i < oDT.length; i++) {
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
glNamedFramebufferTexture(this.fb.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, oDT[i], 0);
}
glNamedFramebufferDrawBuffers(this.fb.framebuffer.id, binding);
var tDT = this.data.translucentDrawTargets;
binding = new int[tDT.length];
for (int i = 0; i < tDT.length; i++) {
binding[i] = GL30.GL_COLOR_ATTACHMENT0+i;
glNamedFramebufferTexture(this.fbTranslucent.framebuffer.id, GL30.GL_COLOR_ATTACHMENT0+i, tDT[i], 0);
}
glNamedFramebufferDrawBuffers(this.fbTranslucent.framebuffer.id, binding);
this.fb.framebuffer.verify();
this.fbTranslucent.framebuffer.verify();
if (data.getUniforms() != null) {
this.shaderUniforms = new GlBuffer(data.getUniforms().size());
} else {
this.shaderUniforms = null;
}
}
@Override
public void setupExtraModelBakeryData(ModelBakerySubsystem modelService) {
modelService.factory.setCustomBlockStateMapping(WorldRenderingSettings.INSTANCE.getBlockStateIds());
}
@Override
public void free() {
if (this.data.thePipeline != this) {
throw new IllegalStateException();
}
this.data.thePipeline = null;
this.depthBlit.delete();
this.fb.free();
this.fbTranslucent.free();
if (this.shaderUniforms != null) {
this.shaderUniforms.free();
}
super.free0();
}
@Override
protected int setup(Viewport<?> viewport, int sourceFramebuffer) {
if (this.shaderUniforms != null) {
//Update the uniforms
long ptr = UploadStream.INSTANCE.uploadTo(this.shaderUniforms);
this.data.getUniforms().updater().accept(ptr);
UploadStream.INSTANCE.commit();
}
this.fb.resize(viewport.width, viewport.height);
this.fbTranslucent.resize(viewport.width, viewport.height);
if (false) {//TODO: only do this if shader specifies
//Clear the colour component
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.framebuffer.id);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
this.initDepthStencil(sourceFramebuffer, this.fb.framebuffer.id, viewport.width, viewport.height);
return this.fb.getDepthTex().id;
}
@Override
protected void postOpaquePreTranslucent(Viewport<?> viewport) {
int msk = GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT;
if (true) {//TODO: make shader specified
if (false) {//TODO: only do this if shader specifies
glBindFramebuffer(GL_FRAMEBUFFER, this.fbTranslucent.framebuffer.id);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
} else {
msk |= GL_COLOR_BUFFER_BIT;
}
glBlitNamedFramebuffer(this.fb.framebuffer.id, this.fbTranslucent.framebuffer.id, 0,0, viewport.width, viewport.height, 0,0, viewport.width, viewport.height, msk, GL_NEAREST);
}
@Override
protected void finish(Viewport<?> viewport, int sourceFrameBuffer) {
glColorMask(false, false, false, false);
AbstractRenderPipeline.transformBlitDepth(this.depthBlit,
this.fbTranslucent.getDepthTex().id, sourceFrameBuffer,
viewport, new Matrix4f(viewport.vanillaProjection).mul(viewport.modelView));
glColorMask(true, true, true, true);
}
@Override
public void setupAndBindOpaque(Viewport<?> viewport) {
this.fb.bind();
if (this.shaderUniforms != null) {
GL30.glBindBufferBase(GL_UNIFORM_BUFFER, 5, this.shaderUniforms.id);// todo: dont randomly select this to 5
}
}
@Override
public void setupAndBindTranslucent(Viewport<?> viewport) {
this.fbTranslucent.bind();
if (this.shaderUniforms != null) {
GL30.glBindBufferBase(GL_UNIFORM_BUFFER, 5, this.shaderUniforms.id);// todo: dont randomly select this to 5
}
if (this.data.getBlender() != null) {
this.data.getBlender().run();
}
}
@Override
public void addDebug(List<String> debug) {
debug.add("Using: " + this.getClass().getSimpleName());
super.addDebug(debug);
}
@Override
public String patchOpaqueShader(AbstractSectionRenderer<?, ?> renderer, String input) {
StringBuilder builder = new StringBuilder(input).append("\n\n\n");
if (this.data.getUniforms() != null) {
//TODO make ths binding point... not randomly 5
builder.append("layout(binding = 5, std140) uniform ShaderUniformBindings ")
.append(this.data.getUniforms().layout())
.append(";\n\n\n");
}
builder.append(this.data.opaqueFragPatch());
return builder.toString();
}
@Override
public String patchTranslucentShader(AbstractSectionRenderer<?, ?> renderer, String input) {
if (this.data.translucentFragPatch() == null) return null;
StringBuilder builder = new StringBuilder(input).append("\n\n\n");
if (this.data.getUniforms() != null) {
//TODO make ths binding point... not randomly 5
builder.append("layout(binding = 5, std140) uniform ShaderUniformBindings ")
.append(this.data.getUniforms().layout())
.append(";\n\n\n");
}
builder.append(this.data.translucentFragPatch());
return builder.toString();
}
}

View File

@@ -0,0 +1,41 @@
package me.cortex.voxy.client.core;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData;
import me.cortex.voxy.common.Logger;
import net.irisshaders.iris.Iris;
import java.util.function.BooleanSupplier;
public class RenderPipelineFactory {
public static AbstractRenderPipeline createPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
//Note this is where will choose/create e.g. IrisRenderPipeline or normal pipeline
AbstractRenderPipeline pipeline = null;
if (IrisUtil.IRIS_INSTALLED && System.getProperty("voxy.enableExperimentalIrisPipeline", "false").equalsIgnoreCase("true")) {
pipeline = createIrisPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
}
if (pipeline == null) {
pipeline = new NormalRenderPipeline(nodeManager, nodeCleaner, traversal, frexSupplier);
}
return pipeline;
}
private static AbstractRenderPipeline createIrisPipeline(AsyncNodeManager nodeManager, NodeCleaner nodeCleaner, HierarchicalOcclusionTraverser traversal, BooleanSupplier frexSupplier) {
var irisPipe = Iris.getPipelineManager().getPipelineNullable();
if (irisPipe == null) {
return null;
}
if (irisPipe instanceof IGetIrisVoxyPipelineData getVoxyPipeData) {
var pipeData = getVoxyPipeData.voxy$getPipelineData();
if (pipeData == null) {
return null;
}
Logger.info("Creating voxy iris render pipeline");
return new IrisVoxyRenderPipeline(pipeData, nodeManager, nodeCleaner, traversal, frexSupplier);
}
return null;
}
}

View File

@@ -0,0 +1,5 @@
package me.cortex.voxy.client.iris;
public interface IGetIrisVoxyPipelineData {
IrisVoxyRenderPipelineData voxy$getPipelineData();
}

View File

@@ -0,0 +1,5 @@
package me.cortex.voxy.client.iris;
public interface IGetVoxyPatchData {
IrisShaderPatch voxy$getPatchData();
}

View File

@@ -0,0 +1,100 @@
package me.cortex.voxy.client.iris;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.Strictness;
import me.cortex.voxy.common.Logger;
import net.irisshaders.iris.shaderpack.ShaderPack;
import net.irisshaders.iris.shaderpack.include.AbsolutePackPath;
import java.lang.reflect.Modifier;
import java.util.function.Function;
import java.util.function.IntSupplier;
public class IrisShaderPatch {
public static final int VERSION = ((IntSupplier)()->1).getAsInt();
private static class PatchGson {
public int version;//TODO maybe replace with semver?
public int[] opaqueDrawBuffers;
public int[] translucentDrawBuffers;
public String[] uniforms;
public String[] samplers;
public String[] opaquePatchData;
public String[] translucentPatchData;
public boolean checkValid() {
return this.opaqueDrawBuffers != null && this.translucentDrawBuffers != null && this.uniforms != null && this.opaquePatchData != null;
}
}
private final PatchGson patchData;
private final ShaderPack pack;
private IrisShaderPatch(PatchGson patchData, ShaderPack pack) {
this.patchData = patchData;
this.pack = pack;
}
public String getPatchOpaqueSource() {
return String.join("\n", this.patchData.opaquePatchData);
}
public String getPatchTranslucentSource() {
return this.patchData.translucentPatchData!=null?String.join("\n", this.patchData.translucentPatchData):null;
}
public String[] getUniformList() {
return this.patchData.uniforms;
}
public String[] getSamplerList() {
return this.patchData.samplers;
}
public int[] getOpqaueTargets() {
return this.patchData.opaqueDrawBuffers;
}
public int[] getTranslucentTargets() {
return this.patchData.translucentDrawBuffers;
}
private static final Gson GSON = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.setStrictness(Strictness.LENIENT)
.create();
public static IrisShaderPatch makePatch(ShaderPack ipack, AbsolutePackPath directory, Function<AbsolutePackPath, String> sourceProvider) {
String voxyPatchData = sourceProvider.apply(directory.resolve("voxy.json"));
if (voxyPatchData == null) {//No voxy patch data in shaderpack
return null;
}
//A more graceful exit on blank string
if (voxyPatchData.isBlank()) {
return null;
}
//Escape things
voxyPatchData = voxyPatchData.replace("\\", "\\\\");
PatchGson patchData = null;
try {
//TODO: basicly find any "commented out" quotation marks and escape them (if the line, when stripped starts with a // or /* then escape all quotation marks in that line)
patchData = GSON.fromJson(voxyPatchData, PatchGson.class);
if (patchData != null && !patchData.checkValid()) {
throw new IllegalStateException("voxy json patch not valid: " + voxyPatchData);
}
} catch (Exception e) {
patchData = null;
Logger.error("Failed to parse patch data gson",e);
}
if (patchData == null) {
return null;
}
if (patchData.version != VERSION) {
Logger.error("Shader has voxy patch data, but patch version is incorrect. expected " + VERSION + " got "+patchData.version);
return null;
}
return new IrisShaderPatch(patchData, ipack);
}
}

View File

@@ -0,0 +1,331 @@
package me.cortex.voxy.client.iris;
import com.google.common.collect.ImmutableSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
import kroppeb.stareval.function.FunctionReturn;
import kroppeb.stareval.function.Type;
import me.cortex.voxy.client.core.IrisVoxyRenderPipeline;
import me.cortex.voxy.client.mixin.iris.CustomUniformsAccessor;
import me.cortex.voxy.client.mixin.iris.IrisRenderingPipelineAccessor;
import me.cortex.voxy.common.Logger;
import net.irisshaders.iris.gl.image.ImageHolder;
import net.irisshaders.iris.gl.sampler.GlSampler;
import net.irisshaders.iris.gl.sampler.SamplerHolder;
import net.irisshaders.iris.gl.state.ValueUpdateNotifier;
import net.irisshaders.iris.gl.texture.InternalTextureFormat;
import net.irisshaders.iris.gl.texture.TextureType;
import net.irisshaders.iris.gl.uniform.*;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
import net.irisshaders.iris.targets.RenderTarget;
import net.irisshaders.iris.targets.RenderTargets;
import net.irisshaders.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.uniforms.custom.cached.*;
import org.joml.*;
import org.lwjgl.system.MemoryUtil;
import java.util.*;
import java.util.function.IntSupplier;
import java.util.function.LongConsumer;
public class IrisVoxyRenderPipelineData {
public IrisVoxyRenderPipeline thePipeline;
public final int[] opaqueDrawTargets;
public final int[] translucentDrawTargets;
private final String opaquePatch;
private final String translucentPatch;
private final StructLayout uniforms;
private final Runnable blendingSetup;
private IrisVoxyRenderPipelineData(IrisShaderPatch patch, int[] opaqueDrawTargets, int[] translucentDrawTargets, StructLayout uniformSet, Runnable blendingSetup) {
this.opaqueDrawTargets = opaqueDrawTargets;
this.translucentDrawTargets = translucentDrawTargets;
this.opaquePatch = patch.getPatchOpaqueSource();
this.translucentPatch = patch.getPatchTranslucentSource();
this.uniforms = uniformSet;
this.blendingSetup = blendingSetup;
}
public StructLayout getUniforms() {
return this.uniforms;
}
public Runnable getBlender() {
return this.blendingSetup;
}
public String opaqueFragPatch() {
return this.opaquePatch;
}
public String translucentFragPatch() {
return this.translucentPatch;
}
public static IrisVoxyRenderPipelineData buildPipeline(IrisRenderingPipeline ipipe, IrisShaderPatch patch, CustomUniforms cu) {
var uniforms = createUniformLayoutStructAndUpdater(createUniformSet(cu, patch));
createImageSet(ipipe, patch);
var opaqueDrawTargets = getDrawBuffers(patch.getOpqaueTargets(), ipipe.getFlippedAfterPrepare(), ((IrisRenderingPipelineAccessor)ipipe).getRenderTargets());
var translucentDrawTargets = getDrawBuffers(patch.getTranslucentTargets(), ipipe.getFlippedAfterPrepare(), ((IrisRenderingPipelineAccessor)ipipe).getRenderTargets());
//TODO: need to transform the string patch with the uniform decleration aswell as sampler declerations
return new IrisVoxyRenderPipelineData(patch, opaqueDrawTargets, translucentDrawTargets, uniforms, null);
}
private static int[] getDrawBuffers(int[] targets, ImmutableSet<Integer> stageWritesToAlt, RenderTargets rt) {
int[] targetTextures = new int[targets.length];
for(int i = 0; i < targets.length; i++) {
RenderTarget target = rt.getOrCreate(targets[i]);
int textureId = stageWritesToAlt.contains(targets[i]) ? target.getAltTexture() : target.getMainTexture();
targetTextures[i] = textureId;
}
return targetTextures;
}
public record StructLayout(int size, String layout, LongConsumer updater) {}
private static StructLayout createUniformLayoutStructAndUpdater(CachedUniform[] uniforms) {
if (uniforms.length == 0) {
return null;
}
List<CachedUniform>[] ordering = new List[]{new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()};
//Creates an optimial struct layout for the uniforms
for (var uniform : uniforms) {
int order = getUniformOrdering(Type.convert(uniform.getType()));
ordering[order].add(uniform);
}
//Emit the ordering, note this is not optimial, but good enough, e.g. if have even number of align 2, emit that after align 4
int pos = 0;
Int2ObjectLinkedOpenHashMap<CachedUniform> layout = new Int2ObjectLinkedOpenHashMap<>();
for (var uniform : ordering[0]) {//Emit exact align 4
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5;
}
if (!ordering[1].isEmpty() && (ordering[1].size()&1)==0) {
//Emit all the align 2 as there is an even number of them
for (var uniform : ordering[1]) {
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5;
}
ordering[1].clear();
}
//Emit align 3
for (var uniform : ordering[2]) {//Emit size odd, alignment must be 4
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5;
//We must get a size 1 to pad to align 4
if (!ordering[3].isEmpty()) {//Size 1
uniform = ordering[3].removeFirst();
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5;
} else {//Padding must be injected
pos += 1;
}
}
//Emit align 2
for (var uniform : ordering[1]) {
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5;
}
//Emit align 1
for (var uniform : ordering[3]) {
layout.put(pos, uniform); pos += getSizeAndAlignment(Type.convert(uniform.getType()))>>5;
}
if (layout.size()!=uniforms.length) {
throw new IllegalStateException();
}
//We have our ordering and aligned offsets, generate an updater aswell as the layout
String structLayout;
{
StringBuilder struct = new StringBuilder("{\n");
for (var pair : layout.int2ObjectEntrySet()) {
struct.append("\t").append(pair.getValue().getType().toString()).append(" ").append(pair.getValue().getName()).append(";\n");
}
struct.append("}");
structLayout = struct.toString();
}
LongConsumer updater;
{
FunctionReturn cacheRetObj = new FunctionReturn();
LongConsumer[] updaters = new LongConsumer[uniforms.length];
int i = 0;
for (var pair : layout.int2ObjectEntrySet()) {
updaters[i++] = createWriter(pair.getIntKey()*4L, cacheRetObj, pair.getValue());
}
updater = ptr -> {
for (var u : updaters) {
u.accept(ptr);
}
};//Writes all the uniforms to the locations
}
return new StructLayout(pos*4, structLayout, updater);//*4 since each slot is 4 bytes
}
private static LongConsumer createWriter(long offset, FunctionReturn ret, CachedUniform uniform) {
if (uniform instanceof BooleanCachedUniform bcu) {
return ptr->{ptr += offset;
bcu.writeTo(ret);
MemoryUtil.memPutInt(ptr, ret.booleanReturn?1:0);
};
} else if (uniform instanceof FloatCachedUniform fcu) {
return ptr->{ptr += offset;
fcu.writeTo(ret);
MemoryUtil.memPutFloat(ptr, ret.floatReturn);
};
} else if (uniform instanceof IntCachedUniform icu) {
return ptr->{ptr += offset;
icu.writeTo(ret);
MemoryUtil.memPutInt(ptr, ret.intReturn);
};
} else if (uniform instanceof Float2VectorCachedUniform v2fcu) {
return ptr->{ptr += offset;
v2fcu.writeTo(ret);
((Vector2f)ret.objectReturn).getToAddress(ptr);
};
} else if (uniform instanceof Float3VectorCachedUniform v3fcu) {
return ptr->{ptr += offset;
v3fcu.writeTo(ret);
((Vector3f)ret.objectReturn).getToAddress(ptr);
};
} else if (uniform instanceof Float4VectorCachedUniform v4fcu) {
return ptr->{ptr += offset;
v4fcu.writeTo(ret);
((Vector4f)ret.objectReturn).getToAddress(ptr);
};
} else if (uniform instanceof Int2VectorCachedUniform v2icu) {
return ptr->{ptr += offset;
v2icu.writeTo(ret);
((Vector2i)ret.objectReturn).getToAddress(ptr);
};
} else if (uniform instanceof Int3VectorCachedUniform v3icu) {
return ptr->{ptr += offset;
v3icu.writeTo(ret);
((Vector3i)ret.objectReturn).getToAddress(ptr);
};
} else if (uniform instanceof Float4MatrixCachedUniform f4mcu) {
return ptr->{ptr += offset;
f4mcu.writeTo(ret);
((Matrix4f)ret.objectReturn).getToAddress(ptr);
};
} else {
throw new IllegalStateException("Unknown uniform type " + uniform.getClass().getName());
}
}
private static int P(int size, int align) {
return size<<5|align;
}
private static int getSizeAndAlignment(UniformType type) {
return switch (type) {
case INT, FLOAT -> P(1,1);//Size, Alignment
case MAT3 -> P(4+4+3,4);//is funky as each row is a vec3 padded to a vec4
case MAT4 -> P(4*4,4);
case VEC2, VEC2I -> P(2,2);
case VEC3, VEC3I -> P(3,4);
case VEC4, VEC4I -> P(4,4);
};
}
private static int getUniformOrdering(UniformType type) {
return switch (type) {
case MAT4, VEC4, VEC4I -> 0;
case VEC2, VEC2I -> 1;
case VEC3, VEC3I, MAT3 -> 2;
case INT, FLOAT -> 3;
};
}
private static CachedUniform[] createUniformSet(CustomUniforms cu, IrisShaderPatch patch) {
//This is a fking awful hack... but it works thinks
LocationalUniformHolder uniformBuilder = new LocationalUniformHolder() {
@Override
public LocationalUniformHolder addUniform(UniformUpdateFrequency uniformUpdateFrequency, Uniform uniform) {
return this;
}
@Override
public OptionalInt location(String uniformName, UniformType uniformType) {
//Yes am aware how performant inefficent this is... just dont care tbh since is on setup and is small
var names = patch.getUniformList();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(uniformName)) {
return OptionalInt.of(i);//Have a base uniform offset of 10
}
}
return OptionalInt.empty();
}
@Override
public UniformHolder externallyManagedUniform(String s, UniformType uniformType) {
return null;
}
};
cu.assignTo(uniformBuilder);
cu.mapholderToPass(uniformBuilder, patch);
CachedUniform[] uniforms = new CachedUniform[patch.getUniformList().length];
((CustomUniformsAccessor)cu).getLocationMap().get(patch).object2IntEntrySet().forEach(entry->uniforms[entry.getIntValue()] = entry.getKey());
//In _theory_ this should work?
return uniforms;
}
private static void createImageSet(IrisRenderingPipeline ipipe, IrisShaderPatch patch) {
Set<String> samplerNameSet = new HashSet<>(List.of(patch.getSamplerList()));
SamplerHolder samplerBuilder = new SamplerHolder() {
@Override
public boolean hasSampler(String s) {
return samplerNameSet.contains(s);
}
public boolean hasSampler(String... names) {
for (var name : names) {
if (samplerNameSet.contains(name)) return true;
}
return false;
}
@Override
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
Logger.error("Unsupported default sampler");
return false;
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) {
return this.addDynamicSampler(type, texture, null, sampler, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
if (!this.hasSampler(names)) return false;
Logger.info(Arrays.toString(names));
return false;
}
@Override
public void addExternalSampler(int texture, String... names) {
if (!this.hasSampler(names)) return;
Logger.info(Arrays.toString(names));
}
};
//Unsupported
ImageHolder imageBuilder = new ImageHolder() {
@Override
public boolean hasImage(String s) {
return false;
}
@Override
public void addTextureImage(IntSupplier intSupplier, InternalTextureFormat internalTextureFormat, String s) {
}
};
ipipe.addGbufferOrShadowSamplers(samplerBuilder, imageBuilder, ipipe::getFlippedAfterPrepare, false, true, true, false);
}
}

View File

@@ -0,0 +1,45 @@
package me.cortex.voxy.client.iris;
import net.irisshaders.iris.gl.sampler.SamplerHolder;
import net.irisshaders.iris.gl.texture.TextureType;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
public class VoxySamplers {
public static void addSamplers(IrisRenderingPipeline pipeline, SamplerHolder samplers) {
var patchData = ((IGetVoxyPatchData)pipeline).voxy$getPatchData();
if (patchData != null) {
//TODO replace ()->0 with the actual depth texture id
samplers.addDynamicSampler(TextureType.TEXTURE_2D, () -> {
var pipeData = ((IGetIrisVoxyPipelineData)pipeline).voxy$getPipelineData();
if (pipeData == null) {
return 0;
}
if (pipeData.thePipeline == null) {
return 0;
}
//In theory the first frame could be null
var dt = pipeData.thePipeline.fb.getDepthTex();
if (dt == null) {
return 0;
}
return dt.id;
}, null, "vxDepthTexOpaque");
samplers.addDynamicSampler(TextureType.TEXTURE_2D, () -> {
var pipeData = ((IGetIrisVoxyPipelineData)pipeline).voxy$getPipelineData();
if (pipeData == null) {
return 0;
}
if (pipeData.thePipeline == null) {
return 0;
}
//In theory the first frame could be null
var dt = pipeData.thePipeline.fbTranslucent.getDepthTex();
if (dt == null) {
return 0;
}
return dt.id;
}, null, "vxDepthTexTrans");
}
}
}

View File

@@ -0,0 +1,83 @@
package me.cortex.voxy.client.iris;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.irisshaders.iris.gl.uniform.UniformHolder;
import net.minecraft.client.MinecraftClient;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.util.function.Supplier;
import static net.irisshaders.iris.gl.uniform.UniformUpdateFrequency.PER_FRAME;
public class VoxyUniforms {
public static Matrix4f getViewProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
var getVrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
return new Matrix4f();
}
var vrs = getVrs.getVoxyRenderSystem();
return new Matrix4f(vrs.getViewport().MVP);
}
public static Matrix4f getProjection() {//This is 1 frame late ;-; cries, since the update occurs _before_ the voxy render pipeline
var getVrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
if (getVrs == null || getVrs.getVoxyRenderSystem() == null) {
return new Matrix4f();
}
var vrs = getVrs.getVoxyRenderSystem();
var mat = vrs.getViewport().projection;
if (mat == null) {
return new Matrix4f();
}
return new Matrix4f(mat);
}
public static void addUniforms(UniformHolder uniforms) {
uniforms
.uniform1i(PER_FRAME, "vxRenderDistance", ()-> VoxyConfig.loadOrCreate().sectionRenderDistance*32)//In chunks
.uniformMatrix(PER_FRAME, "vxViewProj", VoxyUniforms::getViewProjection)
.uniformMatrix(PER_FRAME, "vxViewProjInv", new Inverted(VoxyUniforms::getViewProjection))
.uniformMatrix(PER_FRAME, "vxViewProjPrev", new PreviousMat(VoxyUniforms::getViewProjection))
.uniformMatrix(PER_FRAME, "vxProj", VoxyUniforms::getProjection)
.uniformMatrix(PER_FRAME, "vxProjInv", new Inverted(VoxyUniforms::getProjection))
.uniformMatrix(PER_FRAME, "vxProjPrev", new PreviousMat(VoxyUniforms::getProjection));
}
private record Inverted(Supplier<Matrix4fc> parent) implements Supplier<Matrix4fc> {
private Inverted(Supplier<Matrix4fc> parent) {
this.parent = parent;
}
public Matrix4fc get() {
Matrix4f copy = new Matrix4f(this.parent.get());
copy.invert();
return copy;
}
public Supplier<Matrix4fc> parent() {
return this.parent;
}
}
private static class PreviousMat implements Supplier<Matrix4fc> {
private final Supplier<Matrix4fc> parent;
private Matrix4f previous;
PreviousMat(Supplier<Matrix4fc> parent) {
this.parent = parent;
this.previous = new Matrix4f();
}
public Matrix4fc get() {
Matrix4f previous = this.previous;
this.previous = new Matrix4f(this.parent.get());
return previous;
}
}
}

View File

@@ -0,0 +1,14 @@
package me.cortex.voxy.client.mixin.iris;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import net.irisshaders.iris.uniforms.custom.CustomUniforms;
import net.irisshaders.iris.uniforms.custom.cached.CachedUniform;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Map;
@Mixin(value = CustomUniforms.class, remap = false)
public interface CustomUniformsAccessor {
@Accessor Map<Object, Object2IntMap<CachedUniform>> getLocationMap();
}

View File

@@ -0,0 +1,12 @@
package me.cortex.voxy.client.mixin.iris;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
import net.irisshaders.iris.targets.RenderTargets;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(value = IrisRenderingPipeline.class, remap = false)
public interface IrisRenderingPipelineAccessor {
@Accessor
RenderTargets getRenderTargets();
}

View File

@@ -0,0 +1,46 @@
package me.cortex.voxy.client.mixin.iris;
import me.cortex.voxy.client.iris.IGetIrisVoxyPipelineData;
import me.cortex.voxy.client.iris.IGetVoxyPatchData;
import me.cortex.voxy.client.iris.IrisShaderPatch;
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
import net.irisshaders.iris.shaderpack.programs.ProgramSet;
import net.irisshaders.iris.uniforms.custom.CustomUniforms;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = IrisRenderingPipeline.class, remap = false)
public class MixinIrisRenderingPipeline implements IGetVoxyPatchData, IGetIrisVoxyPipelineData {
@Shadow @Final private CustomUniforms customUniforms;
@Unique IrisShaderPatch patchData;
@Unique
IrisVoxyRenderPipelineData pipeline;
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/irisshaders/iris/pipeline/transform/ShaderPrinter;resetPrintState()V", shift = At.Shift.AFTER))
private void voxy$injectPatchDataStore(ProgramSet programSet, CallbackInfo ci) {
this.patchData = ((IGetVoxyPatchData)programSet).voxy$getPatchData();
}
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/irisshaders/iris/pipeline/IrisRenderingPipeline;createSetupComputes([Lnet/irisshaders/iris/shaderpack/programs/ComputeSource;Lnet/irisshaders/iris/shaderpack/programs/ProgramSet;Lnet/irisshaders/iris/shaderpack/texture/TextureStage;)[Lnet/irisshaders/iris/gl/program/ComputeProgram;"))
private void voxy$injectPipeline(ProgramSet programSet, CallbackInfo ci) {
if (this.patchData != null) {
this.pipeline = IrisVoxyRenderPipelineData.buildPipeline((IrisRenderingPipeline)(Object)this, this.patchData, this.customUniforms);
}
}
@Override
public IrisShaderPatch voxy$getPatchData() {
return this.patchData;
}
@Override
public IrisVoxyRenderPipelineData voxy$getPipelineData() {
return this.pipeline;
}
}

View File

@@ -0,0 +1,25 @@
package me.cortex.voxy.client.mixin.iris;
import com.google.common.collect.ImmutableSet;
import me.cortex.voxy.client.iris.VoxySamplers;
import net.irisshaders.iris.gl.sampler.SamplerHolder;
import net.irisshaders.iris.pipeline.IrisRenderingPipeline;
import net.irisshaders.iris.pipeline.WorldRenderingPipeline;
import net.irisshaders.iris.samplers.IrisSamplers;
import net.irisshaders.iris.targets.RenderTargets;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Supplier;
@Mixin(value = IrisSamplers.class, remap = false)
public class MixinIrisSamplers {
@Inject(method = "addRenderTargetSamplers", at = @At("TAIL"))
private static void voxy$injectSamplers(SamplerHolder samplers, Supplier<ImmutableSet<Integer>> flipped, RenderTargets renderTargets, boolean isFullscreenPass, WorldRenderingPipeline pipeline, CallbackInfo ci) {
if (pipeline instanceof IrisRenderingPipeline ipipe) {
VoxySamplers.addSamplers(ipipe, samplers);
}
}
}

View File

@@ -0,0 +1,20 @@
package me.cortex.voxy.client.mixin.iris;
import me.cortex.voxy.client.iris.VoxyUniforms;
import net.irisshaders.iris.gl.uniform.UniformHolder;
import net.irisshaders.iris.shaderpack.IdMap;
import net.irisshaders.iris.shaderpack.properties.PackDirectives;
import net.irisshaders.iris.uniforms.CommonUniforms;
import net.irisshaders.iris.uniforms.FrameUpdateNotifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = CommonUniforms.class, remap = false)
public class MixinMatrixUniforms {
@Inject(method = "addNonDynamicUniforms", at = @At("TAIL"))
private static void voxy$InjectMatrixUniforms(UniformHolder uniforms, IdMap idMap, PackDirectives directives, FrameUpdateNotifier updateNotifier, CallbackInfo ci) {
VoxyUniforms.addUniforms(uniforms);
}
}

View File

@@ -0,0 +1,50 @@
package me.cortex.voxy.client.mixin.iris;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.iris.IGetVoxyPatchData;
import me.cortex.voxy.client.iris.IrisShaderPatch;
import net.irisshaders.iris.shaderpack.ShaderPack;
import net.irisshaders.iris.shaderpack.include.AbsolutePackPath;
import net.irisshaders.iris.shaderpack.programs.ProgramSet;
import net.irisshaders.iris.shaderpack.properties.PackDirectives;
import net.irisshaders.iris.shaderpack.properties.ShaderProperties;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Function;
@Mixin(value = ProgramSet.class, remap = false)
public class MixinProgramSet implements IGetVoxyPatchData {
@Shadow @Final private PackDirectives packDirectives;
@Unique IrisShaderPatch patchData;
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/irisshaders/iris/shaderpack/programs/ProgramSet;locateDirectives()V", shift = At.Shift.BEFORE))
private void voxy$injectPatchMaker(AbsolutePackPath directory, Function<AbsolutePackPath, String> sourceProvider, ShaderProperties shaderProperties, ShaderPack pack, CallbackInfo ci) {
if (VoxyConfig.CONFIG.isRenderingEnabled()) {
this.patchData = IrisShaderPatch.makePatch(pack, directory, sourceProvider);
}
/*
if (this.patchData != null) {
//Inject directives from voxy
DispatchingDirectiveHolder ddh = new DispatchingDirectiveHolder();
this.packDirectives.acceptDirectivesFrom(ddh);
CommentDirectiveParser.findDirective(this.patchData.getPatchSource(), CommentDirective.Type.RENDERTARGETS)
.map(dir->Arrays.stream(dir.getDirective().split(","))
.mapToInt(Integer::parseInt).toArray())
.ifPresent(ddh::processDirective);
}
*/
}
@Override
public IrisShaderPatch voxy$getPatchData() {
return this.patchData;
}
}

View File

@@ -0,0 +1,18 @@
package me.cortex.voxy.client.mixin.iris;
import com.google.common.collect.ImmutableList;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.irisshaders.iris.shaderpack.include.ShaderPackSourceNames;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(value = ShaderPackSourceNames.class, remap = false)
public class MixinShaderPackSourceNames {
@WrapOperation(method = "findPotentialStarts", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList;builder()Lcom/google/common/collect/ImmutableList$Builder;"))
private static ImmutableList.Builder<String> voxy$injectVoxyShaderPatch(Operation<ImmutableList.Builder<String>> original){
var builder = original.call();
builder.add("voxy.json");
return builder;
}
}

View File

@@ -0,0 +1,28 @@
package me.cortex.voxy.client.mixin.iris;
import com.google.common.collect.ImmutableList;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import me.cortex.voxy.client.config.VoxyConfig;
import net.irisshaders.iris.gl.shader.StandardMacros;
import net.irisshaders.iris.helpers.StringPair;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import java.util.Collection;
import java.util.List;
@Mixin(value = StandardMacros.class, remap = false)
public abstract class MixinStandardMacros {
@Shadow
private static void define(List<StringPair> defines, String key){}
@WrapOperation(method = "createStandardEnvironmentDefines", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList;copyOf(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;"))
private static ImmutableList<StringPair> voxy$injectVoxyDefine(Collection<StringPair> list, Operation<ImmutableList<StringPair>> original) {
if (VoxyConfig.CONFIG.isRenderingEnabled()) {
define((List<StringPair>) list, "VOXY");
}
return ImmutableList.copyOf(list);
}
}

View File

@@ -0,0 +1,366 @@
package me.cortex.voxy.common.world;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
import java.util.function.Supplier;
public final class LoadedPositionTracker {
private final Supplier<Object> factory;
private static final Object SENTINEL_LOCK = new Object();
public LoadedPositionTracker(Supplier<Object> factory) {
this.factory = factory;
this.setSize(12);
}
private void setSize(int bits) {
this.mask = (1<<bits)-1;
this.key = new long[1<<bits];
this.value = new Object[1<<bits];
this.zeroObj = null;
}
public Object getSecOrMakeLoader(long loc) {
loc = mix(loc);
//TODO: upsize if needed
if (loc == 0) {//Special case, lock free
var var = this.zeroObj;
if (var == null) {
var replace = this.factory.get();
do {
var = ZERO_OBJ_HANDLE.compareAndExchange(this, null, replace);
} while (var == SENTINEL_LOCK);
if (var == null) {
var = replace;
}
}
return var;
} else {
this.aReadLock();
int pos = this.findAcquire(loc);
if (pos < 0) {
pos = -pos-1;
//No entry found but we have acquired a write location
// this should be _unique_ to us as in we are the only thread with it
// which means we also dont need any atomic operations on it
if (this.value[pos] == null) {
throw new IllegalStateException();
}
Object val = this.value[pos] = this.factory.get();
VarHandle.releaseFence();
this.rReadLock();
return val;
} else {
Object val;
while ((val=this.value[pos])==null || val == SENTINEL_LOCK) {
Thread.onSpinWait();//Wait to acquire
VarHandle.acquireFence();
}
this.rReadLock();//Cant move before the release acquire sadly as cant have it do a shuffle
return val;
}
}
}
//Does an atomic exchange with the value at the given location
public Object exchange(long loc, Object with) {
if (with == null) throw new IllegalArgumentException("with cannot be null");
loc = mix(loc);
if (loc == 0) {//Special case
Object val;
while (true) {
val = ZERO_OBJ_HANDLE.get(this);
if (val == SENTINEL_LOCK) continue;
if (ZERO_OBJ_HANDLE.compareAndSet(this, val, with)) break;
}
if (val == null) throw new IllegalStateException();
return val;
} else {
this.aReadLock();
int pos = this.find(loc);
this.rReadLock();
if (pos < 0) {
throw new IllegalStateException("Position not found");
} else {
var val = this.value[pos];
this.value[pos] = with;
this.rReadLock();
if (val == null) {
throw new IllegalStateException("Value was null");
}
return val;
}
}
}
public Object removeIfCondition(long loc, Predicate<Object> test) {
loc = mix(loc);
if (loc == 0) {//Special case
var val = this.value;
if (test.test(val)) {
return ZERO_OBJ_HANDLE.compareAndExchange(this, val, null);
}
return null;
} else {
this.aReadLock();//Write lock as we need to ensure correctness
int pos = this.find(loc);
this.rReadLock();
if (pos < 0) {
return null;//did not remove it as it does not exist
} else {
this.aWriteLock();
var val = this.value[pos];
if (test.test(val)) {
//Remove pos
this.value[pos] = null;
this.shiftKeys(pos);
this.rWriteLock();
return val;
} else {
//Test failed, dont remove
this.rWriteLock();
return null;
}
}
}
}
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private void aReadLock() {//Acquire read lock, blocking
}
private void rReadLock() {//Release read lock
}
private void aWriteLock() {//Acquire write lock, blocking
}
private void rWriteLock() {//Release write lock
}
//Faster impl of Long2ObjectOpenHashMap that allows for custom atomic operations on the value fields
private transient long[] key;
private transient Object[] value;
private transient int mask;
private transient Object zeroObj;
//k is the already hashed and mixed entry
private int find(long k) {
final long[] key = this.key;
final int msk = this.mask;
long curr;
int pos;
// The starting point.
if ((curr = key[pos = (int)k&msk]) == 0) return -(pos + 1);
if (k == curr) return pos;
// There's always an unused entry.
while (true) {
if ((curr = key[pos = (pos + 1) & msk]) == 0) return -(pos + 1);
if (k == curr) return pos;
}
}
private int findAcquire(long k) {//Finds key or atomically acquires the position of where to insert one
final long[] key = this.key;
final int msk = this.mask;
int pos = (int)k&msk;
long curr;
// The starting point.
if ((curr = key[pos]) == 0) {
//Try to atomically acquire the position
curr = (long) LONG_ARR_HANDLE.compareAndExchange(key, pos, 0, k);//The witness becomes the curr
if (curr == 0) {//We got the lock
return -(pos+1);
}
}
if (k == curr) return pos;
// There's always an unused entry.
while (true) {
if ((curr = key[pos = (pos + 1) & msk]) == 0) {
//Try to atomically acquire the position
curr = (long) LONG_ARR_HANDLE.compareAndExchange(key, pos, 0, k);
if (curr == 0) {//We got the lock
return -(pos+1);
}
}
if (k == curr) return pos;
}
}
private void shiftKeys(int pos) {
// Shift entries with the same hash.
int last;
long curr;
final long[] key = this.key;
final Object[] value = this.value;
final int msk = this.mask;
while (true) {
pos = ((last = pos) + 1) & msk;
while (true) {
if ((curr = key[pos]) == 0) {
key[last] = 0;
value[last] = null;
return;
}
int slot = (int)curr & msk;
//TODO: optimize all this to make this only 1 branch-less loop
if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) break;
pos = (pos + 1) & msk;
}
key[last] = curr;
value[last] = value[pos];
}
}
private static final long LONG_PHI = 0x9E3779B97F4A7C15L;
/*
public static long mix(long seed) {
seed ^= LONG_PHI;
seed = (seed ^ seed >>> 30) * -4658895280553007687L;
seed = (seed ^ seed >>> 27) * -7723592293110705685L;
return seed ^ seed >>> 31;
}*/
/*
public static long mix(final long x) {
long h = x ^ LONG_PHI;
h *= LONG_PHI;
h ^= h >>> 32;
return h ^ (h >>> 16);
}*/
public static long mix(final long x) {
long h = x * LONG_PHI;
h ^= h >>> 32;
return h ^ (h >>> 16);
}
//Utils
private static final VarHandle LONG_ARR_HANDLE = MethodHandles.arrayElementVarHandle(long[].class);
private static final VarHandle ZERO_OBJ_HANDLE;
static {
try {
ZERO_OBJ_HANDLE = MethodHandles.lookup().findVarHandle(LoadedPositionTracker.class, "zeroObj", Object.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
//Test
public static void main(String[] args) throws InterruptedException, IOException {
var map = new LoadedPositionTracker(AtomicInteger::new);
var t = new Thread[20];
Long2ObjectOpenHashMap<Object> a = new Long2ObjectOpenHashMap<>(3000);
ReentrantLock l = new ReentrantLock();
for (int j = 0; j<t.length; j++) {
int finalJ = j;
t[j] = new Thread(()->{
var r = new Random((finalJ +1)*1482791);
for (int i = 0; i < 20024000; i++) {
long p = r.nextInt(3000)*5981754211L;
if (false) {
l.lock();
var val = a.computeIfAbsent(p, lll->new AtomicInteger());
l.unlock();
if (val instanceof AtomicInteger ai) {
if (ai.incrementAndGet() > 2) {
l.lock();
a.put(p, new AtomicInteger[]{ai});
l.unlock();
}
} else if (val instanceof AtomicInteger[] aia) {
var ai = aia[0];
ai.incrementAndGet();
}
}
if (true) {
Object entry = map.getSecOrMakeLoader(p);
if (entry instanceof AtomicInteger ai) {
if (ai.incrementAndGet() > 2) {
map.exchange(p, new AtomicInteger[]{ai});
}
} else if (entry instanceof AtomicInteger[] aia) {
var ai = aia[0];
ai.incrementAndGet();
} else {
throw new IllegalStateException();
}
if (false&&r.nextBoolean()) {
map.removeIfCondition(p, obj -> {
if (obj instanceof AtomicInteger ai) {
if (ai.get() > 100) {
return true;
}
return false;
} else if (obj instanceof AtomicInteger[] aia) {
var ai = aia[0];
if (ai.get() > 200) {
return true;
}
return false;
} else {
throw new IllegalStateException();
}
});
}
}
}
});
t[j].start();
}
long start = System.currentTimeMillis();
for (var tt : t) {
tt.join();
}
System.err.println(System.currentTimeMillis()-start);
for (var entries : a.long2ObjectEntrySet()) {
var val = map.getSecOrMakeLoader(entries.getLongKey());
int iv = 0;
if (val instanceof AtomicInteger ai) {
iv = ai.get();
} else {
iv = ((AtomicInteger[])val)[0].get();
}
val = entries.getValue();
int iv2 = 0;
if (val instanceof AtomicInteger ai) {
iv2 = ai.get();
} else {
iv2 = ((AtomicInteger[])val)[0].get();
}
if (iv != iv2) {
throw new IllegalStateException();
}
}
}
}