From 707c0b1c85a07624761345cf319f97e00148b1c8 Mon Sep 17 00:00:00 2001 From: mcrcortex <{ID}+{username}@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:34:59 +1000 Subject: [PATCH] Created PrintfInjector to use printf inside shaders --- .../client/core/gl/shader/PrintfInjector.java | 220 ++++++++++++++++++ .../Gl46MeshletsFarWorldRenderer.java | 1 + .../rendering/hierarchical/NodeManager2.java | 4 +- 3 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 src/main/java/me/cortex/voxy/client/core/gl/shader/PrintfInjector.java diff --git a/src/main/java/me/cortex/voxy/client/core/gl/shader/PrintfInjector.java b/src/main/java/me/cortex/voxy/client/core/gl/shader/PrintfInjector.java new file mode 100644 index 00000000..a6d9ec30 --- /dev/null +++ b/src/main/java/me/cortex/voxy/client/core/gl/shader/PrintfInjector.java @@ -0,0 +1,220 @@ +package me.cortex.voxy.client.core.gl.shader; + +import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.rendering.util.DownloadStream; +import org.lwjgl.system.MemoryUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; + +import static org.lwjgl.opengl.ARBDirectStateAccess.nglClearNamedBufferData; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL30.GL_R32UI; +import static org.lwjgl.opengl.GL30.glBindBufferBase; +import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER; +import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER; +import static org.lwjgl.opengl.GL45.nglClearNamedBufferSubData; + +public class PrintfInjector { + private final GlBuffer textBuffer; + private final HashMap printfStringMap = new HashMap<>(); + private final HashMap idToPrintfStringMap = new HashMap<>(); + private final int bindingIndex; + private final Consumer callback; + public PrintfInjector(int bufferSize, int bufferBindingIndex, Consumer callback) { + this.textBuffer = new GlBuffer(bufferSize*4L+4); + nglClearNamedBufferData(this.textBuffer.id, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); + this.bindingIndex = bufferBindingIndex; + this.callback = callback; + } + + private static int findNextCall(String src, int after) { + while (true) { + int idx = src.indexOf("printf", after); + if (idx == -1) { + return -1; + } + + boolean lineComment = false; + boolean multiLineComment = false; + //Check for comments + for (int i = 0; i < idx; i++) { + if (src.charAt(i) == '/' && src.charAt(i + 1) == '/') { + lineComment = true; + } + if (src.charAt(i) == '\n') { + lineComment = false; + } + if ((!lineComment) && src.charAt(i) == '/' && src.charAt(i + 1) == '*') { + multiLineComment = true; + } + if ((!lineComment) && src.charAt(i) == '*' && src.charAt(i + 1) == '/') { + multiLineComment = false; + } + } + if (lineComment || multiLineComment) { + after = idx+1; + continue; + } + return idx; + } + } + + private static void parsePrintfTypes(String fmtStr, List types) { + for (int i = 0; i < fmtStr.length()-1; i++) { + if (fmtStr.charAt(i)=='%' && (i==0||fmtStr.charAt(i-1)!='%')) { + types.add(fmtStr.charAt(i+1)); + } + } + } + + public String transformInject(String src) { + String original = src; + //Quick exit + if (!src.contains("printf")) { + return src; + } + + int pos = 0; + StringBuilder result = new StringBuilder(); + List argVals = new ArrayList<>(); + List types = new ArrayList<>(); + + { + int bufferInjection = Math.max(src.lastIndexOf("#version"), src.lastIndexOf("#extension")); + bufferInjection = src.indexOf("\n", bufferInjection); + + result.append(src, 0, bufferInjection+1); + + result.append(String.format(""" + layout(binding = %s, std430) restrict buffer PrintfOutputStream { + uint index; + uint stream[]; + } printfOutputStruct; + """, this.bindingIndex)); + + src = src.substring(bufferInjection+1); + } + boolean usedPrintf = false; + while (true) { + int nextCall = findNextCall(src, pos); + if (nextCall == -1) { + result.append(src, pos, src.length()); + break; + } + result.append(src, pos, nextCall); + + + //Parse the printf() call + String sub = src.substring(nextCall); + sub = sub.substring(sub.indexOf('"')+1); + sub = sub.substring(0, sub.indexOf(';')); + String fmtStr = sub.substring(0, sub.indexOf('"')); + String args = sub.substring(sub.indexOf('"')); + + //Parse the commas in the args + int prev = 0; + int brace = 0; + argVals.clear(); + for (int i = 0; i < args.length(); i++) { + if (args.charAt(i) == '(' || args.charAt(i) == '[') brace++; + if (args.charAt(i) == ')' || args.charAt(i) == ']') brace--; + if ((args.charAt(i) == ',' && brace==0) || brace==-1) { + if (prev == 0) { + prev = i; + continue; + } + String arg = args.substring(prev+1, i); + prev = i; + argVals.add(arg); + + if (brace==-1) { + break; + } + } + } + + //Parse the format string + types.clear(); + parsePrintfTypes(fmtStr, types); + + if (types.size() != argVals.size()) { + throw new IllegalStateException("Printf obj count dont match arg size"); + } + + + + //Inject the printf code + StringBuilder subCode = new StringBuilder(); + subCode.append(String.format("{" + + "uint printfWriteIndex = atomicAdd(printfOutputStruct.index,%s);" + + "printfOutputStruct.stream[printfWriteIndex]=%s;", types.size()+1, + this.printfStringMap.computeIfAbsent(fmtStr, a->{int id = this.printfStringMap.size(); + this.idToPrintfStringMap.put(id, a); + return id;}))); + + for (int i = 0; i < types.size(); i++) { + subCode.append("printfOutputStruct.stream[printfWriteIndex+").append(i+1).append("]="); + if (types.get(i) == 'd' || types.get(i) == 'i' || types.get(i) == 'u') { + subCode.append("uint(").append(argVals.get(i)).append(")"); + } + if (types.get(i) == 'f') { + subCode.append("floatBitsToUint(").append(argVals.get(i)).append(")"); + } + subCode.append(";"); + } + subCode.append("}"); + result.append(subCode); + usedPrintf = true; + pos = src.indexOf(';', nextCall)+1; + } + if (!usedPrintf) { + return original; + } + return result.toString(); + } + + public void bind() { + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, this.bindingIndex, this.textBuffer.id); + } + + private void processResult(long ptr, long size) { + int total = MemoryUtil.memGetInt(ptr); ptr += 4; + int cnt = 0; + List types = new ArrayList<>(); + while (cnt < total) { + int id = MemoryUtil.memGetInt(ptr); + ptr += 4; + cnt++; + String fmt = this.idToPrintfStringMap.get(id); + types.clear(); + parsePrintfTypes(fmt, types); + Object[] args = new Object[types.size()]; + for (int i = 0; i < types.size(); i++) { + if (types.get(i) == 'd' || types.get(i) == 'i' || types.get(i) == 'u') { + args[i] = MemoryUtil.memGetInt(ptr); + ptr += 4; + cnt++; + } + if (types.get(i) == 'f') { + args[i] = Float.intBitsToFloat(MemoryUtil.memGetInt(ptr)); + ptr += 4; + cnt++; + } + } + this.callback.accept(String.format(fmt, args)); + } + } + + public void download() { + DownloadStream.INSTANCE.download(this.textBuffer, 0, this.textBuffer.size(), this::processResult); + DownloadStream.INSTANCE.commit(); + nglClearNamedBufferSubData(this.textBuffer.id, GL_R32UI, 0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, 0); + } + + public void free() { + this.textBuffer.free(); + } +} diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/Gl46MeshletsFarWorldRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/Gl46MeshletsFarWorldRenderer.java index d491a2d4..a1b76ca0 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/Gl46MeshletsFarWorldRenderer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/Gl46MeshletsFarWorldRenderer.java @@ -1,6 +1,7 @@ package me.cortex.voxy.client.core.rendering; import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.gl.shader.PrintfInjector; import me.cortex.voxy.client.core.gl.shader.Shader; import me.cortex.voxy.client.core.gl.shader.ShaderType; import me.cortex.voxy.client.core.rendering.building.RenderDataFactory; diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java index ae3cadaa..d1dc0b3a 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierarchical/NodeManager2.java @@ -344,14 +344,14 @@ public class NodeManager2 { } private void writeNode(long dst, int id) { - + } //2 parts upload and download private void download() { //Download the request queue then clear the counter (first 4 bytes) - DownloadStream.INSTANCE.download(this.); + //DownloadStream.INSTANCE.download(this.); }