Iris add
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.cortex.voxy.client.iris;
|
||||
|
||||
public interface IGetIrisVoxyPipelineData {
|
||||
IrisVoxyRenderPipelineData voxy$getPipelineData();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package me.cortex.voxy.client.iris;
|
||||
|
||||
public interface IGetVoxyPatchData {
|
||||
IrisShaderPatch voxy$getPatchData();
|
||||
}
|
||||
100
src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java
Normal file
100
src/main/java/me/cortex/voxy/client/iris/IrisShaderPatch.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
45
src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java
Normal file
45
src/main/java/me/cortex/voxy/client/iris/VoxySamplers.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java
Normal file
83
src/main/java/me/cortex/voxy/client/iris/VoxyUniforms.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user