diff --git a/src/main/java/me/cortex/voxy/client/core/IrisVoxyRenderPipeline.java b/src/main/java/me/cortex/voxy/client/core/IrisVoxyRenderPipeline.java new file mode 100644 index 00000000..cf252e67 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/IrisVoxyRenderPipeline.java @@ -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 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(); + } +} diff --git a/src/main/java/me/cortex/voxy/client/core/RenderPipelineFactory.java b/src/main/java/me/cortex/voxy/client/core/RenderPipelineFactory.java new file mode 100644 index 00000000..ba3ac1f5 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/RenderPipelineFactory.java @@ -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; + } +} diff --git a/src/main/java/me/cortex/voxy/client/iris/IGetIrisVoxyPipelineData.java b/src/main/java/me/cortex/voxy/client/iris/IGetIrisVoxyPipelineData.java new file mode 100644 index 00000000..c5417756 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/iris/IGetIrisVoxyPipelineData.java @@ -0,0 +1,5 @@ +package me.cortex.voxy.client.iris; + +public interface IGetIrisVoxyPipelineData { + IrisVoxyRenderPipelineData voxy$getPipelineData(); +} diff --git a/src/main/java/me/cortex/voxy/client/iris/IGetVoxyPatchData.java b/src/main/java/me/cortex/voxy/client/iris/IGetVoxyPatchData.java new file mode 100644 index 00000000..a931b9dd --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/iris/IGetVoxyPatchData.java @@ -0,0 +1,5 @@ +package me.cortex.voxy.client.iris; + +public interface IGetVoxyPatchData { + IrisShaderPatch voxy$getPatchData(); +} diff --git a/src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java b/src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java new file mode 100644 index 00000000..6641a3bb --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java @@ -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 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); + } +} diff --git a/src/main/java/me/cortex/voxy/client/iris/IrisVoxyRenderPipelineData.java b/src/main/java/me/cortex/voxy/client/iris/IrisVoxyRenderPipelineData.java new file mode 100644 index 00000000..7eac80b3 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/iris/IrisVoxyRenderPipelineData.java @@ -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 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[] 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 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 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); + } + +} diff --git a/src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java b/src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java new file mode 100644 index 00000000..cb2227ec --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java @@ -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"); + } + } +} diff --git a/src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java b/src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java new file mode 100644 index 00000000..ef719e47 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java @@ -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 parent) implements Supplier { + private Inverted(Supplier parent) { + this.parent = parent; + } + + public Matrix4fc get() { + Matrix4f copy = new Matrix4f(this.parent.get()); + copy.invert(); + return copy; + } + + public Supplier parent() { + return this.parent; + } + } + + private static class PreviousMat implements Supplier { + private final Supplier parent; + private Matrix4f previous; + + PreviousMat(Supplier parent) { + this.parent = parent; + this.previous = new Matrix4f(); + } + + public Matrix4fc get() { + Matrix4f previous = this.previous; + this.previous = new Matrix4f(this.parent.get()); + return previous; + } + } +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/CustomUniformsAccessor.java b/src/main/java/me/cortex/voxy/client/mixin/iris/CustomUniformsAccessor.java new file mode 100644 index 00000000..6db3580f --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/CustomUniformsAccessor.java @@ -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> getLocationMap(); +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/IrisRenderingPipelineAccessor.java b/src/main/java/me/cortex/voxy/client/mixin/iris/IrisRenderingPipelineAccessor.java new file mode 100644 index 00000000..bbbb0e53 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/IrisRenderingPipelineAccessor.java @@ -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(); +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/MixinIrisRenderingPipeline.java b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinIrisRenderingPipeline.java new file mode 100644 index 00000000..88810012 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinIrisRenderingPipeline.java @@ -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 = "", 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 = "", 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; + } +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/MixinIrisSamplers.java b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinIrisSamplers.java new file mode 100644 index 00000000..f6780f2b --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinIrisSamplers.java @@ -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> flipped, RenderTargets renderTargets, boolean isFullscreenPass, WorldRenderingPipeline pipeline, CallbackInfo ci) { + if (pipeline instanceof IrisRenderingPipeline ipipe) { + VoxySamplers.addSamplers(ipipe, samplers); + } + } +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/MixinMatrixUniforms.java b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinMatrixUniforms.java new file mode 100644 index 00000000..c466153c --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinMatrixUniforms.java @@ -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); + } +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/MixinProgramSet.java b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinProgramSet.java new file mode 100644 index 00000000..9f034110 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinProgramSet.java @@ -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 = "", at = @At(value = "INVOKE", target = "Lnet/irisshaders/iris/shaderpack/programs/ProgramSet;locateDirectives()V", shift = At.Shift.BEFORE)) + private void voxy$injectPatchMaker(AbsolutePackPath directory, Function 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; + } +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/MixinShaderPackSourceNames.java b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinShaderPackSourceNames.java new file mode 100644 index 00000000..94ebd010 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinShaderPackSourceNames.java @@ -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 voxy$injectVoxyShaderPatch(Operation> original){ + var builder = original.call(); + builder.add("voxy.json"); + return builder; + } +} diff --git a/src/main/java/me/cortex/voxy/client/mixin/iris/MixinStandardMacros.java b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinStandardMacros.java new file mode 100644 index 00000000..994b6ac5 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/mixin/iris/MixinStandardMacros.java @@ -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 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 voxy$injectVoxyDefine(Collection list, Operation> original) { + if (VoxyConfig.CONFIG.isRenderingEnabled()) { + define((List) list, "VOXY"); + } + return ImmutableList.copyOf(list); + } +} diff --git a/src/main/java/me/cortex/voxy/common/world/LoadedPositionTracker.java b/src/main/java/me/cortex/voxy/common/world/LoadedPositionTracker.java new file mode 100644 index 00000000..ec65cc34 --- /dev/null +++ b/src/main/java/me/cortex/voxy/common/world/LoadedPositionTracker.java @@ -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 factory; + private static final Object SENTINEL_LOCK = new Object(); + + public LoadedPositionTracker(Supplier factory) { + this.factory = factory; + this.setSize(12); + } + + private void setSize(int bits) { + this.mask = (1< 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 a = new Long2ObjectOpenHashMap<>(3000); + ReentrantLock l = new ReentrantLock(); + for (int j = 0; j{ + 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(); + } + } + } +}