From fe72612a1516217590f487d0df91e4f7dc6b3481 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 10:12:52 +0100 Subject: [PATCH 01/50] Refactor of CommandBuffer to break it out into a more maintainable & debuggable layout --- .../hd/opengl/commandbuffer/BaseCommand.java | 124 +++++++++ .../opengl/commandbuffer/CommandBuffer.java | 213 ++++++++++++++++ .../commands/BindElementsArrayCommand.java | 32 +++ .../commands/BindVertexArrayCommand.java | 31 +++ .../commands/ColorMaskCommand.java | 47 ++++ .../commands/DepthMaskCommand.java | 38 +++ .../commands/DrawArraysCommand.java | 43 ++++ .../commands/DrawElementsCommand.java | 44 ++++ .../commands/MultiDrawArraysCommand.java | 80 ++++++ .../commandbuffer/commands/ToggleCommand.java | 46 ++++ .../commands/UpdateCMDUBOCommand.java | 59 +++++ src/main/java/rs117/hd/renderer/zone/VAO.java | 2 +- .../java/rs117/hd/renderer/zone/Zone.java | 2 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 7 +- .../java/rs117/hd/utils/CommandBuffer.java | 240 ------------------ 15 files changed, 763 insertions(+), 245 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java delete mode 100644 src/main/java/rs117/hd/utils/CommandBuffer.java diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java new file mode 100644 index 0000000000..9950244097 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -0,0 +1,124 @@ +package rs117.hd.opengl.commandbuffer; + +import lombok.Getter; +import rs117.hd.opengl.uniforms.UBOCommandBuffer; + +public abstract class BaseCommand { + protected int id; + @Getter + private final boolean isDrawCall; + + protected int bitHead; + protected int wordIndex; + protected CommandBuffer buffer; + + protected BaseCommand() { + this.isDrawCall = false; + } + + protected BaseCommand(boolean isDrawCall) { + this.isDrawCall = isDrawCall; + } + + protected UBOCommandBuffer getUBOCommandBuffer() { return buffer.uboCommandBuffer; } + + public abstract void execute(); + public abstract void print(StringBuilder sb); + + public final void write(CommandBuffer buffer) { + this.buffer = buffer; + + buffer.ensureCapacity(1); + buffer.cmd[buffer.writeHead++] = 0; + + this.wordIndex = buffer.writeHead - 1; + this.bitHead = 0; + + write8(id); + doWrite(); + + // Advance writeHead if needed + if (bitHead > 0) + wordIndex++; + + buffer.writeHead = wordIndex; + } + + protected abstract void doWrite(); + + public final void read(CommandBuffer buffer) { + this.buffer = buffer; + this.wordIndex = buffer.readHead; + this.bitHead = 0; + + read8(); // Skip over the command id + doRead(); + + if (bitHead > 0) + wordIndex++; + + buffer.readHead = wordIndex; + } + + protected abstract void doRead(); + + protected void writeBits(long value, int numBits) { + while (numBits > 0) { + buffer.ensureCapacity(wordIndex + 1); + int bitsLeftInWord = 64 - bitHead; + int bitsToWrite = Math.min(bitsLeftInWord, numBits); + + long mask = (1L << bitsToWrite) - 1; + long bits = value & mask; + + buffer.cmd[wordIndex] |= bits << bitHead; + + bitHead += bitsToWrite; + if (bitHead == 64) { + bitHead = 0; + wordIndex++; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } + } + + protected void write1(int value) { writeBits(value, 1); } + protected void write8(int value) { writeBits(value, 8); } + protected void write16(int value) { writeBits(value, 16); } + protected void write32(int value) { writeBits(value, 32); } + protected void write64(long value){ writeBits(value, 64); } + + protected long readBits(int numBits) { + long result = 0; + int shift = 0; + + while (numBits > 0) { + long word = buffer.cmd[wordIndex]; + int bitsLeftInWord = 64 - bitHead; + int bitsToRead = Math.min(bitsLeftInWord, numBits); + + long mask = (1L << bitsToRead) - 1; + long bits = (word >>> bitHead) & mask; + + result |= bits << shift; + + bitHead += bitsToRead; + if (bitHead == 64) { + bitHead = 0; + wordIndex++; + } + + shift += bitsToRead; + numBits -= bitsToRead; + } + return result; + } + + protected int read1() { return (int) readBits(1); } + protected int read8() { return (int) readBits(8); } + protected int read16() { return (int) readBits(16); } + protected int read32() { return (int) readBits(32); } + protected long read64() { return readBits(64); } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java new file mode 100644 index 0000000000..ec4a563a67 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -0,0 +1,213 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.Arrays; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; +import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; +import rs117.hd.opengl.commandbuffer.commands.UpdateCMDUBOCommand; +import rs117.hd.opengl.uniforms.UBOCommandBuffer; + +@Slf4j +public class CommandBuffer { + private static int COMMAND_COUNT = 0; + private static final BaseCommand[] REGISTERED_COMMANDS = { + (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), + (DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new)), + (MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new)), + (TOGGLE_COMMAND = REGISTER_COMMAND(ToggleCommand::new)), + (COLOR_MASK_COMMAND = REGISTER_COMMAND(ColorMaskCommand::new)), + (DEPTH_MASK_COMMAND = REGISTER_COMMAND(DepthMaskCommand::new)), + (BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new)), + (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), + (UPDATE_CMD_UBO_COMMAND = REGISTER_COMMAND(UpdateCMDUBOCommand::new)), + }; + + private static final DrawArraysCommand DRAW_ARRAYS_COMMAND; + private static final DrawElementsCommand DRAW_ELEMENTS_COMMAND; + private static final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND; + private static final ToggleCommand TOGGLE_COMMAND; + private static final ColorMaskCommand COLOR_MASK_COMMAND; + private static final DepthMaskCommand DEPTH_MASK_COMMAND; + private static final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; + private static final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; + private static final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; + + interface ICreateCommand { T construct(); } + + private static T REGISTER_COMMAND(ICreateCommand createCommand) { + T newCommand = createCommand.construct(); + newCommand.id = COMMAND_COUNT; + COMMAND_COUNT++; + return newCommand; + } + + @Setter + protected UBOCommandBuffer uboCommandBuffer; + + protected long[] cmd = new long[1 << 20]; // ~1 million calls + protected int writeHead = 0; + protected int readHead = 0; + + private final boolean validate = true; + + public void ensureCapacity(int numLongs) { + if (writeHead + numLongs >= cmd.length) + cmd = Arrays.copyOf(cmd, cmd.length * 2); + } + + public void SetBaseOffset(int x, int y, int z) { + UPDATE_CMD_UBO_COMMAND.isBaseOffset = true; + UPDATE_CMD_UBO_COMMAND.x = x; + UPDATE_CMD_UBO_COMMAND.y = y; + UPDATE_CMD_UBO_COMMAND.z = z; + UPDATE_CMD_UBO_COMMAND.write(this); + + if(validate) { + UPDATE_CMD_UBO_COMMAND.read(this); + assert UPDATE_CMD_UBO_COMMAND.isBaseOffset && + UPDATE_CMD_UBO_COMMAND.x == x && + UPDATE_CMD_UBO_COMMAND.y == y && + UPDATE_CMD_UBO_COMMAND.z == z; + } + } + + public void SetWorldViewIndex(int index) { + UPDATE_CMD_UBO_COMMAND.isBaseOffset = false; + UPDATE_CMD_UBO_COMMAND.worldViewId = index; + UPDATE_CMD_UBO_COMMAND.write(this); + + if(validate) { + UPDATE_CMD_UBO_COMMAND.read(this); + assert UPDATE_CMD_UBO_COMMAND.worldViewId == index; + } + } + + public void BindVertexArray(int vao) { + BIND_VERTEX_ARRAY_COMMAND.vao = vao; + BIND_VERTEX_ARRAY_COMMAND.write(this); + + if(validate) { + BIND_VERTEX_ARRAY_COMMAND.read(this); + assert BIND_VERTEX_ARRAY_COMMAND.vao == vao; + } + } + + public void BindElementsArray(int ebo) { + BIND_ELEMENTS_ARRAY_COMMAND.ebo = ebo; + BIND_ELEMENTS_ARRAY_COMMAND.write(this); + + if(validate) { + BIND_ELEMENTS_ARRAY_COMMAND.read(this); + assert BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo; + } + } + + public void DepthMask(boolean writeDepth) { + DEPTH_MASK_COMMAND.flag = writeDepth; + DEPTH_MASK_COMMAND.write(this); + + if(validate) { + DEPTH_MASK_COMMAND.read(this); + assert DEPTH_MASK_COMMAND.flag == writeDepth; + } + } + + public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { + COLOR_MASK_COMMAND.red = writeRed; + COLOR_MASK_COMMAND.green = writeGreen; + COLOR_MASK_COMMAND.blue = writeBlue; + COLOR_MASK_COMMAND.alpha = writeAlpha; + + if(validate) { + COLOR_MASK_COMMAND.read(this); + assert COLOR_MASK_COMMAND.red == writeRed && + COLOR_MASK_COMMAND.green == writeGreen && + COLOR_MASK_COMMAND.blue == writeBlue && + COLOR_MASK_COMMAND.alpha == writeAlpha; + } + } + + public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { + MULTI_DRAW_ARRAYS_COMMAND.mode = mode; + MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; + MULTI_DRAW_ARRAYS_COMMAND.counts = counts; + MULTI_DRAW_ARRAYS_COMMAND.write(this); + } + + public void DrawElements(int mode, int vertexCount, long offset) { + DRAW_ELEMENTS_COMMAND.mode = mode; + DRAW_ELEMENTS_COMMAND.vertexCount = vertexCount; + DRAW_ELEMENTS_COMMAND.offset = offset; + DRAW_ELEMENTS_COMMAND.write(this); + + if(validate) { + DRAW_ELEMENTS_COMMAND.read(this); + assert DRAW_ELEMENTS_COMMAND.mode == mode && + DRAW_ELEMENTS_COMMAND.vertexCount == vertexCount && + DRAW_ELEMENTS_COMMAND.offset == offset; + } + } + + public void DrawArrays(int mode, int offset, int vertexCount) { + DRAW_ARRAYS_COMMAND.mode = mode; + DRAW_ARRAYS_COMMAND.offset = offset; + DRAW_ARRAYS_COMMAND.vertexCount = vertexCount; + DRAW_ARRAYS_COMMAND.write(this); + + if(validate) { + DRAW_ARRAYS_COMMAND.read(this); + assert DRAW_ARRAYS_COMMAND.mode == mode && + DRAW_ARRAYS_COMMAND.offset == offset && + DRAW_ARRAYS_COMMAND.vertexCount == vertexCount; + } + } + + public void Enable(int capability) { + Toggle(capability, true); + } + + public void Disable(int capability) { + Toggle(capability, false); + } + + public void Toggle(int capability, boolean enabled) { + TOGGLE_COMMAND.capability = capability; + TOGGLE_COMMAND.state = enabled; + TOGGLE_COMMAND.write(this); + + if(validate) { + TOGGLE_COMMAND.read(this); + assert TOGGLE_COMMAND.capability == capability && + TOGGLE_COMMAND.state == enabled; + } + } + + @SneakyThrows + public void execute() { + readHead = 0; + while (readHead < writeHead) { + int type = (int) (cmd[readHead] & 0xFF); + assert type < REGISTERED_COMMANDS.length; + + BaseCommand command = REGISTERED_COMMANDS[type]; + if (command.isDrawCall() && uboCommandBuffer != null && uboCommandBuffer.isDirty()) { + uboCommandBuffer.upload(); + } + command.read(this); + command.execute(); + } + } + + public void reset() { + writeHead = 0; + readHead = 0; + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java new file mode 100644 index 0000000000..d6a7040afb --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -0,0 +1,32 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.glBindBuffer; + +public class BindElementsArrayCommand extends BaseCommand { + public int ebo; + + @Override + protected void doWrite() { + write32(ebo); + } + + @Override + protected void doRead() { + ebo = read32(); + } + + @Override + public void execute() { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glBindBuffer("); + sb.append(ebo); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java new file mode 100644 index 0000000000..470491c873 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -0,0 +1,31 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30.glBindVertexArray; + +public class BindVertexArrayCommand extends BaseCommand { + public int vao; + + @Override + protected void doWrite() { + write32(vao); + } + + @Override + protected void doRead() { + vao = read32(); + } + + @Override + public void execute() { + glBindVertexArray(vao); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glBindVertexArray("); + sb.append(vao); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java new file mode 100644 index 0000000000..65805e634a --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -0,0 +1,47 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glColorMask; + +public class ColorMaskCommand extends BaseCommand { + public boolean red; + public boolean green; + public boolean blue; + public boolean alpha; + + + @Override + protected void doWrite() { + write1(red ? 1 : 0); + write1(green ? 1 : 0); + write1(blue ? 1 : 0); + write1(alpha ? 1 : 0); + } + + @Override + protected void doRead() { + red = read1() == 1; + green = read1() == 1; + blue = read1() == 1; + alpha = read1() == 1; + } + + @Override + public void execute() { + glColorMask(red, green, blue, alpha); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glColorMask("); + sb.append(red); + sb.append(", "); + sb.append(green); + sb.append(", "); + sb.append(blue); + sb.append(", "); + sb.append(alpha); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java new file mode 100644 index 0000000000..3d3aa48567 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -0,0 +1,38 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glDepthMask; + +public class DepthMaskCommand extends BaseCommand { + public static boolean SKIP_DEPTH_MASKING; + + public boolean flag; + + @Override + protected void doWrite() { + write1(flag ? 1 : 0); + } + + @Override + protected void doRead() { + flag = read1() == 1; + } + + @Override + public void execute() { + if(SKIP_DEPTH_MASKING) + return; + glDepthMask(flag); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDepthMask("); + sb.append(flag); + sb.append(");"); + + if(SKIP_DEPTH_MASKING) + sb.append(" - SKIPPED"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java new file mode 100644 index 0000000000..17aebbce38 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -0,0 +1,43 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glDrawArrays; + +public class DrawArraysCommand extends BaseCommand { + public int mode; + public int vertexCount; + public int offset; + + public DrawArraysCommand() { super(true); } + + @Override + public void doWrite() { + write16(mode); + write32(vertexCount); + write64(offset); + } + + @Override + public void doRead() { + mode = read16(); + vertexCount = read32(); + offset = read32(); + } + + @Override + public void execute() { + glDrawArrays(mode, offset, vertexCount); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDrawElements("); + sb.append(mode); + sb.append(", "); + sb.append(vertexCount); + sb.append(", GL_UNSIGNED_INT, "); + sb.append(offset); + sb.append(");"); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java new file mode 100644 index 0000000000..22980ba5f0 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -0,0 +1,44 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.glDrawElements; + +public class DrawElementsCommand extends BaseCommand { + public int mode; + public int vertexCount; + public long offset; + + public DrawElementsCommand() { super(true); } + + @Override + public void doWrite() { + write16(mode); + write32(vertexCount); + write64(offset); + } + + @Override + public void doRead() { + mode = read16(); + vertexCount = read32(); + offset = read64(); + } + + @Override + public void execute() { + glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, offset); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDrawElements("); + sb.append(mode); + sb.append(", "); + sb.append(vertexCount); + sb.append(", GL_UNSIGNED_INT, "); + sb.append(offset); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java new file mode 100644 index 0000000000..61019cd705 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -0,0 +1,80 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryUtil; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL14.glMultiDrawArrays; + +public class MultiDrawArraysCommand extends BaseCommand { + public int mode; + public int[] offsets; + public int[] counts; + + private IntBuffer offsetsBuffer; + private IntBuffer countsBuffer; + + public MultiDrawArraysCommand() { super(true); } + + @Override + public void doWrite() { + assert offsets.length == counts.length; + + write16(mode); + write32(offsets.length); + for(int i = 0; i < offsets.length; i++) { + write32(offsets[i]); + write32(counts[i]); + } + + offsets = null; + counts = null; + } + + @Override + public void doRead() { + mode = read16(); + + int count = read32(); + if(offsetsBuffer == null || offsetsBuffer.capacity() < count) { + if(offsetsBuffer != null) MemoryUtil.memFree(offsetsBuffer); + if(countsBuffer != null) MemoryUtil.memFree(countsBuffer); + + offsetsBuffer = MemoryUtil.memAllocInt(count * 2); + countsBuffer = MemoryUtil.memAllocInt(count * 2); + } else { + offsetsBuffer.clear(); + countsBuffer.clear(); + } + + for(int i = 0; i < count; i++) { + offsetsBuffer.put(read32()); + countsBuffer.put(read32()); + } + + offsetsBuffer.flip(); + countsBuffer.flip(); + } + + @Override + public void execute() { + glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glMultiDrawArrays("); + sb.append(mode); + sb.append(", ["); + for(int i = 0; i < offsetsBuffer.limit(); i++) { + if(i > 0) sb.append(", "); + sb.append(offsetsBuffer.get(i)); + } + sb.append("], "); + for(int i = 0; i < countsBuffer.limit(); i++) { + if(i > 0) sb.append(", "); + sb.append(countsBuffer.get(i)); + } + sb.append(");"); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java new file mode 100644 index 0000000000..cdbe16a1b5 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -0,0 +1,46 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glDisable; +import static org.lwjgl.opengl.GL11C.glEnable; + +public class ToggleCommand extends BaseCommand { + public int capability; + public boolean state; + + + @Override + public void doWrite() { + write32(capability); + write1(state ? 1 : 0); + } + + @Override + public void doRead() { + capability = read32(); + state = read1() == 1; + } + + @Override + public void execute() { + if (state) { + glEnable(capability); + } else { + glDisable(capability); + } + } + + @Override + public void print(StringBuilder sb) { + if (state) { + sb.append("glEnable("); + sb.append(capability); + sb.append(");"); + } else { + sb.append("glDisable("); + sb.append(capability); + sb.append(");"); + } + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java new file mode 100644 index 0000000000..3e6f9b4659 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java @@ -0,0 +1,59 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public class UpdateCMDUBOCommand extends BaseCommand { + public boolean isBaseOffset; + public int x, y, z; + public int worldViewId; + + @Override + protected void doWrite() { + write1(isBaseOffset ? 1 : 0); + if(isBaseOffset) { + write32(x); + write32(y); + write32(z); + } else { + write32(worldViewId); + } + } + + @Override + protected void doRead() { + isBaseOffset = read1() == 1; + if(isBaseOffset) { + x = read32(); + y = read32(); + z = read32(); + } else { + worldViewId = read32(); + } + } + + @Override + public void execute() { + if(isBaseOffset) { + getUBOCommandBuffer().sceneBase.set(x, y, z); + } else { + getUBOCommandBuffer().worldViewIndex.set(worldViewId); + } + } + + @Override + public void print(StringBuilder sb) { + if(isBaseOffset) { + sb.append("UBOCommandBuffer::sceneBase = ("); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(", "); + sb.append(z); + sb.append(");"); + } else { + sb.append("UBOCommandBuffer::worldViewIndex = ("); + sb.append(worldViewId); + sb.append(");"); + } + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 3164a5656c..0b4af634b2 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; -import rs117.hd.utils.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 35fec691d5..67fe145a86 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -11,10 +11,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.scene.SceneContext; import rs117.hd.scene.materials.Material; import rs117.hd.utils.Camera; -import rs117.hd.utils.CommandBuffer; import static net.runelite.api.Perspective.*; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index ec6721e5e8..36cfd891ff 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -47,6 +47,8 @@ import rs117.hd.HdPluginConfig; import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -68,7 +70,6 @@ import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.Camera; import rs117.hd.utils.ColorUtils; -import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.HDUtils; import rs117.hd.utils.Mat4; import rs117.hd.utils.ModelHash; @@ -789,10 +790,10 @@ private void postDrawTopLevel() { glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); - CommandBuffer.SKIP_DEPTH_MASKING = true; + DepthMaskCommand.SKIP_DEPTH_MASKING = true; sceneCmd.execute(); directionalCmd.execute(); - CommandBuffer.SKIP_DEPTH_MASKING = false; + DepthMaskCommand.SKIP_DEPTH_MASKING = false; glDisable(GL_DEPTH_TEST); diff --git a/src/main/java/rs117/hd/utils/CommandBuffer.java b/src/main/java/rs117/hd/utils/CommandBuffer.java deleted file mode 100644 index 03af06a3d7..0000000000 --- a/src/main/java/rs117/hd/utils/CommandBuffer.java +++ /dev/null @@ -1,240 +0,0 @@ -package rs117.hd.utils; - -import java.nio.IntBuffer; -import java.util.Arrays; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.lwjgl.system.MemoryStack; -import rs117.hd.opengl.uniforms.UBOCommandBuffer; - -import static org.lwjgl.opengl.GL33C.*; - -@Slf4j -public class CommandBuffer { - public static boolean SKIP_DEPTH_MASKING; - - private static final int GL_BIND_VERTEX_ARRAY_TYPE = 0; - private static final int GL_BIND_ELEMENTS_ARRAY_TYPE = 1; - private static final int GL_MULTI_DRAW_ARRAYS_TYPE = 2; - private static final int GL_DRAW_ARRAYS_TYPE = 3; - private static final int GL_DRAW_ELEMENTS_TYPE = 4; - - private static final int GL_DEPTH_MASK_TYPE = 5; - private static final int GL_COLOR_MASK_TYPE = 6; - - private static final int UNIFORM_BASE_OFFSET = 7; - private static final int UNIFORM_WORLD_VIEW_ID = 8; - - private static final int GL_TOGGLE_TYPE = 9; // Combined glEnable & glDisable - - private static final long INT_MASK = 0xFFFF_FFFFL; - - @Setter - private UBOCommandBuffer uboCommandBuffer; - - private long[] cmd = new long[1 << 20]; // ~1 million calls - private int writeHead = 0; - private int readHead = 0; - - private void ensureCapacity(int numLongs) { - if (writeHead + numLongs >= cmd.length) - cmd = Arrays.copyOf(cmd, cmd.length * 2); - } - - public void SetBaseOffset(int x, int y, int z) { - ensureCapacity(4); - cmd[writeHead++] = UNIFORM_BASE_OFFSET; - cmd[writeHead++] = x; - cmd[writeHead++] = y; - cmd[writeHead++] = z; - } - - public void SetWorldViewIndex(int index) { - ensureCapacity(2); - cmd[writeHead++] = UNIFORM_WORLD_VIEW_ID; - cmd[writeHead++] = index; - } - - public void BindVertexArray(int vao) { - ensureCapacity(2); - cmd[writeHead++] = GL_BIND_VERTEX_ARRAY_TYPE; - cmd[writeHead++] = vao; - } - - public void BindElementsArray(int ebo) { - ensureCapacity(2); - cmd[writeHead++] = GL_BIND_ELEMENTS_ARRAY_TYPE; - cmd[writeHead++] = ebo; - } - - public void DepthMask(boolean writeDepth) { - ensureCapacity(2); - cmd[writeHead++] = GL_DEPTH_MASK_TYPE; - cmd[writeHead++] = writeDepth ? 1 : 0; - } - - public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { - ensureCapacity(5); - cmd[writeHead++] = GL_COLOR_MASK_TYPE; - cmd[writeHead++] = writeRed ? 1 : 0; - cmd[writeHead++] = writeGreen ? 1 : 0; - cmd[writeHead++] = writeBlue ? 1 : 0; - cmd[writeHead++] = writeAlpha ? 1 : 0; - } - - public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { - assert offsets.length == counts.length; - - ensureCapacity(3 + (offsets.length * 2)); - cmd[writeHead++] = GL_MULTI_DRAW_ARRAYS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = offsets.length; - for (int i = 0; i < offsets.length; i++) { - cmd[writeHead++] = offsets[i]; - cmd[writeHead++] = counts[i]; - } - } - - public void DrawElements(int mode, int vertexCount, long offset) { - ensureCapacity(4); - cmd[writeHead++] = GL_DRAW_ELEMENTS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = vertexCount; - cmd[writeHead++] = offset; - } - - public void DrawArrays(int mode, int offset, int vertexCount) { - ensureCapacity(4); - cmd[writeHead++] = GL_DRAW_ARRAYS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = offset; - cmd[writeHead++] = vertexCount; - } - - public void Enable(int capability) { - Toggle(capability, true); - } - - public void Disable(int capability) { - Toggle(capability, false); - } - - public void Toggle(int capability, boolean enabled) { - ensureCapacity(2); - cmd[writeHead++] = GL_TOGGLE_TYPE; - cmd[writeHead++] = (enabled ? 1L : 0) << 32 | capability & INT_MASK; - } - - public void execute() { - try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer offsets = null, counts = null; - readHead = 0; - while (readHead < writeHead) { - int type = (int) cmd[readHead++]; - switch (type) { - case UNIFORM_BASE_OFFSET: { - int x = (int) cmd[readHead++]; - int y = (int) cmd[readHead++]; - int z = (int) cmd[readHead++]; - if (uboCommandBuffer != null) - uboCommandBuffer.sceneBase.set(x, y, z); - break; - } - case UNIFORM_WORLD_VIEW_ID: { - int id = (int) cmd[readHead++]; - if (uboCommandBuffer != null) - uboCommandBuffer.worldViewIndex.set(id); - break; - } - case GL_DEPTH_MASK_TYPE: { - int state = (int) cmd[readHead++]; - if (SKIP_DEPTH_MASKING) - continue; - glDepthMask(state == 1); - break; - } - case GL_COLOR_MASK_TYPE: { - int red = (int) cmd[readHead++]; - int green = (int) cmd[readHead++]; - int blue = (int) cmd[readHead++]; - int alpha = (int) cmd[readHead++]; - glColorMask(red == 1, green == 1, blue == 1, alpha == 1); - break; - } - case GL_BIND_VERTEX_ARRAY_TYPE: { - glBindVertexArray((int) cmd[readHead++]); - break; - } - case GL_BIND_ELEMENTS_ARRAY_TYPE: { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (int) cmd[readHead++]); - break; - } - case GL_DRAW_ARRAYS_TYPE: { - int mode = (int) cmd[readHead++]; - int offset = (int) cmd[readHead++]; - int count = (int) cmd[readHead++]; - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glDrawArrays(mode, offset, count); - break; - } - case GL_DRAW_ELEMENTS_TYPE: { - int mode = (int) cmd[readHead++]; - int vertexCount = (int) cmd[readHead++]; - long byteOffset = cmd[readHead++]; - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, byteOffset); - break; - } - case GL_MULTI_DRAW_ARRAYS_TYPE: { - int mode = (int) cmd[readHead++]; - int drawCount = (int) cmd[readHead++]; - - if (offsets == null || offsets.capacity() < drawCount) { - offsets = stack.callocInt(drawCount); - counts = stack.callocInt(drawCount); - } - - for (int i = 0; i < drawCount; i++) { - offsets.put((int) cmd[readHead++]); - counts.put((int) cmd[readHead++]); - } - - offsets.flip(); - counts.flip(); - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glMultiDrawArrays(mode, offsets, counts); - - offsets.clear(); - counts.clear(); - break; - } - case GL_TOGGLE_TYPE: { - long packed = cmd[readHead++]; - int capability = (int) (packed & INT_MASK); - if ((packed >> 32) != 0) { - glEnable(capability); - } else { - glDisable(capability); - } - break; - } - default: - throw new IllegalArgumentException("Encountered an unknown DrawCall type: " + type); - } - } - } - } - - public void reset() { - writeHead = 0; - } -} From 91637d0b439046da8061c3bfc0ea90055d647bac Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:22:05 +0100 Subject: [PATCH 02/50] Fixes, Clean up & better validation --- src/main/java/rs117/hd/HdPlugin.java | 16 ++- .../hd/opengl/commandbuffer/BaseCommand.java | 133 ++++++++++++++---- .../opengl/commandbuffer/CommandBuffer.java | 54 +++++-- .../commands/MultiDrawArraysCommand.java | 19 +-- 4 files changed, 171 insertions(+), 51 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index d0c89d46c3..1af8994735 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -1824,14 +1824,17 @@ public static void clearGLErrors() { // @formatter:on } - public static void checkGLErrors() { + public static boolean checkGLErrors() { return checkGLErrors(null); } + + public static boolean checkGLErrors(String context) { if (SKIP_GL_ERROR_CHECKS) - return; + return false; + boolean hasError = false; while (true) { int err = glGetError(); if (err == GL_NO_ERROR) - return; + return hasError; String errStr; switch (err) { @@ -1858,7 +1861,12 @@ public static void checkGLErrors() { break; } - log.debug("glGetError:", new Exception(errStr)); + if(context != null) { + log.debug("{} - glGetError:", context, new Exception(errStr)); + } else { + log.debug("glGetError:", new Exception(errStr)); + } + hasError = true; } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index 9950244097..ea806c9bab 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -1,26 +1,36 @@ package rs117.hd.opengl.commandbuffer; +import java.nio.BufferUnderflowException; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import rs117.hd.opengl.uniforms.UBOCommandBuffer; +@Slf4j public abstract class BaseCommand { protected int id; + + @Getter + private final String name; + @Getter private final boolean isDrawCall; protected int bitHead; protected int wordIndex; protected CommandBuffer buffer; - - protected BaseCommand() { - this.isDrawCall = false; - } + protected final StringBuilder sb = new StringBuilder(); + protected boolean validate = true; protected BaseCommand(boolean isDrawCall) { + this.name = getClass().getSimpleName(); this.isDrawCall = isDrawCall; } - protected UBOCommandBuffer getUBOCommandBuffer() { return buffer.uboCommandBuffer; } + protected BaseCommand() { this(false); } + + protected UBOCommandBuffer getUBOCommandBuffer() { + return buffer.uboCommandBuffer; + } public abstract void execute(); public abstract void print(StringBuilder sb); @@ -28,18 +38,34 @@ protected BaseCommand(boolean isDrawCall) { public final void write(CommandBuffer buffer) { this.buffer = buffer; - buffer.ensureCapacity(1); - buffer.cmd[buffer.writeHead++] = 0; + buffer.ensureCapacity(buffer.writeHead + 1); + buffer.cmd[buffer.writeHead] = 0; - this.wordIndex = buffer.writeHead - 1; + this.wordIndex = buffer.writeHead; this.bitHead = 0; + if(validate) { + sb.setLength(0); + sb.append(name); + sb.append("::write() wordIndex: "); + sb.append(wordIndex); + sb.append(" START\n"); + } + write8(id); doWrite(); - // Advance writeHead if needed - if (bitHead > 0) + if (bitHead > 0) { + bitHead = 0; wordIndex++; + } + + if(validate) { + sb.append(name); + sb.append("::write() wordIndex: "); + sb.append(wordIndex); + sb.append(" END\n"); + } buffer.writeHead = wordIndex; } @@ -48,14 +74,31 @@ public final void write(CommandBuffer buffer) { public final void read(CommandBuffer buffer) { this.buffer = buffer; + this.wordIndex = buffer.readHead; this.bitHead = 0; - read8(); // Skip over the command id + if(validate) { + sb.append(name); + sb.append("::read() wordIndex: "); + sb.append(wordIndex); + sb.append(" START\n"); + } + + assert read8() == id; doRead(); - if (bitHead > 0) + if (bitHead > 0) { + bitHead = 0; wordIndex++; + } + + if(validate) { + sb.append(name); + sb.append("::read() wordIndex: "); + sb.append(wordIndex); + sb.append(" END\n"); + } buffer.readHead = wordIndex; } @@ -64,19 +107,35 @@ public final void read(CommandBuffer buffer) { protected void writeBits(long value, int numBits) { while (numBits > 0) { - buffer.ensureCapacity(wordIndex + 1); int bitsLeftInWord = 64 - bitHead; int bitsToWrite = Math.min(bitsLeftInWord, numBits); - long mask = (1L << bitsToWrite) - 1; + long mask = bitsToWrite == 64 ? ~0L : (1L << bitsToWrite) - 1; long bits = value & mask; + if(validate) { + sb.append(name); + sb.append("::writeBits("); + sb.append(value); + sb.append(", "); + sb.append(numBits); + sb.append(") wordIndex: "); + sb.append(wordIndex); + sb.append(" bitHead: "); + sb.append(bitHead); + sb.append(" bitsToWrite: "); + sb.append(bitsToWrite); + sb.append("\n"); + } + buffer.cmd[wordIndex] |= bits << bitHead; bitHead += bitsToWrite; if (bitHead == 64) { bitHead = 0; wordIndex++; + buffer.ensureCapacity(wordIndex); + buffer.cmd[wordIndex] = 0; } value >>>= bitsToWrite; @@ -84,22 +143,38 @@ protected void writeBits(long value, int numBits) { } } - protected void write1(int value) { writeBits(value, 1); } - protected void write8(int value) { writeBits(value, 8); } - protected void write16(int value) { writeBits(value, 16); } - protected void write32(int value) { writeBits(value, 32); } - protected void write64(long value){ writeBits(value, 64); } + protected void write1(int value) { writeBits(value, 1); } + protected void write8(int value) { writeBits(value, 8); } + protected void write16(int value) { writeBits(value, 16); } + protected void write32(int value) { writeBits(value, 32); } + protected void write64(long value) { writeBits(value, 64); } protected long readBits(int numBits) { long result = 0; int shift = 0; while (numBits > 0) { + if (wordIndex >= buffer.cmd.length) + throw new BufferUnderflowException(); + long word = buffer.cmd[wordIndex]; int bitsLeftInWord = 64 - bitHead; int bitsToRead = Math.min(bitsLeftInWord, numBits); - long mask = (1L << bitsToRead) - 1; + if(validate) { + sb.append(name); + sb.append("::readBits("); + sb.append(numBits); + sb.append(") wordIndex: "); + sb.append(wordIndex); + sb.append(" bitHead: "); + sb.append(bitHead); + sb.append(" bitsToWrite: "); + sb.append(bitsToRead); + sb.append("\n"); + } + + long mask = bitsToRead == 64 ? ~0L : (1L << bitsToRead) - 1; long bits = (word >>> bitHead) & mask; result |= bits << shift; @@ -113,12 +188,20 @@ protected long readBits(int numBits) { shift += bitsToRead; numBits -= bitsToRead; } + + if(validate) { + sb.append(name); + sb.append("::readBits() Result: "); + sb.append(result); + sb.append("\n"); + } + return result; } - protected int read1() { return (int) readBits(1); } - protected int read8() { return (int) readBits(8); } - protected int read16() { return (int) readBits(16); } - protected int read32() { return (int) readBits(32); } - protected long read64() { return readBits(64); } + protected int read1() { return (int) readBits(1); } + protected int read8() { return (int) readBits(8); } + protected int read16() { return (int) readBits(16); } + protected int read32() { return (int) readBits(32); } + protected long read64() { return readBits(64); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index ec4a563a67..f66e062668 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -4,6 +4,7 @@ import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; @@ -17,6 +18,8 @@ @Slf4j public class CommandBuffer { + private static final StringBuilder SB = new StringBuilder(); + private static int COMMAND_COUNT = 0; private static final BaseCommand[] REGISTERED_COMMANDS = { (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), @@ -72,10 +75,11 @@ public void SetBaseOffset(int x, int y, int z) { if(validate) { UPDATE_CMD_UBO_COMMAND.read(this); - assert UPDATE_CMD_UBO_COMMAND.isBaseOffset && + assert readHead == writeHead && + UPDATE_CMD_UBO_COMMAND.isBaseOffset && UPDATE_CMD_UBO_COMMAND.x == x && UPDATE_CMD_UBO_COMMAND.y == y && - UPDATE_CMD_UBO_COMMAND.z == z; + UPDATE_CMD_UBO_COMMAND.z == z : UPDATE_CMD_UBO_COMMAND.sb.toString(); } } @@ -86,7 +90,8 @@ public void SetWorldViewIndex(int index) { if(validate) { UPDATE_CMD_UBO_COMMAND.read(this); - assert UPDATE_CMD_UBO_COMMAND.worldViewId == index; + assert readHead == writeHead && + UPDATE_CMD_UBO_COMMAND.worldViewId == index : UPDATE_CMD_UBO_COMMAND.sb.toString(); } } @@ -96,7 +101,8 @@ public void BindVertexArray(int vao) { if(validate) { BIND_VERTEX_ARRAY_COMMAND.read(this); - assert BIND_VERTEX_ARRAY_COMMAND.vao == vao; + assert readHead == writeHead && + BIND_VERTEX_ARRAY_COMMAND.vao == vao : BIND_VERTEX_ARRAY_COMMAND.sb.toString(); } } @@ -106,7 +112,8 @@ public void BindElementsArray(int ebo) { if(validate) { BIND_ELEMENTS_ARRAY_COMMAND.read(this); - assert BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo; + assert readHead == writeHead && + BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo : BIND_ELEMENTS_ARRAY_COMMAND.sb.toString(); } } @@ -116,7 +123,8 @@ public void DepthMask(boolean writeDepth) { if(validate) { DEPTH_MASK_COMMAND.read(this); - assert DEPTH_MASK_COMMAND.flag == writeDepth; + assert readHead == writeHead && + DEPTH_MASK_COMMAND.flag == writeDepth : DEPTH_MASK_COMMAND.sb.toString(); } } @@ -125,13 +133,15 @@ public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, b COLOR_MASK_COMMAND.green = writeGreen; COLOR_MASK_COMMAND.blue = writeBlue; COLOR_MASK_COMMAND.alpha = writeAlpha; + COLOR_MASK_COMMAND.write(this); if(validate) { COLOR_MASK_COMMAND.read(this); - assert COLOR_MASK_COMMAND.red == writeRed && + assert readHead == writeHead && + COLOR_MASK_COMMAND.red == writeRed && COLOR_MASK_COMMAND.green == writeGreen && COLOR_MASK_COMMAND.blue == writeBlue && - COLOR_MASK_COMMAND.alpha == writeAlpha; + COLOR_MASK_COMMAND.alpha == writeAlpha : COLOR_MASK_COMMAND.sb.toString(); } } @@ -140,6 +150,15 @@ public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; MULTI_DRAW_ARRAYS_COMMAND.counts = counts; MULTI_DRAW_ARRAYS_COMMAND.write(this); + + if(validate) { + MULTI_DRAW_ARRAYS_COMMAND.read(this); + assert readHead == writeHead && MULTI_DRAW_ARRAYS_COMMAND.mode == mode : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); + for(int i = 0; i < offsets.length; i++) { + assert offsets[i] == MULTI_DRAW_ARRAYS_COMMAND.offsetsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); + assert counts[i] == MULTI_DRAW_ARRAYS_COMMAND.countsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); + } + } } public void DrawElements(int mode, int vertexCount, long offset) { @@ -150,9 +169,10 @@ public void DrawElements(int mode, int vertexCount, long offset) { if(validate) { DRAW_ELEMENTS_COMMAND.read(this); - assert DRAW_ELEMENTS_COMMAND.mode == mode && + assert readHead == writeHead && + DRAW_ELEMENTS_COMMAND.mode == mode && DRAW_ELEMENTS_COMMAND.vertexCount == vertexCount && - DRAW_ELEMENTS_COMMAND.offset == offset; + DRAW_ELEMENTS_COMMAND.offset == offset : DRAW_ELEMENTS_COMMAND.sb.toString(); } } @@ -164,9 +184,10 @@ public void DrawArrays(int mode, int offset, int vertexCount) { if(validate) { DRAW_ARRAYS_COMMAND.read(this); - assert DRAW_ARRAYS_COMMAND.mode == mode && + assert readHead == writeHead && + DRAW_ARRAYS_COMMAND.mode == mode && DRAW_ARRAYS_COMMAND.offset == offset && - DRAW_ARRAYS_COMMAND.vertexCount == vertexCount; + DRAW_ARRAYS_COMMAND.vertexCount == vertexCount : DRAW_ARRAYS_COMMAND.sb.toString(); } } @@ -186,7 +207,7 @@ public void Toggle(int capability, boolean enabled) { if(validate) { TOGGLE_COMMAND.read(this); assert TOGGLE_COMMAND.capability == capability && - TOGGLE_COMMAND.state == enabled; + TOGGLE_COMMAND.state == enabled : TOGGLE_COMMAND.sb.toString(); } } @@ -203,6 +224,13 @@ public void execute() { } command.read(this); command.execute(); + + if(HdPlugin.checkGLErrors(command.getName())) { + command.print(SB); + log.warn("Encountered Error whilst processing command:\n{}", SB.toString()); + SB.setLength(0); + break; + } } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index 61019cd705..37434ca292 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -11,8 +11,8 @@ public class MultiDrawArraysCommand extends BaseCommand { public int[] offsets; public int[] counts; - private IntBuffer offsetsBuffer; - private IntBuffer countsBuffer; + public IntBuffer offsetsBuffer; + public IntBuffer countsBuffer; public MultiDrawArraysCommand() { super(true); } @@ -23,6 +23,7 @@ public void doWrite() { write16(mode); write32(offsets.length); for(int i = 0; i < offsets.length; i++) { + assert offsets[i] >= 0 && counts[i] >= 0; write32(offsets[i]); write32(counts[i]); } @@ -35,19 +36,19 @@ public void doWrite() { public void doRead() { mode = read16(); - int count = read32(); - if(offsetsBuffer == null || offsetsBuffer.capacity() < count) { + int length = read32(); + if(offsetsBuffer == null || offsetsBuffer.capacity() < length) { if(offsetsBuffer != null) MemoryUtil.memFree(offsetsBuffer); if(countsBuffer != null) MemoryUtil.memFree(countsBuffer); - offsetsBuffer = MemoryUtil.memAllocInt(count * 2); - countsBuffer = MemoryUtil.memAllocInt(count * 2); + offsetsBuffer = MemoryUtil.memAllocInt(length * 2); + countsBuffer = MemoryUtil.memAllocInt(length * 2); } else { offsetsBuffer.clear(); countsBuffer.clear(); } - for(int i = 0; i < count; i++) { + for(int i = 0; i < length; i++) { offsetsBuffer.put(read32()); countsBuffer.put(read32()); } @@ -70,11 +71,11 @@ public void print(StringBuilder sb) { if(i > 0) sb.append(", "); sb.append(offsetsBuffer.get(i)); } - sb.append("], "); + sb.append("], ["); for(int i = 0; i < countsBuffer.limit(); i++) { if(i > 0) sb.append(", "); sb.append(countsBuffer.get(i)); } - sb.append(");"); + sb.append("]);"); } } \ No newline at end of file From cb50ae90e42eba21a18733df46255e308640ba82 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:27:19 +0100 Subject: [PATCH 03/50] Disable Validation by default --- src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java | 2 +- src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index ea806c9bab..33755f4782 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -19,7 +19,7 @@ public abstract class BaseCommand { protected int wordIndex; protected CommandBuffer buffer; protected final StringBuilder sb = new StringBuilder(); - protected boolean validate = true; + protected boolean validate = false; protected BaseCommand(boolean isDrawCall) { this.name = getClass().getSimpleName(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index f66e062668..57a361da50 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -59,7 +59,7 @@ private static T REGISTER_COMMAND(ICreateCommand crea protected int writeHead = 0; protected int readHead = 0; - private final boolean validate = true; + private final boolean validate = false; public void ensureCapacity(int numLongs) { if (writeHead + numLongs >= cmd.length) From 184e74867cbfa9f354234ed4409334b370c316b6 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:27:27 +0100 Subject: [PATCH 04/50] Optimizations, Tighter Packing, Validation improvements & Fixes :) --- .../hd/opengl/commandbuffer/BaseCommand.java | 170 ++---------- .../opengl/commandbuffer/CommandBuffer.java | 243 +++++++++++------- .../commands/BindElementsArrayCommand.java | 2 +- .../commands/BindVertexArrayCommand.java | 2 +- .../commands/ColorMaskCommand.java | 19 +- .../commands/DepthMaskCommand.java | 6 +- .../commands/DrawArraysCommand.java | 15 +- .../commands/DrawElementsCommand.java | 20 +- .../commands/MultiDrawArraysCommand.java | 6 +- .../commandbuffer/commands/ToggleCommand.java | 11 +- .../commands/UpdateCMDUBOCommand.java | 6 +- src/main/java/rs117/hd/overlays/Timer.java | 1 + .../rs117/hd/renderer/zone/ZoneRenderer.java | 8 +- 13 files changed, 231 insertions(+), 278 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index 33755f4782..8e7c1be669 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -1,12 +1,12 @@ package rs117.hd.opengl.commandbuffer; -import java.nio.BufferUnderflowException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import rs117.hd.opengl.uniforms.UBOCommandBuffer; @Slf4j public abstract class BaseCommand { + protected int id; @Getter @@ -15,11 +15,7 @@ public abstract class BaseCommand { @Getter private final boolean isDrawCall; - protected int bitHead; - protected int wordIndex; protected CommandBuffer buffer; - protected final StringBuilder sb = new StringBuilder(); - protected boolean validate = false; protected BaseCommand(boolean isDrawCall) { this.name = getClass().getSimpleName(); @@ -38,36 +34,13 @@ protected UBOCommandBuffer getUBOCommandBuffer() { public final void write(CommandBuffer buffer) { this.buffer = buffer; - buffer.ensureCapacity(buffer.writeHead + 1); - buffer.cmd[buffer.writeHead] = 0; - - this.wordIndex = buffer.writeHead; - this.bitHead = 0; - - if(validate) { - sb.setLength(0); - sb.append(name); - sb.append("::write() wordIndex: "); - sb.append(wordIndex); - sb.append(" START\n"); + if(CommandBuffer.VALIDATE) { + buffer.appendToValidationLog(name); + buffer.appendToValidationLog("::write\n"); } write8(id); doWrite(); - - if (bitHead > 0) { - bitHead = 0; - wordIndex++; - } - - if(validate) { - sb.append(name); - sb.append("::write() wordIndex: "); - sb.append(wordIndex); - sb.append(" END\n"); - } - - buffer.writeHead = wordIndex; } protected abstract void doWrite(); @@ -75,133 +48,28 @@ public final void write(CommandBuffer buffer) { public final void read(CommandBuffer buffer) { this.buffer = buffer; - this.wordIndex = buffer.readHead; - this.bitHead = 0; - - if(validate) { - sb.append(name); - sb.append("::read() wordIndex: "); - sb.append(wordIndex); - sb.append(" START\n"); + if(CommandBuffer.VALIDATE) { + buffer.appendToValidationLog(name); + buffer.appendToValidationLog("::read\n"); } - assert read8() == id; doRead(); - - if (bitHead > 0) { - bitHead = 0; - wordIndex++; - } - - if(validate) { - sb.append(name); - sb.append("::read() wordIndex: "); - sb.append(wordIndex); - sb.append(" END\n"); - } - - buffer.readHead = wordIndex; } protected abstract void doRead(); - protected void writeBits(long value, int numBits) { - while (numBits > 0) { - int bitsLeftInWord = 64 - bitHead; - int bitsToWrite = Math.min(bitsLeftInWord, numBits); - - long mask = bitsToWrite == 64 ? ~0L : (1L << bitsToWrite) - 1; - long bits = value & mask; - - if(validate) { - sb.append(name); - sb.append("::writeBits("); - sb.append(value); - sb.append(", "); - sb.append(numBits); - sb.append(") wordIndex: "); - sb.append(wordIndex); - sb.append(" bitHead: "); - sb.append(bitHead); - sb.append(" bitsToWrite: "); - sb.append(bitsToWrite); - sb.append("\n"); - } - - buffer.cmd[wordIndex] |= bits << bitHead; - - bitHead += bitsToWrite; - if (bitHead == 64) { - bitHead = 0; - wordIndex++; - buffer.ensureCapacity(wordIndex); - buffer.cmd[wordIndex] = 0; - } - - value >>>= bitsToWrite; - numBits -= bitsToWrite; - } - } - - protected void write1(int value) { writeBits(value, 1); } - protected void write8(int value) { writeBits(value, 8); } - protected void write16(int value) { writeBits(value, 16); } - protected void write32(int value) { writeBits(value, 32); } - protected void write64(long value) { writeBits(value, 64); } - - protected long readBits(int numBits) { - long result = 0; - int shift = 0; - - while (numBits > 0) { - if (wordIndex >= buffer.cmd.length) - throw new BufferUnderflowException(); - - long word = buffer.cmd[wordIndex]; - int bitsLeftInWord = 64 - bitHead; - int bitsToRead = Math.min(bitsLeftInWord, numBits); - - if(validate) { - sb.append(name); - sb.append("::readBits("); - sb.append(numBits); - sb.append(") wordIndex: "); - sb.append(wordIndex); - sb.append(" bitHead: "); - sb.append(bitHead); - sb.append(" bitsToWrite: "); - sb.append(bitsToRead); - sb.append("\n"); - } - - long mask = bitsToRead == 64 ? ~0L : (1L << bitsToRead) - 1; - long bits = (word >>> bitHead) & mask; - - result |= bits << shift; - - bitHead += bitsToRead; - if (bitHead == 64) { - bitHead = 0; - wordIndex++; - } - - shift += bitsToRead; - numBits -= bitsToRead; - } + protected final void write1(int value) { buffer.writeBits(value & 0x1L, 1); } + protected final void write8(int value) { buffer.writeBits(value & 0xFFL, 8); } + protected final void write16(int value) { buffer.writeBits(value & 0xFFFFL, 16); } + protected final void write32(int value) { buffer.writeBits(value & 0xFFFFFFFFL, 32); } + protected final void write64(long value) { buffer.writeBits(value, 64); } - if(validate) { - sb.append(name); - sb.append("::readBits() Result: "); - sb.append(result); - sb.append("\n"); - } - - return result; - } + protected final int read1() { return (int) buffer.readBits(1); } + protected final int read8() { return (int) buffer.readBits(8); } + protected final int read16() { return (int) buffer.readBits(16); } + protected final int read32() { return (int) buffer.readBits(32); } + protected final long read64() { return buffer.readBits(64); } - protected int read1() { return (int) readBits(1); } - protected int read8() { return (int) readBits(8); } - protected int read16() { return (int) readBits(16); } - protected int read32() { return (int) readBits(32); } - protected long read64() { return readBits(64); } + protected final void writeFlag(boolean value) { write1(value ? 1 : 0); } + protected final boolean readFlag() { return read1() != 0; } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 57a361da50..aee12be8dc 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -1,7 +1,7 @@ package rs117.hd.opengl.commandbuffer; +import java.nio.BufferUnderflowException; import java.util.Arrays; -import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import rs117.hd.HdPlugin; @@ -17,8 +17,10 @@ import rs117.hd.opengl.uniforms.UBOCommandBuffer; @Slf4j -public class CommandBuffer { - private static final StringBuilder SB = new StringBuilder(); +public final class CommandBuffer { + public static boolean VALIDATE = false; + + private static final int BITS_PER_WORD = 64; private static int COMMAND_COUNT = 0; private static final BaseCommand[] REGISTERED_COMMANDS = { @@ -52,14 +54,16 @@ private static T REGISTER_COMMAND(ICreateCommand crea return newCommand; } - @Setter - protected UBOCommandBuffer uboCommandBuffer; + public UBOCommandBuffer uboCommandBuffer; + + private long[] cmd = new long[1 << 20]; // ~1 million calls + private int writeHead = 0; + private int writeBitHead = 0; - protected long[] cmd = new long[1 << 20]; // ~1 million calls - protected int writeHead = 0; - protected int readHead = 0; + private int readHead = 0; + private int readBitHead = 0; - private final boolean validate = false; + private final StringBuilder validation_log = new StringBuilder(); public void ensureCapacity(int numLongs) { if (writeHead + numLongs >= cmd.length) @@ -72,60 +76,27 @@ public void SetBaseOffset(int x, int y, int z) { UPDATE_CMD_UBO_COMMAND.y = y; UPDATE_CMD_UBO_COMMAND.z = z; UPDATE_CMD_UBO_COMMAND.write(this); - - if(validate) { - UPDATE_CMD_UBO_COMMAND.read(this); - assert readHead == writeHead && - UPDATE_CMD_UBO_COMMAND.isBaseOffset && - UPDATE_CMD_UBO_COMMAND.x == x && - UPDATE_CMD_UBO_COMMAND.y == y && - UPDATE_CMD_UBO_COMMAND.z == z : UPDATE_CMD_UBO_COMMAND.sb.toString(); - } } public void SetWorldViewIndex(int index) { UPDATE_CMD_UBO_COMMAND.isBaseOffset = false; UPDATE_CMD_UBO_COMMAND.worldViewId = index; UPDATE_CMD_UBO_COMMAND.write(this); - - if(validate) { - UPDATE_CMD_UBO_COMMAND.read(this); - assert readHead == writeHead && - UPDATE_CMD_UBO_COMMAND.worldViewId == index : UPDATE_CMD_UBO_COMMAND.sb.toString(); - } } public void BindVertexArray(int vao) { BIND_VERTEX_ARRAY_COMMAND.vao = vao; BIND_VERTEX_ARRAY_COMMAND.write(this); - - if(validate) { - BIND_VERTEX_ARRAY_COMMAND.read(this); - assert readHead == writeHead && - BIND_VERTEX_ARRAY_COMMAND.vao == vao : BIND_VERTEX_ARRAY_COMMAND.sb.toString(); - } } public void BindElementsArray(int ebo) { BIND_ELEMENTS_ARRAY_COMMAND.ebo = ebo; BIND_ELEMENTS_ARRAY_COMMAND.write(this); - - if(validate) { - BIND_ELEMENTS_ARRAY_COMMAND.read(this); - assert readHead == writeHead && - BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo : BIND_ELEMENTS_ARRAY_COMMAND.sb.toString(); - } } public void DepthMask(boolean writeDepth) { DEPTH_MASK_COMMAND.flag = writeDepth; DEPTH_MASK_COMMAND.write(this); - - if(validate) { - DEPTH_MASK_COMMAND.read(this); - assert readHead == writeHead && - DEPTH_MASK_COMMAND.flag == writeDepth : DEPTH_MASK_COMMAND.sb.toString(); - } } public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { @@ -134,15 +105,6 @@ public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, b COLOR_MASK_COMMAND.blue = writeBlue; COLOR_MASK_COMMAND.alpha = writeAlpha; COLOR_MASK_COMMAND.write(this); - - if(validate) { - COLOR_MASK_COMMAND.read(this); - assert readHead == writeHead && - COLOR_MASK_COMMAND.red == writeRed && - COLOR_MASK_COMMAND.green == writeGreen && - COLOR_MASK_COMMAND.blue == writeBlue && - COLOR_MASK_COMMAND.alpha == writeAlpha : COLOR_MASK_COMMAND.sb.toString(); - } } public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { @@ -150,30 +112,13 @@ public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; MULTI_DRAW_ARRAYS_COMMAND.counts = counts; MULTI_DRAW_ARRAYS_COMMAND.write(this); - - if(validate) { - MULTI_DRAW_ARRAYS_COMMAND.read(this); - assert readHead == writeHead && MULTI_DRAW_ARRAYS_COMMAND.mode == mode : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); - for(int i = 0; i < offsets.length; i++) { - assert offsets[i] == MULTI_DRAW_ARRAYS_COMMAND.offsetsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); - assert counts[i] == MULTI_DRAW_ARRAYS_COMMAND.countsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); - } - } } - public void DrawElements(int mode, int vertexCount, long offset) { + public void DrawElements(int mode, int vertexCount, long bytesOffset) { DRAW_ELEMENTS_COMMAND.mode = mode; DRAW_ELEMENTS_COMMAND.vertexCount = vertexCount; - DRAW_ELEMENTS_COMMAND.offset = offset; + DRAW_ELEMENTS_COMMAND.bytesOffset = bytesOffset; DRAW_ELEMENTS_COMMAND.write(this); - - if(validate) { - DRAW_ELEMENTS_COMMAND.read(this); - assert readHead == writeHead && - DRAW_ELEMENTS_COMMAND.mode == mode && - DRAW_ELEMENTS_COMMAND.vertexCount == vertexCount && - DRAW_ELEMENTS_COMMAND.offset == offset : DRAW_ELEMENTS_COMMAND.sb.toString(); - } } public void DrawArrays(int mode, int offset, int vertexCount) { @@ -181,14 +126,6 @@ public void DrawArrays(int mode, int offset, int vertexCount) { DRAW_ARRAYS_COMMAND.offset = offset; DRAW_ARRAYS_COMMAND.vertexCount = vertexCount; DRAW_ARRAYS_COMMAND.write(this); - - if(validate) { - DRAW_ARRAYS_COMMAND.read(this); - assert readHead == writeHead && - DRAW_ARRAYS_COMMAND.mode == mode && - DRAW_ARRAYS_COMMAND.offset == offset && - DRAW_ARRAYS_COMMAND.vertexCount == vertexCount : DRAW_ARRAYS_COMMAND.sb.toString(); - } } public void Enable(int capability) { @@ -203,39 +140,165 @@ public void Toggle(int capability, boolean enabled) { TOGGLE_COMMAND.capability = capability; TOGGLE_COMMAND.state = enabled; TOGGLE_COMMAND.write(this); - - if(validate) { - TOGGLE_COMMAND.read(this); - assert TOGGLE_COMMAND.capability == capability && - TOGGLE_COMMAND.state == enabled : TOGGLE_COMMAND.sb.toString(); - } } @SneakyThrows public void execute() { readHead = 0; - while (readHead < writeHead) { - int type = (int) (cmd[readHead] & 0xFF); - assert type < REGISTERED_COMMANDS.length; + readBitHead = 0; + if(VALIDATE) validation_log.setLength(0); + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + int type = (int)readBits(8); + assert type < REGISTERED_COMMANDS.length : validation_log.toString(); BaseCommand command = REGISTERED_COMMANDS[type]; if (command.isDrawCall() && uboCommandBuffer != null && uboCommandBuffer.isDirty()) { uboCommandBuffer.upload(); } + command.read(this); + + command.print(validation_log); + validation_log.append("\n"); + command.execute(); if(HdPlugin.checkGLErrors(command.getName())) { - command.print(SB); - log.warn("Encountered Error whilst processing command:\n{}", SB.toString()); - SB.setLength(0); + log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", validation_log); + validation_log.setLength(0); break; } } } + protected long readBits(int numBits) { + long result = 0; + int shift = 0; + + if (readHead >= cmd.length) + throw new BufferUnderflowException(); + + while (numBits > 0) { + long word = cmd[readHead]; + int bitsLeftInWord = BITS_PER_WORD - readBitHead; + int bitsToRead = Math.min(bitsLeftInWord, numBits); + + if(VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); + + long mask = bitsToRead == BITS_PER_WORD ? ~0L : (1L << bitsToRead) - 1; + long bits = (word >>> readBitHead) & mask; + + result |= bits << shift; + + readBitHead += bitsToRead; + if (readBitHead == BITS_PER_WORD) { + readBitHead = 0; + readHead++; + } + + shift += bitsToRead; + numBits -= bitsToRead; + } + + if(VALIDATE) logReadResult(result); + + int remainder = readBitHead & 7; + if (remainder != 0) readBits(8 - remainder); + + return result; + } + + protected void writeBits(long value, int numBits) { + long originalValue = value; + int originalNumBits = numBits; + int originalWriteHead = writeHead; + int originalWriteBitHead = writeBitHead; + + while (numBits > 0) { + int bitsLeftInWord = BITS_PER_WORD - writeBitHead; + int bitsToWrite = Math.min(bitsLeftInWord, numBits); + + long valueMask = bitsToWrite == BITS_PER_WORD ? ~0L : (1L << bitsToWrite) - 1L; + long bits = value & valueMask; + + if(VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); + + long destMask = bitsToWrite == BITS_PER_WORD ? ~0L : valueMask << writeBitHead; + cmd[writeHead] = (cmd[writeHead] & ~destMask) | ((bits << writeBitHead) & destMask); + + writeBitHead += bitsToWrite; + if (writeBitHead == BITS_PER_WORD) { + writeBitHead = 0; + writeHead++; + ensureCapacity(writeHead); + cmd[writeHead] = 0; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } + + if(VALIDATE) { + int originalReadHead = readHead; + int originalReadBitHead = readBitHead; + + readHead = originalWriteHead; + readBitHead = originalWriteBitHead; + + long decoded = readBits(originalNumBits); + assert decoded == originalValue : "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; + + readHead = originalReadHead; + readBitHead = originalReadBitHead; + } + + int remainder = writeBitHead & 7; + if (remainder != 0) writeBits(0, 8 - remainder); + } + + protected void appendToValidationLog(String str) { + validation_log.append(str); + } + + private void logWriteBits(long value, int numBits, int wordIndex, int bitHead, int bitsToWrite) { + validation_log.append("writeBits("); + validation_log.append(value); + validation_log.append(", "); + validation_log.append(numBits); + validation_log.append(") wordIndex: "); + validation_log.append(wordIndex); + validation_log.append(" bitHead: "); + validation_log.append(bitHead); + validation_log.append(" bitsToWrite: "); + validation_log.append(bitsToWrite); + validation_log.append("\n"); + } + + private void logReadBits(int numBits, int wordIndex, int bitHead, int bitsToRead) { + validation_log.append("readBits("); + validation_log.append(numBits); + validation_log.append(") wordIndex: "); + validation_log.append(wordIndex); + validation_log.append(" bitHead: "); + validation_log.append(bitHead); + validation_log.append(" bitsToRead: "); + validation_log.append(bitsToRead); + validation_log.append("\n"); + } + + private void logReadResult(long result) { + validation_log.append("readBits() Result: "); + validation_log.append(result); + validation_log.append("\n"); + } + public void reset() { writeHead = 0; + writeBitHead = 0; + readHead = 0; + readBitHead = 0; + + validation_log.setLength(0); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java index d6a7040afb..5d9a4a0e4e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -5,7 +5,7 @@ import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; import static org.lwjgl.opengl.GL15.glBindBuffer; -public class BindElementsArrayCommand extends BaseCommand { +public final class BindElementsArrayCommand extends BaseCommand { public int ebo; @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java index 470491c873..ecd7526239 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -4,7 +4,7 @@ import static org.lwjgl.opengl.GL30.glBindVertexArray; -public class BindVertexArrayCommand extends BaseCommand { +public final class BindVertexArrayCommand extends BaseCommand { public int vao; @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java index 65805e634a..4468884adc 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -4,27 +4,26 @@ import static org.lwjgl.opengl.GL11.glColorMask; -public class ColorMaskCommand extends BaseCommand { +public final class ColorMaskCommand extends BaseCommand { public boolean red; public boolean green; public boolean blue; public boolean alpha; - @Override protected void doWrite() { - write1(red ? 1 : 0); - write1(green ? 1 : 0); - write1(blue ? 1 : 0); - write1(alpha ? 1 : 0); + writeFlag(red); + writeFlag(green); + writeFlag(blue); + writeFlag(alpha); } @Override protected void doRead() { - red = read1() == 1; - green = read1() == 1; - blue = read1() == 1; - alpha = read1() == 1; + red = readFlag(); + green = readFlag(); + blue = readFlag(); + alpha = readFlag(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java index 3d3aa48567..45f2ff862c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -4,19 +4,19 @@ import static org.lwjgl.opengl.GL11.glDepthMask; -public class DepthMaskCommand extends BaseCommand { +public final class DepthMaskCommand extends BaseCommand { public static boolean SKIP_DEPTH_MASKING; public boolean flag; @Override protected void doWrite() { - write1(flag ? 1 : 0); + writeFlag(flag); } @Override protected void doRead() { - flag = read1() == 1; + flag = readFlag(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java index 17aebbce38..37d90a3baf 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -2,9 +2,9 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; -import static org.lwjgl.opengl.GL11.glDrawArrays; +import static org.lwjgl.opengl.GL11C.glDrawArrays; -public class DrawArraysCommand extends BaseCommand { +public final class DrawArraysCommand extends BaseCommand { public int mode; public int vertexCount; public int offset; @@ -13,9 +13,13 @@ public class DrawArraysCommand extends BaseCommand { @Override public void doWrite() { + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert offset >= 0 : "offset must be >= 0"; + write16(mode); write32(vertexCount); - write64(offset); + write32(offset); } @Override @@ -23,10 +27,15 @@ public void doRead() { mode = read16(); vertexCount = read32(); offset = read32(); + + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert offset >= 0 : "offset must be >= 0"; } @Override public void execute() { + glDrawArrays(mode, offset, vertexCount); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java index 22980ba5f0..8096ba5518 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -5,30 +5,38 @@ import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; import static org.lwjgl.opengl.GL11.glDrawElements; -public class DrawElementsCommand extends BaseCommand { +public final class DrawElementsCommand extends BaseCommand { public int mode; public int vertexCount; - public long offset; + public long bytesOffset; public DrawElementsCommand() { super(true); } @Override public void doWrite() { + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert bytesOffset >= 0 : "bytesOffset must be >= 0"; + write16(mode); write32(vertexCount); - write64(offset); + write64(bytesOffset); } @Override public void doRead() { mode = read16(); vertexCount = read32(); - offset = read64(); + bytesOffset = read64(); + + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert bytesOffset >= 0 : "bytesOffset must be >= 0"; } @Override public void execute() { - glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, offset); + glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, bytesOffset); } @Override @@ -38,7 +46,7 @@ public void print(StringBuilder sb) { sb.append(", "); sb.append(vertexCount); sb.append(", GL_UNSIGNED_INT, "); - sb.append(offset); + sb.append(bytesOffset); sb.append(");"); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index 37434ca292..002db9f493 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -6,7 +6,7 @@ import static org.lwjgl.opengl.GL14.glMultiDrawArrays; -public class MultiDrawArraysCommand extends BaseCommand { +public final class MultiDrawArraysCommand extends BaseCommand { public int mode; public int[] offsets; public int[] counts; @@ -23,7 +23,8 @@ public void doWrite() { write16(mode); write32(offsets.length); for(int i = 0; i < offsets.length; i++) { - assert offsets[i] >= 0 && counts[i] >= 0; + assert offsets[i] >= 0 : "offset must be >= 0"; + assert counts[i] >= 0 : "vertexCount must be >= 0"; write32(offsets[i]); write32(counts[i]); } @@ -59,6 +60,7 @@ public void doRead() { @Override public void execute() { + glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java index cdbe16a1b5..3051238355 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -2,24 +2,23 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; -import static org.lwjgl.opengl.GL11C.glDisable; -import static org.lwjgl.opengl.GL11C.glEnable; +import static org.lwjgl.opengl.GL11.glDisable; +import static org.lwjgl.opengl.GL11.glEnable; -public class ToggleCommand extends BaseCommand { +public final class ToggleCommand extends BaseCommand { public int capability; public boolean state; - @Override public void doWrite() { write32(capability); - write1(state ? 1 : 0); + writeFlag(state); } @Override public void doRead() { capability = read32(); - state = read1() == 1; + state = readFlag(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java index 3e6f9b4659..cced2a5369 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java @@ -2,14 +2,14 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; -public class UpdateCMDUBOCommand extends BaseCommand { +public final class UpdateCMDUBOCommand extends BaseCommand { public boolean isBaseOffset; public int x, y, z; public int worldViewId; @Override protected void doWrite() { - write1(isBaseOffset ? 1 : 0); + writeFlag(isBaseOffset); if(isBaseOffset) { write32(x); write32(y); @@ -21,7 +21,7 @@ protected void doWrite() { @Override protected void doRead() { - isBaseOffset = read1() == 1; + isBaseOffset = readFlag(); if(isBaseOffset) { x = read32(); y = read32(); diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 0decbc6535..f20c80e2dc 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,6 +17,7 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), + COMMAND_BUFFER_EXECUTE, UPDATE_ENVIRONMENT, UPDATE_LIGHTS, IMPOSTOR_TRACKING, diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 433a54f2a8..e95b3f3203 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -244,8 +244,8 @@ public void initialize() { uboCommandBuffer.initialize(UNIFORM_BLOCK_COMMAND_BUFFER); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); - sceneCmd.setUboCommandBuffer(uboCommandBuffer); - directionalCmd.setUboCommandBuffer(uboCommandBuffer); + sceneCmd.uboCommandBuffer = uboCommandBuffer; + directionalCmd.uboCommandBuffer = uboCommandBuffer; } @Override @@ -791,9 +791,11 @@ private void postDrawTopLevel() { glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); + frameTimer.begin(Timer.COMMAND_BUFFER_EXECUTE); DepthMaskCommand.SKIP_DEPTH_MASKING = true; directionalCmd.execute(); DepthMaskCommand.SKIP_DEPTH_MASKING = false; + frameTimer.end(Timer.COMMAND_BUFFER_EXECUTE); glDisable(GL_DEPTH_TEST); @@ -834,7 +836,9 @@ private void postDrawTopLevel() { glDepthFunc(GL_GREATER); // Render the scene + frameTimer.begin(Timer.COMMAND_BUFFER_EXECUTE); sceneCmd.execute(); + frameTimer.end(Timer.COMMAND_BUFFER_EXECUTE); // TODO: Filler tiles From 4998e001fae4e4f12339033973fd29c14d626656 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 00:25:58 +0100 Subject: [PATCH 05/50] Removed old workaround --- .../java/rs117/hd/opengl/commandbuffer/CommandBuffer.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index aee12be8dc..80e71aa8a0 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -202,9 +202,6 @@ protected long readBits(int numBits) { if(VALIDATE) logReadResult(result); - int remainder = readBitHead & 7; - if (remainder != 0) readBits(8 - remainder); - return result; } @@ -251,9 +248,6 @@ protected void writeBits(long value, int numBits) { readHead = originalReadHead; readBitHead = originalReadBitHead; } - - int remainder = writeBitHead & 7; - if (remainder != 0) writeBits(0, 8 - remainder); } protected void appendToValidationLog(String str) { From 13700477492c18e42567aa41ec671460154f0c98 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:48:23 +0100 Subject: [PATCH 06/50] Optimisations --- .../opengl/commandbuffer/CommandBuffer.java | 154 +++++++++++------- 1 file changed, 99 insertions(+), 55 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 80e71aa8a0..b5cd9bb049 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -21,6 +21,15 @@ public final class CommandBuffer { public static boolean VALIDATE = false; private static final int BITS_PER_WORD = 64; + private static final long[] MASKS = new long[65]; + + static { + MASKS[0] = 0L; + for (int i = 1; i < BITS_PER_WORD; i++) { + MASKS[i] = (1L << i) - 1L; + } + MASKS[BITS_PER_WORD] = ~0L; + } private static int COMMAND_COUNT = 0; private static final BaseCommand[] REGISTERED_COMMANDS = { @@ -63,6 +72,7 @@ private static T REGISTER_COMMAND(ICreateCommand crea private int readHead = 0; private int readBitHead = 0; + private final StringBuilder cmd_log = new StringBuilder(); private final StringBuilder validation_log = new StringBuilder(); public void ensureCapacity(int numLongs) { @@ -142,11 +152,29 @@ public void Toggle(int capability, boolean enabled) { TOGGLE_COMMAND.write(this); } + public void printCommandBuffer() { + readHead = 0; + readBitHead = 0; + validation_log.setLength(0); + cmd_log.setLength(0); + + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + int type = (int) readBits(8); + assert type < REGISTERED_COMMANDS.length : validation_log.toString(); + + BaseCommand command = REGISTERED_COMMANDS[type]; + command.read(this); + command.print(cmd_log); + } + log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", cmd_log); + } + @SneakyThrows public void execute() { readHead = 0; readBitHead = 0; - if(VALIDATE) validation_log.setLength(0); + validation_log.setLength(0); + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { int type = (int)readBits(8); assert type < REGISTERED_COMMANDS.length : validation_log.toString(); @@ -157,47 +185,51 @@ public void execute() { } command.read(this); - - command.print(validation_log); - validation_log.append("\n"); - command.execute(); if(HdPlugin.checkGLErrors(command.getName())) { - log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", validation_log); - validation_log.setLength(0); + printCommandBuffer(); break; } } } protected long readBits(int numBits) { - long result = 0; - int shift = 0; - if (readHead >= cmd.length) throw new BufferUnderflowException(); - while (numBits > 0) { - long word = cmd[readHead]; - int bitsLeftInWord = BITS_PER_WORD - readBitHead; - int bitsToRead = Math.min(bitsLeftInWord, numBits); - - if(VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); - - long mask = bitsToRead == BITS_PER_WORD ? ~0L : (1L << bitsToRead) - 1; - long bits = (word >>> readBitHead) & mask; - - result |= bits << shift; - - readBitHead += bitsToRead; - if (readBitHead == BITS_PER_WORD) { - readBitHead = 0; + long result = 0; + long word = cmd[readHead]; + if(readBitHead == 0) { + if (numBits == BITS_PER_WORD){ readHead++; + result = word; + } else { + readBitHead += numBits; + result = word & MASKS[numBits]; + } + } else { + int shift = 0; + while (numBits > 0) { + int bitsLeftInWord = BITS_PER_WORD - readBitHead; + int bitsToRead = Math.min(bitsLeftInWord, numBits); + + if (VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); + + long mask = (bitsToRead == BITS_PER_WORD) ? ~0L : MASKS[bitsToRead]; + long bits = (word >>> readBitHead) & mask; + result |= (bits << shift); + + readBitHead += bitsToRead; + if (readBitHead == BITS_PER_WORD) { + readBitHead = 0; + readHead++; + word = cmd[readHead]; + } + + shift += bitsToRead; + numBits -= bitsToRead; } - - shift += bitsToRead; - numBits -= bitsToRead; } if(VALIDATE) logReadResult(result); @@ -211,42 +243,54 @@ protected void writeBits(long value, int numBits) { int originalWriteHead = writeHead; int originalWriteBitHead = writeBitHead; - while (numBits > 0) { - int bitsLeftInWord = BITS_PER_WORD - writeBitHead; - int bitsToWrite = Math.min(bitsLeftInWord, numBits); + if (writeBitHead == 0) { + if (numBits == BITS_PER_WORD) { + cmd[writeHead++] = value; + } else { + writeBitHead += numBits; + cmd[writeHead] = value & MASKS[numBits]; + } + } else { + long word = cmd[writeHead]; + while (numBits > 0) { + int bitsLeftInWord = BITS_PER_WORD - writeBitHead; + int bitsToWrite = Math.min(bitsLeftInWord, numBits); - long valueMask = bitsToWrite == BITS_PER_WORD ? ~0L : (1L << bitsToWrite) - 1L; - long bits = value & valueMask; + long bits = (bitsToWrite == BITS_PER_WORD) ? value : (value & MASKS[bitsToWrite]); - if(VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); + if (VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); - long destMask = bitsToWrite == BITS_PER_WORD ? ~0L : valueMask << writeBitHead; - cmd[writeHead] = (cmd[writeHead] & ~destMask) | ((bits << writeBitHead) & destMask); + long destMask = (bitsToWrite == BITS_PER_WORD) ? ~0L : (MASKS[bitsToWrite] << writeBitHead); + word = (word & ~destMask) | ((bits << writeBitHead) & destMask); + cmd[writeHead] = word; - writeBitHead += bitsToWrite; - if (writeBitHead == BITS_PER_WORD) { - writeBitHead = 0; - writeHead++; - ensureCapacity(writeHead); - cmd[writeHead] = 0; - } + writeBitHead += bitsToWrite; + if (writeBitHead == BITS_PER_WORD) { + writeHead++; + ensureCapacity(writeHead); - value >>>= bitsToWrite; - numBits -= bitsToWrite; - } + writeBitHead = 0; + word = 0; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } - if(VALIDATE) { - int originalReadHead = readHead; - int originalReadBitHead = readBitHead; + if (VALIDATE) { + int originalReadHead = readHead; + int originalReadBitHead = readBitHead; - readHead = originalWriteHead; - readBitHead = originalWriteBitHead; + readHead = originalWriteHead; + readBitHead = originalWriteBitHead; - long decoded = readBits(originalNumBits); - assert decoded == originalValue : "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; + long decoded = readBits(originalNumBits); + assert decoded == originalValue : + "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; - readHead = originalReadHead; - readBitHead = originalReadBitHead; + readHead = originalReadHead; + readBitHead = originalReadBitHead; + } } } From 64be837e97b1923d155a9224e43e58ca3956b87e Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 10:12:52 +0100 Subject: [PATCH 07/50] Refactor of CommandBuffer to break it out into a more maintainable & debuggable layout --- .../hd/opengl/commandbuffer/BaseCommand.java | 124 +++++++++ .../opengl/commandbuffer/CommandBuffer.java | 213 ++++++++++++++++ .../commands/BindElementsArrayCommand.java | 32 +++ .../commands/BindVertexArrayCommand.java | 31 +++ .../commands/ColorMaskCommand.java | 47 ++++ .../commands/DepthMaskCommand.java | 38 +++ .../commands/DrawArraysCommand.java | 43 ++++ .../commands/DrawElementsCommand.java | 44 ++++ .../commands/MultiDrawArraysCommand.java | 80 ++++++ .../commandbuffer/commands/ToggleCommand.java | 46 ++++ .../commands/UpdateCMDUBOCommand.java | 59 +++++ src/main/java/rs117/hd/renderer/zone/VAO.java | 2 +- .../java/rs117/hd/renderer/zone/Zone.java | 2 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 7 +- .../java/rs117/hd/utils/CommandBuffer.java | 240 ------------------ 15 files changed, 763 insertions(+), 245 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java delete mode 100644 src/main/java/rs117/hd/utils/CommandBuffer.java diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java new file mode 100644 index 0000000000..9950244097 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -0,0 +1,124 @@ +package rs117.hd.opengl.commandbuffer; + +import lombok.Getter; +import rs117.hd.opengl.uniforms.UBOCommandBuffer; + +public abstract class BaseCommand { + protected int id; + @Getter + private final boolean isDrawCall; + + protected int bitHead; + protected int wordIndex; + protected CommandBuffer buffer; + + protected BaseCommand() { + this.isDrawCall = false; + } + + protected BaseCommand(boolean isDrawCall) { + this.isDrawCall = isDrawCall; + } + + protected UBOCommandBuffer getUBOCommandBuffer() { return buffer.uboCommandBuffer; } + + public abstract void execute(); + public abstract void print(StringBuilder sb); + + public final void write(CommandBuffer buffer) { + this.buffer = buffer; + + buffer.ensureCapacity(1); + buffer.cmd[buffer.writeHead++] = 0; + + this.wordIndex = buffer.writeHead - 1; + this.bitHead = 0; + + write8(id); + doWrite(); + + // Advance writeHead if needed + if (bitHead > 0) + wordIndex++; + + buffer.writeHead = wordIndex; + } + + protected abstract void doWrite(); + + public final void read(CommandBuffer buffer) { + this.buffer = buffer; + this.wordIndex = buffer.readHead; + this.bitHead = 0; + + read8(); // Skip over the command id + doRead(); + + if (bitHead > 0) + wordIndex++; + + buffer.readHead = wordIndex; + } + + protected abstract void doRead(); + + protected void writeBits(long value, int numBits) { + while (numBits > 0) { + buffer.ensureCapacity(wordIndex + 1); + int bitsLeftInWord = 64 - bitHead; + int bitsToWrite = Math.min(bitsLeftInWord, numBits); + + long mask = (1L << bitsToWrite) - 1; + long bits = value & mask; + + buffer.cmd[wordIndex] |= bits << bitHead; + + bitHead += bitsToWrite; + if (bitHead == 64) { + bitHead = 0; + wordIndex++; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } + } + + protected void write1(int value) { writeBits(value, 1); } + protected void write8(int value) { writeBits(value, 8); } + protected void write16(int value) { writeBits(value, 16); } + protected void write32(int value) { writeBits(value, 32); } + protected void write64(long value){ writeBits(value, 64); } + + protected long readBits(int numBits) { + long result = 0; + int shift = 0; + + while (numBits > 0) { + long word = buffer.cmd[wordIndex]; + int bitsLeftInWord = 64 - bitHead; + int bitsToRead = Math.min(bitsLeftInWord, numBits); + + long mask = (1L << bitsToRead) - 1; + long bits = (word >>> bitHead) & mask; + + result |= bits << shift; + + bitHead += bitsToRead; + if (bitHead == 64) { + bitHead = 0; + wordIndex++; + } + + shift += bitsToRead; + numBits -= bitsToRead; + } + return result; + } + + protected int read1() { return (int) readBits(1); } + protected int read8() { return (int) readBits(8); } + protected int read16() { return (int) readBits(16); } + protected int read32() { return (int) readBits(32); } + protected long read64() { return readBits(64); } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java new file mode 100644 index 0000000000..ec4a563a67 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -0,0 +1,213 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.Arrays; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; +import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; +import rs117.hd.opengl.commandbuffer.commands.UpdateCMDUBOCommand; +import rs117.hd.opengl.uniforms.UBOCommandBuffer; + +@Slf4j +public class CommandBuffer { + private static int COMMAND_COUNT = 0; + private static final BaseCommand[] REGISTERED_COMMANDS = { + (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), + (DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new)), + (MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new)), + (TOGGLE_COMMAND = REGISTER_COMMAND(ToggleCommand::new)), + (COLOR_MASK_COMMAND = REGISTER_COMMAND(ColorMaskCommand::new)), + (DEPTH_MASK_COMMAND = REGISTER_COMMAND(DepthMaskCommand::new)), + (BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new)), + (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), + (UPDATE_CMD_UBO_COMMAND = REGISTER_COMMAND(UpdateCMDUBOCommand::new)), + }; + + private static final DrawArraysCommand DRAW_ARRAYS_COMMAND; + private static final DrawElementsCommand DRAW_ELEMENTS_COMMAND; + private static final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND; + private static final ToggleCommand TOGGLE_COMMAND; + private static final ColorMaskCommand COLOR_MASK_COMMAND; + private static final DepthMaskCommand DEPTH_MASK_COMMAND; + private static final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; + private static final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; + private static final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; + + interface ICreateCommand { T construct(); } + + private static T REGISTER_COMMAND(ICreateCommand createCommand) { + T newCommand = createCommand.construct(); + newCommand.id = COMMAND_COUNT; + COMMAND_COUNT++; + return newCommand; + } + + @Setter + protected UBOCommandBuffer uboCommandBuffer; + + protected long[] cmd = new long[1 << 20]; // ~1 million calls + protected int writeHead = 0; + protected int readHead = 0; + + private final boolean validate = true; + + public void ensureCapacity(int numLongs) { + if (writeHead + numLongs >= cmd.length) + cmd = Arrays.copyOf(cmd, cmd.length * 2); + } + + public void SetBaseOffset(int x, int y, int z) { + UPDATE_CMD_UBO_COMMAND.isBaseOffset = true; + UPDATE_CMD_UBO_COMMAND.x = x; + UPDATE_CMD_UBO_COMMAND.y = y; + UPDATE_CMD_UBO_COMMAND.z = z; + UPDATE_CMD_UBO_COMMAND.write(this); + + if(validate) { + UPDATE_CMD_UBO_COMMAND.read(this); + assert UPDATE_CMD_UBO_COMMAND.isBaseOffset && + UPDATE_CMD_UBO_COMMAND.x == x && + UPDATE_CMD_UBO_COMMAND.y == y && + UPDATE_CMD_UBO_COMMAND.z == z; + } + } + + public void SetWorldViewIndex(int index) { + UPDATE_CMD_UBO_COMMAND.isBaseOffset = false; + UPDATE_CMD_UBO_COMMAND.worldViewId = index; + UPDATE_CMD_UBO_COMMAND.write(this); + + if(validate) { + UPDATE_CMD_UBO_COMMAND.read(this); + assert UPDATE_CMD_UBO_COMMAND.worldViewId == index; + } + } + + public void BindVertexArray(int vao) { + BIND_VERTEX_ARRAY_COMMAND.vao = vao; + BIND_VERTEX_ARRAY_COMMAND.write(this); + + if(validate) { + BIND_VERTEX_ARRAY_COMMAND.read(this); + assert BIND_VERTEX_ARRAY_COMMAND.vao == vao; + } + } + + public void BindElementsArray(int ebo) { + BIND_ELEMENTS_ARRAY_COMMAND.ebo = ebo; + BIND_ELEMENTS_ARRAY_COMMAND.write(this); + + if(validate) { + BIND_ELEMENTS_ARRAY_COMMAND.read(this); + assert BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo; + } + } + + public void DepthMask(boolean writeDepth) { + DEPTH_MASK_COMMAND.flag = writeDepth; + DEPTH_MASK_COMMAND.write(this); + + if(validate) { + DEPTH_MASK_COMMAND.read(this); + assert DEPTH_MASK_COMMAND.flag == writeDepth; + } + } + + public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { + COLOR_MASK_COMMAND.red = writeRed; + COLOR_MASK_COMMAND.green = writeGreen; + COLOR_MASK_COMMAND.blue = writeBlue; + COLOR_MASK_COMMAND.alpha = writeAlpha; + + if(validate) { + COLOR_MASK_COMMAND.read(this); + assert COLOR_MASK_COMMAND.red == writeRed && + COLOR_MASK_COMMAND.green == writeGreen && + COLOR_MASK_COMMAND.blue == writeBlue && + COLOR_MASK_COMMAND.alpha == writeAlpha; + } + } + + public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { + MULTI_DRAW_ARRAYS_COMMAND.mode = mode; + MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; + MULTI_DRAW_ARRAYS_COMMAND.counts = counts; + MULTI_DRAW_ARRAYS_COMMAND.write(this); + } + + public void DrawElements(int mode, int vertexCount, long offset) { + DRAW_ELEMENTS_COMMAND.mode = mode; + DRAW_ELEMENTS_COMMAND.vertexCount = vertexCount; + DRAW_ELEMENTS_COMMAND.offset = offset; + DRAW_ELEMENTS_COMMAND.write(this); + + if(validate) { + DRAW_ELEMENTS_COMMAND.read(this); + assert DRAW_ELEMENTS_COMMAND.mode == mode && + DRAW_ELEMENTS_COMMAND.vertexCount == vertexCount && + DRAW_ELEMENTS_COMMAND.offset == offset; + } + } + + public void DrawArrays(int mode, int offset, int vertexCount) { + DRAW_ARRAYS_COMMAND.mode = mode; + DRAW_ARRAYS_COMMAND.offset = offset; + DRAW_ARRAYS_COMMAND.vertexCount = vertexCount; + DRAW_ARRAYS_COMMAND.write(this); + + if(validate) { + DRAW_ARRAYS_COMMAND.read(this); + assert DRAW_ARRAYS_COMMAND.mode == mode && + DRAW_ARRAYS_COMMAND.offset == offset && + DRAW_ARRAYS_COMMAND.vertexCount == vertexCount; + } + } + + public void Enable(int capability) { + Toggle(capability, true); + } + + public void Disable(int capability) { + Toggle(capability, false); + } + + public void Toggle(int capability, boolean enabled) { + TOGGLE_COMMAND.capability = capability; + TOGGLE_COMMAND.state = enabled; + TOGGLE_COMMAND.write(this); + + if(validate) { + TOGGLE_COMMAND.read(this); + assert TOGGLE_COMMAND.capability == capability && + TOGGLE_COMMAND.state == enabled; + } + } + + @SneakyThrows + public void execute() { + readHead = 0; + while (readHead < writeHead) { + int type = (int) (cmd[readHead] & 0xFF); + assert type < REGISTERED_COMMANDS.length; + + BaseCommand command = REGISTERED_COMMANDS[type]; + if (command.isDrawCall() && uboCommandBuffer != null && uboCommandBuffer.isDirty()) { + uboCommandBuffer.upload(); + } + command.read(this); + command.execute(); + } + } + + public void reset() { + writeHead = 0; + readHead = 0; + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java new file mode 100644 index 0000000000..d6a7040afb --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -0,0 +1,32 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.glBindBuffer; + +public class BindElementsArrayCommand extends BaseCommand { + public int ebo; + + @Override + protected void doWrite() { + write32(ebo); + } + + @Override + protected void doRead() { + ebo = read32(); + } + + @Override + public void execute() { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glBindBuffer("); + sb.append(ebo); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java new file mode 100644 index 0000000000..470491c873 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -0,0 +1,31 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30.glBindVertexArray; + +public class BindVertexArrayCommand extends BaseCommand { + public int vao; + + @Override + protected void doWrite() { + write32(vao); + } + + @Override + protected void doRead() { + vao = read32(); + } + + @Override + public void execute() { + glBindVertexArray(vao); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glBindVertexArray("); + sb.append(vao); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java new file mode 100644 index 0000000000..65805e634a --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -0,0 +1,47 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glColorMask; + +public class ColorMaskCommand extends BaseCommand { + public boolean red; + public boolean green; + public boolean blue; + public boolean alpha; + + + @Override + protected void doWrite() { + write1(red ? 1 : 0); + write1(green ? 1 : 0); + write1(blue ? 1 : 0); + write1(alpha ? 1 : 0); + } + + @Override + protected void doRead() { + red = read1() == 1; + green = read1() == 1; + blue = read1() == 1; + alpha = read1() == 1; + } + + @Override + public void execute() { + glColorMask(red, green, blue, alpha); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glColorMask("); + sb.append(red); + sb.append(", "); + sb.append(green); + sb.append(", "); + sb.append(blue); + sb.append(", "); + sb.append(alpha); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java new file mode 100644 index 0000000000..3d3aa48567 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -0,0 +1,38 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glDepthMask; + +public class DepthMaskCommand extends BaseCommand { + public static boolean SKIP_DEPTH_MASKING; + + public boolean flag; + + @Override + protected void doWrite() { + write1(flag ? 1 : 0); + } + + @Override + protected void doRead() { + flag = read1() == 1; + } + + @Override + public void execute() { + if(SKIP_DEPTH_MASKING) + return; + glDepthMask(flag); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDepthMask("); + sb.append(flag); + sb.append(");"); + + if(SKIP_DEPTH_MASKING) + sb.append(" - SKIPPED"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java new file mode 100644 index 0000000000..17aebbce38 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -0,0 +1,43 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glDrawArrays; + +public class DrawArraysCommand extends BaseCommand { + public int mode; + public int vertexCount; + public int offset; + + public DrawArraysCommand() { super(true); } + + @Override + public void doWrite() { + write16(mode); + write32(vertexCount); + write64(offset); + } + + @Override + public void doRead() { + mode = read16(); + vertexCount = read32(); + offset = read32(); + } + + @Override + public void execute() { + glDrawArrays(mode, offset, vertexCount); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDrawElements("); + sb.append(mode); + sb.append(", "); + sb.append(vertexCount); + sb.append(", GL_UNSIGNED_INT, "); + sb.append(offset); + sb.append(");"); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java new file mode 100644 index 0000000000..22980ba5f0 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -0,0 +1,44 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.glDrawElements; + +public class DrawElementsCommand extends BaseCommand { + public int mode; + public int vertexCount; + public long offset; + + public DrawElementsCommand() { super(true); } + + @Override + public void doWrite() { + write16(mode); + write32(vertexCount); + write64(offset); + } + + @Override + public void doRead() { + mode = read16(); + vertexCount = read32(); + offset = read64(); + } + + @Override + public void execute() { + glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, offset); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDrawElements("); + sb.append(mode); + sb.append(", "); + sb.append(vertexCount); + sb.append(", GL_UNSIGNED_INT, "); + sb.append(offset); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java new file mode 100644 index 0000000000..61019cd705 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -0,0 +1,80 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryUtil; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL14.glMultiDrawArrays; + +public class MultiDrawArraysCommand extends BaseCommand { + public int mode; + public int[] offsets; + public int[] counts; + + private IntBuffer offsetsBuffer; + private IntBuffer countsBuffer; + + public MultiDrawArraysCommand() { super(true); } + + @Override + public void doWrite() { + assert offsets.length == counts.length; + + write16(mode); + write32(offsets.length); + for(int i = 0; i < offsets.length; i++) { + write32(offsets[i]); + write32(counts[i]); + } + + offsets = null; + counts = null; + } + + @Override + public void doRead() { + mode = read16(); + + int count = read32(); + if(offsetsBuffer == null || offsetsBuffer.capacity() < count) { + if(offsetsBuffer != null) MemoryUtil.memFree(offsetsBuffer); + if(countsBuffer != null) MemoryUtil.memFree(countsBuffer); + + offsetsBuffer = MemoryUtil.memAllocInt(count * 2); + countsBuffer = MemoryUtil.memAllocInt(count * 2); + } else { + offsetsBuffer.clear(); + countsBuffer.clear(); + } + + for(int i = 0; i < count; i++) { + offsetsBuffer.put(read32()); + countsBuffer.put(read32()); + } + + offsetsBuffer.flip(); + countsBuffer.flip(); + } + + @Override + public void execute() { + glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glMultiDrawArrays("); + sb.append(mode); + sb.append(", ["); + for(int i = 0; i < offsetsBuffer.limit(); i++) { + if(i > 0) sb.append(", "); + sb.append(offsetsBuffer.get(i)); + } + sb.append("], "); + for(int i = 0; i < countsBuffer.limit(); i++) { + if(i > 0) sb.append(", "); + sb.append(countsBuffer.get(i)); + } + sb.append(");"); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java new file mode 100644 index 0000000000..cdbe16a1b5 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -0,0 +1,46 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glDisable; +import static org.lwjgl.opengl.GL11C.glEnable; + +public class ToggleCommand extends BaseCommand { + public int capability; + public boolean state; + + + @Override + public void doWrite() { + write32(capability); + write1(state ? 1 : 0); + } + + @Override + public void doRead() { + capability = read32(); + state = read1() == 1; + } + + @Override + public void execute() { + if (state) { + glEnable(capability); + } else { + glDisable(capability); + } + } + + @Override + public void print(StringBuilder sb) { + if (state) { + sb.append("glEnable("); + sb.append(capability); + sb.append(");"); + } else { + sb.append("glDisable("); + sb.append(capability); + sb.append(");"); + } + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java new file mode 100644 index 0000000000..3e6f9b4659 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java @@ -0,0 +1,59 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public class UpdateCMDUBOCommand extends BaseCommand { + public boolean isBaseOffset; + public int x, y, z; + public int worldViewId; + + @Override + protected void doWrite() { + write1(isBaseOffset ? 1 : 0); + if(isBaseOffset) { + write32(x); + write32(y); + write32(z); + } else { + write32(worldViewId); + } + } + + @Override + protected void doRead() { + isBaseOffset = read1() == 1; + if(isBaseOffset) { + x = read32(); + y = read32(); + z = read32(); + } else { + worldViewId = read32(); + } + } + + @Override + public void execute() { + if(isBaseOffset) { + getUBOCommandBuffer().sceneBase.set(x, y, z); + } else { + getUBOCommandBuffer().worldViewIndex.set(worldViewId); + } + } + + @Override + public void print(StringBuilder sb) { + if(isBaseOffset) { + sb.append("UBOCommandBuffer::sceneBase = ("); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(", "); + sb.append(z); + sb.append(");"); + } else { + sb.append("UBOCommandBuffer::worldViewIndex = ("); + sb.append(worldViewId); + sb.append(");"); + } + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 3164a5656c..0b4af634b2 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; -import rs117.hd.utils.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 35fec691d5..67fe145a86 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -11,10 +11,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.scene.SceneContext; import rs117.hd.scene.materials.Material; import rs117.hd.utils.Camera; -import rs117.hd.utils.CommandBuffer; import static net.runelite.api.Perspective.*; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index b7120779dc..232c26da57 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -48,6 +48,8 @@ import rs117.hd.HdPluginConfig; import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -69,7 +71,6 @@ import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.Camera; import rs117.hd.utils.ColorUtils; -import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.HDUtils; import rs117.hd.utils.Mat4; import rs117.hd.utils.ModelHash; @@ -794,9 +795,9 @@ private void postDrawTopLevel() { glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); - CommandBuffer.SKIP_DEPTH_MASKING = true; + DepthMaskCommand.SKIP_DEPTH_MASKING = true; directionalCmd.execute(); - CommandBuffer.SKIP_DEPTH_MASKING = false; + DepthMaskCommand.SKIP_DEPTH_MASKING = false; glDisable(GL_DEPTH_TEST); diff --git a/src/main/java/rs117/hd/utils/CommandBuffer.java b/src/main/java/rs117/hd/utils/CommandBuffer.java deleted file mode 100644 index 03af06a3d7..0000000000 --- a/src/main/java/rs117/hd/utils/CommandBuffer.java +++ /dev/null @@ -1,240 +0,0 @@ -package rs117.hd.utils; - -import java.nio.IntBuffer; -import java.util.Arrays; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.lwjgl.system.MemoryStack; -import rs117.hd.opengl.uniforms.UBOCommandBuffer; - -import static org.lwjgl.opengl.GL33C.*; - -@Slf4j -public class CommandBuffer { - public static boolean SKIP_DEPTH_MASKING; - - private static final int GL_BIND_VERTEX_ARRAY_TYPE = 0; - private static final int GL_BIND_ELEMENTS_ARRAY_TYPE = 1; - private static final int GL_MULTI_DRAW_ARRAYS_TYPE = 2; - private static final int GL_DRAW_ARRAYS_TYPE = 3; - private static final int GL_DRAW_ELEMENTS_TYPE = 4; - - private static final int GL_DEPTH_MASK_TYPE = 5; - private static final int GL_COLOR_MASK_TYPE = 6; - - private static final int UNIFORM_BASE_OFFSET = 7; - private static final int UNIFORM_WORLD_VIEW_ID = 8; - - private static final int GL_TOGGLE_TYPE = 9; // Combined glEnable & glDisable - - private static final long INT_MASK = 0xFFFF_FFFFL; - - @Setter - private UBOCommandBuffer uboCommandBuffer; - - private long[] cmd = new long[1 << 20]; // ~1 million calls - private int writeHead = 0; - private int readHead = 0; - - private void ensureCapacity(int numLongs) { - if (writeHead + numLongs >= cmd.length) - cmd = Arrays.copyOf(cmd, cmd.length * 2); - } - - public void SetBaseOffset(int x, int y, int z) { - ensureCapacity(4); - cmd[writeHead++] = UNIFORM_BASE_OFFSET; - cmd[writeHead++] = x; - cmd[writeHead++] = y; - cmd[writeHead++] = z; - } - - public void SetWorldViewIndex(int index) { - ensureCapacity(2); - cmd[writeHead++] = UNIFORM_WORLD_VIEW_ID; - cmd[writeHead++] = index; - } - - public void BindVertexArray(int vao) { - ensureCapacity(2); - cmd[writeHead++] = GL_BIND_VERTEX_ARRAY_TYPE; - cmd[writeHead++] = vao; - } - - public void BindElementsArray(int ebo) { - ensureCapacity(2); - cmd[writeHead++] = GL_BIND_ELEMENTS_ARRAY_TYPE; - cmd[writeHead++] = ebo; - } - - public void DepthMask(boolean writeDepth) { - ensureCapacity(2); - cmd[writeHead++] = GL_DEPTH_MASK_TYPE; - cmd[writeHead++] = writeDepth ? 1 : 0; - } - - public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { - ensureCapacity(5); - cmd[writeHead++] = GL_COLOR_MASK_TYPE; - cmd[writeHead++] = writeRed ? 1 : 0; - cmd[writeHead++] = writeGreen ? 1 : 0; - cmd[writeHead++] = writeBlue ? 1 : 0; - cmd[writeHead++] = writeAlpha ? 1 : 0; - } - - public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { - assert offsets.length == counts.length; - - ensureCapacity(3 + (offsets.length * 2)); - cmd[writeHead++] = GL_MULTI_DRAW_ARRAYS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = offsets.length; - for (int i = 0; i < offsets.length; i++) { - cmd[writeHead++] = offsets[i]; - cmd[writeHead++] = counts[i]; - } - } - - public void DrawElements(int mode, int vertexCount, long offset) { - ensureCapacity(4); - cmd[writeHead++] = GL_DRAW_ELEMENTS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = vertexCount; - cmd[writeHead++] = offset; - } - - public void DrawArrays(int mode, int offset, int vertexCount) { - ensureCapacity(4); - cmd[writeHead++] = GL_DRAW_ARRAYS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = offset; - cmd[writeHead++] = vertexCount; - } - - public void Enable(int capability) { - Toggle(capability, true); - } - - public void Disable(int capability) { - Toggle(capability, false); - } - - public void Toggle(int capability, boolean enabled) { - ensureCapacity(2); - cmd[writeHead++] = GL_TOGGLE_TYPE; - cmd[writeHead++] = (enabled ? 1L : 0) << 32 | capability & INT_MASK; - } - - public void execute() { - try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer offsets = null, counts = null; - readHead = 0; - while (readHead < writeHead) { - int type = (int) cmd[readHead++]; - switch (type) { - case UNIFORM_BASE_OFFSET: { - int x = (int) cmd[readHead++]; - int y = (int) cmd[readHead++]; - int z = (int) cmd[readHead++]; - if (uboCommandBuffer != null) - uboCommandBuffer.sceneBase.set(x, y, z); - break; - } - case UNIFORM_WORLD_VIEW_ID: { - int id = (int) cmd[readHead++]; - if (uboCommandBuffer != null) - uboCommandBuffer.worldViewIndex.set(id); - break; - } - case GL_DEPTH_MASK_TYPE: { - int state = (int) cmd[readHead++]; - if (SKIP_DEPTH_MASKING) - continue; - glDepthMask(state == 1); - break; - } - case GL_COLOR_MASK_TYPE: { - int red = (int) cmd[readHead++]; - int green = (int) cmd[readHead++]; - int blue = (int) cmd[readHead++]; - int alpha = (int) cmd[readHead++]; - glColorMask(red == 1, green == 1, blue == 1, alpha == 1); - break; - } - case GL_BIND_VERTEX_ARRAY_TYPE: { - glBindVertexArray((int) cmd[readHead++]); - break; - } - case GL_BIND_ELEMENTS_ARRAY_TYPE: { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (int) cmd[readHead++]); - break; - } - case GL_DRAW_ARRAYS_TYPE: { - int mode = (int) cmd[readHead++]; - int offset = (int) cmd[readHead++]; - int count = (int) cmd[readHead++]; - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glDrawArrays(mode, offset, count); - break; - } - case GL_DRAW_ELEMENTS_TYPE: { - int mode = (int) cmd[readHead++]; - int vertexCount = (int) cmd[readHead++]; - long byteOffset = cmd[readHead++]; - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, byteOffset); - break; - } - case GL_MULTI_DRAW_ARRAYS_TYPE: { - int mode = (int) cmd[readHead++]; - int drawCount = (int) cmd[readHead++]; - - if (offsets == null || offsets.capacity() < drawCount) { - offsets = stack.callocInt(drawCount); - counts = stack.callocInt(drawCount); - } - - for (int i = 0; i < drawCount; i++) { - offsets.put((int) cmd[readHead++]); - counts.put((int) cmd[readHead++]); - } - - offsets.flip(); - counts.flip(); - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glMultiDrawArrays(mode, offsets, counts); - - offsets.clear(); - counts.clear(); - break; - } - case GL_TOGGLE_TYPE: { - long packed = cmd[readHead++]; - int capability = (int) (packed & INT_MASK); - if ((packed >> 32) != 0) { - glEnable(capability); - } else { - glDisable(capability); - } - break; - } - default: - throw new IllegalArgumentException("Encountered an unknown DrawCall type: " + type); - } - } - } - } - - public void reset() { - writeHead = 0; - } -} From 45265b9c256470d889277adfd752397b4f15c5a9 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:22:05 +0100 Subject: [PATCH 08/50] Fixes, Clean up & better validation --- src/main/java/rs117/hd/HdPlugin.java | 16 ++- .../hd/opengl/commandbuffer/BaseCommand.java | 133 ++++++++++++++---- .../opengl/commandbuffer/CommandBuffer.java | 54 +++++-- .../commands/MultiDrawArraysCommand.java | 19 +-- 4 files changed, 171 insertions(+), 51 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 3a677604fa..20b237ad98 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -1826,14 +1826,17 @@ public static void clearGLErrors() { // @formatter:on } - public static void checkGLErrors() { + public static boolean checkGLErrors() { return checkGLErrors(null); } + + public static boolean checkGLErrors(String context) { if (SKIP_GL_ERROR_CHECKS) - return; + return false; + boolean hasError = false; while (true) { int err = glGetError(); if (err == GL_NO_ERROR) - return; + return hasError; String errStr; switch (err) { @@ -1860,7 +1863,12 @@ public static void checkGLErrors() { break; } - log.debug("glGetError:", new Exception(errStr)); + if(context != null) { + log.debug("{} - glGetError:", context, new Exception(errStr)); + } else { + log.debug("glGetError:", new Exception(errStr)); + } + hasError = true; } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index 9950244097..ea806c9bab 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -1,26 +1,36 @@ package rs117.hd.opengl.commandbuffer; +import java.nio.BufferUnderflowException; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import rs117.hd.opengl.uniforms.UBOCommandBuffer; +@Slf4j public abstract class BaseCommand { protected int id; + + @Getter + private final String name; + @Getter private final boolean isDrawCall; protected int bitHead; protected int wordIndex; protected CommandBuffer buffer; - - protected BaseCommand() { - this.isDrawCall = false; - } + protected final StringBuilder sb = new StringBuilder(); + protected boolean validate = true; protected BaseCommand(boolean isDrawCall) { + this.name = getClass().getSimpleName(); this.isDrawCall = isDrawCall; } - protected UBOCommandBuffer getUBOCommandBuffer() { return buffer.uboCommandBuffer; } + protected BaseCommand() { this(false); } + + protected UBOCommandBuffer getUBOCommandBuffer() { + return buffer.uboCommandBuffer; + } public abstract void execute(); public abstract void print(StringBuilder sb); @@ -28,18 +38,34 @@ protected BaseCommand(boolean isDrawCall) { public final void write(CommandBuffer buffer) { this.buffer = buffer; - buffer.ensureCapacity(1); - buffer.cmd[buffer.writeHead++] = 0; + buffer.ensureCapacity(buffer.writeHead + 1); + buffer.cmd[buffer.writeHead] = 0; - this.wordIndex = buffer.writeHead - 1; + this.wordIndex = buffer.writeHead; this.bitHead = 0; + if(validate) { + sb.setLength(0); + sb.append(name); + sb.append("::write() wordIndex: "); + sb.append(wordIndex); + sb.append(" START\n"); + } + write8(id); doWrite(); - // Advance writeHead if needed - if (bitHead > 0) + if (bitHead > 0) { + bitHead = 0; wordIndex++; + } + + if(validate) { + sb.append(name); + sb.append("::write() wordIndex: "); + sb.append(wordIndex); + sb.append(" END\n"); + } buffer.writeHead = wordIndex; } @@ -48,14 +74,31 @@ public final void write(CommandBuffer buffer) { public final void read(CommandBuffer buffer) { this.buffer = buffer; + this.wordIndex = buffer.readHead; this.bitHead = 0; - read8(); // Skip over the command id + if(validate) { + sb.append(name); + sb.append("::read() wordIndex: "); + sb.append(wordIndex); + sb.append(" START\n"); + } + + assert read8() == id; doRead(); - if (bitHead > 0) + if (bitHead > 0) { + bitHead = 0; wordIndex++; + } + + if(validate) { + sb.append(name); + sb.append("::read() wordIndex: "); + sb.append(wordIndex); + sb.append(" END\n"); + } buffer.readHead = wordIndex; } @@ -64,19 +107,35 @@ public final void read(CommandBuffer buffer) { protected void writeBits(long value, int numBits) { while (numBits > 0) { - buffer.ensureCapacity(wordIndex + 1); int bitsLeftInWord = 64 - bitHead; int bitsToWrite = Math.min(bitsLeftInWord, numBits); - long mask = (1L << bitsToWrite) - 1; + long mask = bitsToWrite == 64 ? ~0L : (1L << bitsToWrite) - 1; long bits = value & mask; + if(validate) { + sb.append(name); + sb.append("::writeBits("); + sb.append(value); + sb.append(", "); + sb.append(numBits); + sb.append(") wordIndex: "); + sb.append(wordIndex); + sb.append(" bitHead: "); + sb.append(bitHead); + sb.append(" bitsToWrite: "); + sb.append(bitsToWrite); + sb.append("\n"); + } + buffer.cmd[wordIndex] |= bits << bitHead; bitHead += bitsToWrite; if (bitHead == 64) { bitHead = 0; wordIndex++; + buffer.ensureCapacity(wordIndex); + buffer.cmd[wordIndex] = 0; } value >>>= bitsToWrite; @@ -84,22 +143,38 @@ protected void writeBits(long value, int numBits) { } } - protected void write1(int value) { writeBits(value, 1); } - protected void write8(int value) { writeBits(value, 8); } - protected void write16(int value) { writeBits(value, 16); } - protected void write32(int value) { writeBits(value, 32); } - protected void write64(long value){ writeBits(value, 64); } + protected void write1(int value) { writeBits(value, 1); } + protected void write8(int value) { writeBits(value, 8); } + protected void write16(int value) { writeBits(value, 16); } + protected void write32(int value) { writeBits(value, 32); } + protected void write64(long value) { writeBits(value, 64); } protected long readBits(int numBits) { long result = 0; int shift = 0; while (numBits > 0) { + if (wordIndex >= buffer.cmd.length) + throw new BufferUnderflowException(); + long word = buffer.cmd[wordIndex]; int bitsLeftInWord = 64 - bitHead; int bitsToRead = Math.min(bitsLeftInWord, numBits); - long mask = (1L << bitsToRead) - 1; + if(validate) { + sb.append(name); + sb.append("::readBits("); + sb.append(numBits); + sb.append(") wordIndex: "); + sb.append(wordIndex); + sb.append(" bitHead: "); + sb.append(bitHead); + sb.append(" bitsToWrite: "); + sb.append(bitsToRead); + sb.append("\n"); + } + + long mask = bitsToRead == 64 ? ~0L : (1L << bitsToRead) - 1; long bits = (word >>> bitHead) & mask; result |= bits << shift; @@ -113,12 +188,20 @@ protected long readBits(int numBits) { shift += bitsToRead; numBits -= bitsToRead; } + + if(validate) { + sb.append(name); + sb.append("::readBits() Result: "); + sb.append(result); + sb.append("\n"); + } + return result; } - protected int read1() { return (int) readBits(1); } - protected int read8() { return (int) readBits(8); } - protected int read16() { return (int) readBits(16); } - protected int read32() { return (int) readBits(32); } - protected long read64() { return readBits(64); } + protected int read1() { return (int) readBits(1); } + protected int read8() { return (int) readBits(8); } + protected int read16() { return (int) readBits(16); } + protected int read32() { return (int) readBits(32); } + protected long read64() { return readBits(64); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index ec4a563a67..f66e062668 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -4,6 +4,7 @@ import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; @@ -17,6 +18,8 @@ @Slf4j public class CommandBuffer { + private static final StringBuilder SB = new StringBuilder(); + private static int COMMAND_COUNT = 0; private static final BaseCommand[] REGISTERED_COMMANDS = { (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), @@ -72,10 +75,11 @@ public void SetBaseOffset(int x, int y, int z) { if(validate) { UPDATE_CMD_UBO_COMMAND.read(this); - assert UPDATE_CMD_UBO_COMMAND.isBaseOffset && + assert readHead == writeHead && + UPDATE_CMD_UBO_COMMAND.isBaseOffset && UPDATE_CMD_UBO_COMMAND.x == x && UPDATE_CMD_UBO_COMMAND.y == y && - UPDATE_CMD_UBO_COMMAND.z == z; + UPDATE_CMD_UBO_COMMAND.z == z : UPDATE_CMD_UBO_COMMAND.sb.toString(); } } @@ -86,7 +90,8 @@ public void SetWorldViewIndex(int index) { if(validate) { UPDATE_CMD_UBO_COMMAND.read(this); - assert UPDATE_CMD_UBO_COMMAND.worldViewId == index; + assert readHead == writeHead && + UPDATE_CMD_UBO_COMMAND.worldViewId == index : UPDATE_CMD_UBO_COMMAND.sb.toString(); } } @@ -96,7 +101,8 @@ public void BindVertexArray(int vao) { if(validate) { BIND_VERTEX_ARRAY_COMMAND.read(this); - assert BIND_VERTEX_ARRAY_COMMAND.vao == vao; + assert readHead == writeHead && + BIND_VERTEX_ARRAY_COMMAND.vao == vao : BIND_VERTEX_ARRAY_COMMAND.sb.toString(); } } @@ -106,7 +112,8 @@ public void BindElementsArray(int ebo) { if(validate) { BIND_ELEMENTS_ARRAY_COMMAND.read(this); - assert BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo; + assert readHead == writeHead && + BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo : BIND_ELEMENTS_ARRAY_COMMAND.sb.toString(); } } @@ -116,7 +123,8 @@ public void DepthMask(boolean writeDepth) { if(validate) { DEPTH_MASK_COMMAND.read(this); - assert DEPTH_MASK_COMMAND.flag == writeDepth; + assert readHead == writeHead && + DEPTH_MASK_COMMAND.flag == writeDepth : DEPTH_MASK_COMMAND.sb.toString(); } } @@ -125,13 +133,15 @@ public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, b COLOR_MASK_COMMAND.green = writeGreen; COLOR_MASK_COMMAND.blue = writeBlue; COLOR_MASK_COMMAND.alpha = writeAlpha; + COLOR_MASK_COMMAND.write(this); if(validate) { COLOR_MASK_COMMAND.read(this); - assert COLOR_MASK_COMMAND.red == writeRed && + assert readHead == writeHead && + COLOR_MASK_COMMAND.red == writeRed && COLOR_MASK_COMMAND.green == writeGreen && COLOR_MASK_COMMAND.blue == writeBlue && - COLOR_MASK_COMMAND.alpha == writeAlpha; + COLOR_MASK_COMMAND.alpha == writeAlpha : COLOR_MASK_COMMAND.sb.toString(); } } @@ -140,6 +150,15 @@ public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; MULTI_DRAW_ARRAYS_COMMAND.counts = counts; MULTI_DRAW_ARRAYS_COMMAND.write(this); + + if(validate) { + MULTI_DRAW_ARRAYS_COMMAND.read(this); + assert readHead == writeHead && MULTI_DRAW_ARRAYS_COMMAND.mode == mode : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); + for(int i = 0; i < offsets.length; i++) { + assert offsets[i] == MULTI_DRAW_ARRAYS_COMMAND.offsetsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); + assert counts[i] == MULTI_DRAW_ARRAYS_COMMAND.countsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); + } + } } public void DrawElements(int mode, int vertexCount, long offset) { @@ -150,9 +169,10 @@ public void DrawElements(int mode, int vertexCount, long offset) { if(validate) { DRAW_ELEMENTS_COMMAND.read(this); - assert DRAW_ELEMENTS_COMMAND.mode == mode && + assert readHead == writeHead && + DRAW_ELEMENTS_COMMAND.mode == mode && DRAW_ELEMENTS_COMMAND.vertexCount == vertexCount && - DRAW_ELEMENTS_COMMAND.offset == offset; + DRAW_ELEMENTS_COMMAND.offset == offset : DRAW_ELEMENTS_COMMAND.sb.toString(); } } @@ -164,9 +184,10 @@ public void DrawArrays(int mode, int offset, int vertexCount) { if(validate) { DRAW_ARRAYS_COMMAND.read(this); - assert DRAW_ARRAYS_COMMAND.mode == mode && + assert readHead == writeHead && + DRAW_ARRAYS_COMMAND.mode == mode && DRAW_ARRAYS_COMMAND.offset == offset && - DRAW_ARRAYS_COMMAND.vertexCount == vertexCount; + DRAW_ARRAYS_COMMAND.vertexCount == vertexCount : DRAW_ARRAYS_COMMAND.sb.toString(); } } @@ -186,7 +207,7 @@ public void Toggle(int capability, boolean enabled) { if(validate) { TOGGLE_COMMAND.read(this); assert TOGGLE_COMMAND.capability == capability && - TOGGLE_COMMAND.state == enabled; + TOGGLE_COMMAND.state == enabled : TOGGLE_COMMAND.sb.toString(); } } @@ -203,6 +224,13 @@ public void execute() { } command.read(this); command.execute(); + + if(HdPlugin.checkGLErrors(command.getName())) { + command.print(SB); + log.warn("Encountered Error whilst processing command:\n{}", SB.toString()); + SB.setLength(0); + break; + } } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index 61019cd705..37434ca292 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -11,8 +11,8 @@ public class MultiDrawArraysCommand extends BaseCommand { public int[] offsets; public int[] counts; - private IntBuffer offsetsBuffer; - private IntBuffer countsBuffer; + public IntBuffer offsetsBuffer; + public IntBuffer countsBuffer; public MultiDrawArraysCommand() { super(true); } @@ -23,6 +23,7 @@ public void doWrite() { write16(mode); write32(offsets.length); for(int i = 0; i < offsets.length; i++) { + assert offsets[i] >= 0 && counts[i] >= 0; write32(offsets[i]); write32(counts[i]); } @@ -35,19 +36,19 @@ public void doWrite() { public void doRead() { mode = read16(); - int count = read32(); - if(offsetsBuffer == null || offsetsBuffer.capacity() < count) { + int length = read32(); + if(offsetsBuffer == null || offsetsBuffer.capacity() < length) { if(offsetsBuffer != null) MemoryUtil.memFree(offsetsBuffer); if(countsBuffer != null) MemoryUtil.memFree(countsBuffer); - offsetsBuffer = MemoryUtil.memAllocInt(count * 2); - countsBuffer = MemoryUtil.memAllocInt(count * 2); + offsetsBuffer = MemoryUtil.memAllocInt(length * 2); + countsBuffer = MemoryUtil.memAllocInt(length * 2); } else { offsetsBuffer.clear(); countsBuffer.clear(); } - for(int i = 0; i < count; i++) { + for(int i = 0; i < length; i++) { offsetsBuffer.put(read32()); countsBuffer.put(read32()); } @@ -70,11 +71,11 @@ public void print(StringBuilder sb) { if(i > 0) sb.append(", "); sb.append(offsetsBuffer.get(i)); } - sb.append("], "); + sb.append("], ["); for(int i = 0; i < countsBuffer.limit(); i++) { if(i > 0) sb.append(", "); sb.append(countsBuffer.get(i)); } - sb.append(");"); + sb.append("]);"); } } \ No newline at end of file From ae3fe7c3fb4441d80b52386b6c11d1a482f4238c Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:27:19 +0100 Subject: [PATCH 09/50] Disable Validation by default --- src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java | 2 +- src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index ea806c9bab..33755f4782 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -19,7 +19,7 @@ public abstract class BaseCommand { protected int wordIndex; protected CommandBuffer buffer; protected final StringBuilder sb = new StringBuilder(); - protected boolean validate = true; + protected boolean validate = false; protected BaseCommand(boolean isDrawCall) { this.name = getClass().getSimpleName(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index f66e062668..57a361da50 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -59,7 +59,7 @@ private static T REGISTER_COMMAND(ICreateCommand crea protected int writeHead = 0; protected int readHead = 0; - private final boolean validate = true; + private final boolean validate = false; public void ensureCapacity(int numLongs) { if (writeHead + numLongs >= cmd.length) From b86505d70f35bd237ef27589f4a5f1a6ae9d0614 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:27:27 +0100 Subject: [PATCH 10/50] Optimizations, Tighter Packing, Validation improvements & Fixes :) --- .../hd/opengl/commandbuffer/BaseCommand.java | 170 ++---------- .../opengl/commandbuffer/CommandBuffer.java | 243 +++++++++++------- .../commands/BindElementsArrayCommand.java | 2 +- .../commands/BindVertexArrayCommand.java | 2 +- .../commands/ColorMaskCommand.java | 19 +- .../commands/DepthMaskCommand.java | 6 +- .../commands/DrawArraysCommand.java | 15 +- .../commands/DrawElementsCommand.java | 20 +- .../commands/MultiDrawArraysCommand.java | 6 +- .../commandbuffer/commands/ToggleCommand.java | 11 +- .../commands/UpdateCMDUBOCommand.java | 6 +- src/main/java/rs117/hd/overlays/Timer.java | 1 + .../rs117/hd/renderer/zone/ZoneRenderer.java | 8 +- 13 files changed, 231 insertions(+), 278 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index 33755f4782..8e7c1be669 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -1,12 +1,12 @@ package rs117.hd.opengl.commandbuffer; -import java.nio.BufferUnderflowException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import rs117.hd.opengl.uniforms.UBOCommandBuffer; @Slf4j public abstract class BaseCommand { + protected int id; @Getter @@ -15,11 +15,7 @@ public abstract class BaseCommand { @Getter private final boolean isDrawCall; - protected int bitHead; - protected int wordIndex; protected CommandBuffer buffer; - protected final StringBuilder sb = new StringBuilder(); - protected boolean validate = false; protected BaseCommand(boolean isDrawCall) { this.name = getClass().getSimpleName(); @@ -38,36 +34,13 @@ protected UBOCommandBuffer getUBOCommandBuffer() { public final void write(CommandBuffer buffer) { this.buffer = buffer; - buffer.ensureCapacity(buffer.writeHead + 1); - buffer.cmd[buffer.writeHead] = 0; - - this.wordIndex = buffer.writeHead; - this.bitHead = 0; - - if(validate) { - sb.setLength(0); - sb.append(name); - sb.append("::write() wordIndex: "); - sb.append(wordIndex); - sb.append(" START\n"); + if(CommandBuffer.VALIDATE) { + buffer.appendToValidationLog(name); + buffer.appendToValidationLog("::write\n"); } write8(id); doWrite(); - - if (bitHead > 0) { - bitHead = 0; - wordIndex++; - } - - if(validate) { - sb.append(name); - sb.append("::write() wordIndex: "); - sb.append(wordIndex); - sb.append(" END\n"); - } - - buffer.writeHead = wordIndex; } protected abstract void doWrite(); @@ -75,133 +48,28 @@ public final void write(CommandBuffer buffer) { public final void read(CommandBuffer buffer) { this.buffer = buffer; - this.wordIndex = buffer.readHead; - this.bitHead = 0; - - if(validate) { - sb.append(name); - sb.append("::read() wordIndex: "); - sb.append(wordIndex); - sb.append(" START\n"); + if(CommandBuffer.VALIDATE) { + buffer.appendToValidationLog(name); + buffer.appendToValidationLog("::read\n"); } - assert read8() == id; doRead(); - - if (bitHead > 0) { - bitHead = 0; - wordIndex++; - } - - if(validate) { - sb.append(name); - sb.append("::read() wordIndex: "); - sb.append(wordIndex); - sb.append(" END\n"); - } - - buffer.readHead = wordIndex; } protected abstract void doRead(); - protected void writeBits(long value, int numBits) { - while (numBits > 0) { - int bitsLeftInWord = 64 - bitHead; - int bitsToWrite = Math.min(bitsLeftInWord, numBits); - - long mask = bitsToWrite == 64 ? ~0L : (1L << bitsToWrite) - 1; - long bits = value & mask; - - if(validate) { - sb.append(name); - sb.append("::writeBits("); - sb.append(value); - sb.append(", "); - sb.append(numBits); - sb.append(") wordIndex: "); - sb.append(wordIndex); - sb.append(" bitHead: "); - sb.append(bitHead); - sb.append(" bitsToWrite: "); - sb.append(bitsToWrite); - sb.append("\n"); - } - - buffer.cmd[wordIndex] |= bits << bitHead; - - bitHead += bitsToWrite; - if (bitHead == 64) { - bitHead = 0; - wordIndex++; - buffer.ensureCapacity(wordIndex); - buffer.cmd[wordIndex] = 0; - } - - value >>>= bitsToWrite; - numBits -= bitsToWrite; - } - } - - protected void write1(int value) { writeBits(value, 1); } - protected void write8(int value) { writeBits(value, 8); } - protected void write16(int value) { writeBits(value, 16); } - protected void write32(int value) { writeBits(value, 32); } - protected void write64(long value) { writeBits(value, 64); } - - protected long readBits(int numBits) { - long result = 0; - int shift = 0; - - while (numBits > 0) { - if (wordIndex >= buffer.cmd.length) - throw new BufferUnderflowException(); - - long word = buffer.cmd[wordIndex]; - int bitsLeftInWord = 64 - bitHead; - int bitsToRead = Math.min(bitsLeftInWord, numBits); - - if(validate) { - sb.append(name); - sb.append("::readBits("); - sb.append(numBits); - sb.append(") wordIndex: "); - sb.append(wordIndex); - sb.append(" bitHead: "); - sb.append(bitHead); - sb.append(" bitsToWrite: "); - sb.append(bitsToRead); - sb.append("\n"); - } - - long mask = bitsToRead == 64 ? ~0L : (1L << bitsToRead) - 1; - long bits = (word >>> bitHead) & mask; - - result |= bits << shift; - - bitHead += bitsToRead; - if (bitHead == 64) { - bitHead = 0; - wordIndex++; - } - - shift += bitsToRead; - numBits -= bitsToRead; - } + protected final void write1(int value) { buffer.writeBits(value & 0x1L, 1); } + protected final void write8(int value) { buffer.writeBits(value & 0xFFL, 8); } + protected final void write16(int value) { buffer.writeBits(value & 0xFFFFL, 16); } + protected final void write32(int value) { buffer.writeBits(value & 0xFFFFFFFFL, 32); } + protected final void write64(long value) { buffer.writeBits(value, 64); } - if(validate) { - sb.append(name); - sb.append("::readBits() Result: "); - sb.append(result); - sb.append("\n"); - } - - return result; - } + protected final int read1() { return (int) buffer.readBits(1); } + protected final int read8() { return (int) buffer.readBits(8); } + protected final int read16() { return (int) buffer.readBits(16); } + protected final int read32() { return (int) buffer.readBits(32); } + protected final long read64() { return buffer.readBits(64); } - protected int read1() { return (int) readBits(1); } - protected int read8() { return (int) readBits(8); } - protected int read16() { return (int) readBits(16); } - protected int read32() { return (int) readBits(32); } - protected long read64() { return readBits(64); } + protected final void writeFlag(boolean value) { write1(value ? 1 : 0); } + protected final boolean readFlag() { return read1() != 0; } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 57a361da50..aee12be8dc 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -1,7 +1,7 @@ package rs117.hd.opengl.commandbuffer; +import java.nio.BufferUnderflowException; import java.util.Arrays; -import lombok.Setter; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import rs117.hd.HdPlugin; @@ -17,8 +17,10 @@ import rs117.hd.opengl.uniforms.UBOCommandBuffer; @Slf4j -public class CommandBuffer { - private static final StringBuilder SB = new StringBuilder(); +public final class CommandBuffer { + public static boolean VALIDATE = false; + + private static final int BITS_PER_WORD = 64; private static int COMMAND_COUNT = 0; private static final BaseCommand[] REGISTERED_COMMANDS = { @@ -52,14 +54,16 @@ private static T REGISTER_COMMAND(ICreateCommand crea return newCommand; } - @Setter - protected UBOCommandBuffer uboCommandBuffer; + public UBOCommandBuffer uboCommandBuffer; + + private long[] cmd = new long[1 << 20]; // ~1 million calls + private int writeHead = 0; + private int writeBitHead = 0; - protected long[] cmd = new long[1 << 20]; // ~1 million calls - protected int writeHead = 0; - protected int readHead = 0; + private int readHead = 0; + private int readBitHead = 0; - private final boolean validate = false; + private final StringBuilder validation_log = new StringBuilder(); public void ensureCapacity(int numLongs) { if (writeHead + numLongs >= cmd.length) @@ -72,60 +76,27 @@ public void SetBaseOffset(int x, int y, int z) { UPDATE_CMD_UBO_COMMAND.y = y; UPDATE_CMD_UBO_COMMAND.z = z; UPDATE_CMD_UBO_COMMAND.write(this); - - if(validate) { - UPDATE_CMD_UBO_COMMAND.read(this); - assert readHead == writeHead && - UPDATE_CMD_UBO_COMMAND.isBaseOffset && - UPDATE_CMD_UBO_COMMAND.x == x && - UPDATE_CMD_UBO_COMMAND.y == y && - UPDATE_CMD_UBO_COMMAND.z == z : UPDATE_CMD_UBO_COMMAND.sb.toString(); - } } public void SetWorldViewIndex(int index) { UPDATE_CMD_UBO_COMMAND.isBaseOffset = false; UPDATE_CMD_UBO_COMMAND.worldViewId = index; UPDATE_CMD_UBO_COMMAND.write(this); - - if(validate) { - UPDATE_CMD_UBO_COMMAND.read(this); - assert readHead == writeHead && - UPDATE_CMD_UBO_COMMAND.worldViewId == index : UPDATE_CMD_UBO_COMMAND.sb.toString(); - } } public void BindVertexArray(int vao) { BIND_VERTEX_ARRAY_COMMAND.vao = vao; BIND_VERTEX_ARRAY_COMMAND.write(this); - - if(validate) { - BIND_VERTEX_ARRAY_COMMAND.read(this); - assert readHead == writeHead && - BIND_VERTEX_ARRAY_COMMAND.vao == vao : BIND_VERTEX_ARRAY_COMMAND.sb.toString(); - } } public void BindElementsArray(int ebo) { BIND_ELEMENTS_ARRAY_COMMAND.ebo = ebo; BIND_ELEMENTS_ARRAY_COMMAND.write(this); - - if(validate) { - BIND_ELEMENTS_ARRAY_COMMAND.read(this); - assert readHead == writeHead && - BIND_ELEMENTS_ARRAY_COMMAND.ebo == ebo : BIND_ELEMENTS_ARRAY_COMMAND.sb.toString(); - } } public void DepthMask(boolean writeDepth) { DEPTH_MASK_COMMAND.flag = writeDepth; DEPTH_MASK_COMMAND.write(this); - - if(validate) { - DEPTH_MASK_COMMAND.read(this); - assert readHead == writeHead && - DEPTH_MASK_COMMAND.flag == writeDepth : DEPTH_MASK_COMMAND.sb.toString(); - } } public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { @@ -134,15 +105,6 @@ public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, b COLOR_MASK_COMMAND.blue = writeBlue; COLOR_MASK_COMMAND.alpha = writeAlpha; COLOR_MASK_COMMAND.write(this); - - if(validate) { - COLOR_MASK_COMMAND.read(this); - assert readHead == writeHead && - COLOR_MASK_COMMAND.red == writeRed && - COLOR_MASK_COMMAND.green == writeGreen && - COLOR_MASK_COMMAND.blue == writeBlue && - COLOR_MASK_COMMAND.alpha == writeAlpha : COLOR_MASK_COMMAND.sb.toString(); - } } public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { @@ -150,30 +112,13 @@ public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; MULTI_DRAW_ARRAYS_COMMAND.counts = counts; MULTI_DRAW_ARRAYS_COMMAND.write(this); - - if(validate) { - MULTI_DRAW_ARRAYS_COMMAND.read(this); - assert readHead == writeHead && MULTI_DRAW_ARRAYS_COMMAND.mode == mode : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); - for(int i = 0; i < offsets.length; i++) { - assert offsets[i] == MULTI_DRAW_ARRAYS_COMMAND.offsetsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); - assert counts[i] == MULTI_DRAW_ARRAYS_COMMAND.countsBuffer.get(i) : MULTI_DRAW_ARRAYS_COMMAND.sb.toString(); - } - } } - public void DrawElements(int mode, int vertexCount, long offset) { + public void DrawElements(int mode, int vertexCount, long bytesOffset) { DRAW_ELEMENTS_COMMAND.mode = mode; DRAW_ELEMENTS_COMMAND.vertexCount = vertexCount; - DRAW_ELEMENTS_COMMAND.offset = offset; + DRAW_ELEMENTS_COMMAND.bytesOffset = bytesOffset; DRAW_ELEMENTS_COMMAND.write(this); - - if(validate) { - DRAW_ELEMENTS_COMMAND.read(this); - assert readHead == writeHead && - DRAW_ELEMENTS_COMMAND.mode == mode && - DRAW_ELEMENTS_COMMAND.vertexCount == vertexCount && - DRAW_ELEMENTS_COMMAND.offset == offset : DRAW_ELEMENTS_COMMAND.sb.toString(); - } } public void DrawArrays(int mode, int offset, int vertexCount) { @@ -181,14 +126,6 @@ public void DrawArrays(int mode, int offset, int vertexCount) { DRAW_ARRAYS_COMMAND.offset = offset; DRAW_ARRAYS_COMMAND.vertexCount = vertexCount; DRAW_ARRAYS_COMMAND.write(this); - - if(validate) { - DRAW_ARRAYS_COMMAND.read(this); - assert readHead == writeHead && - DRAW_ARRAYS_COMMAND.mode == mode && - DRAW_ARRAYS_COMMAND.offset == offset && - DRAW_ARRAYS_COMMAND.vertexCount == vertexCount : DRAW_ARRAYS_COMMAND.sb.toString(); - } } public void Enable(int capability) { @@ -203,39 +140,165 @@ public void Toggle(int capability, boolean enabled) { TOGGLE_COMMAND.capability = capability; TOGGLE_COMMAND.state = enabled; TOGGLE_COMMAND.write(this); - - if(validate) { - TOGGLE_COMMAND.read(this); - assert TOGGLE_COMMAND.capability == capability && - TOGGLE_COMMAND.state == enabled : TOGGLE_COMMAND.sb.toString(); - } } @SneakyThrows public void execute() { readHead = 0; - while (readHead < writeHead) { - int type = (int) (cmd[readHead] & 0xFF); - assert type < REGISTERED_COMMANDS.length; + readBitHead = 0; + if(VALIDATE) validation_log.setLength(0); + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + int type = (int)readBits(8); + assert type < REGISTERED_COMMANDS.length : validation_log.toString(); BaseCommand command = REGISTERED_COMMANDS[type]; if (command.isDrawCall() && uboCommandBuffer != null && uboCommandBuffer.isDirty()) { uboCommandBuffer.upload(); } + command.read(this); + + command.print(validation_log); + validation_log.append("\n"); + command.execute(); if(HdPlugin.checkGLErrors(command.getName())) { - command.print(SB); - log.warn("Encountered Error whilst processing command:\n{}", SB.toString()); - SB.setLength(0); + log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", validation_log); + validation_log.setLength(0); break; } } } + protected long readBits(int numBits) { + long result = 0; + int shift = 0; + + if (readHead >= cmd.length) + throw new BufferUnderflowException(); + + while (numBits > 0) { + long word = cmd[readHead]; + int bitsLeftInWord = BITS_PER_WORD - readBitHead; + int bitsToRead = Math.min(bitsLeftInWord, numBits); + + if(VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); + + long mask = bitsToRead == BITS_PER_WORD ? ~0L : (1L << bitsToRead) - 1; + long bits = (word >>> readBitHead) & mask; + + result |= bits << shift; + + readBitHead += bitsToRead; + if (readBitHead == BITS_PER_WORD) { + readBitHead = 0; + readHead++; + } + + shift += bitsToRead; + numBits -= bitsToRead; + } + + if(VALIDATE) logReadResult(result); + + int remainder = readBitHead & 7; + if (remainder != 0) readBits(8 - remainder); + + return result; + } + + protected void writeBits(long value, int numBits) { + long originalValue = value; + int originalNumBits = numBits; + int originalWriteHead = writeHead; + int originalWriteBitHead = writeBitHead; + + while (numBits > 0) { + int bitsLeftInWord = BITS_PER_WORD - writeBitHead; + int bitsToWrite = Math.min(bitsLeftInWord, numBits); + + long valueMask = bitsToWrite == BITS_PER_WORD ? ~0L : (1L << bitsToWrite) - 1L; + long bits = value & valueMask; + + if(VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); + + long destMask = bitsToWrite == BITS_PER_WORD ? ~0L : valueMask << writeBitHead; + cmd[writeHead] = (cmd[writeHead] & ~destMask) | ((bits << writeBitHead) & destMask); + + writeBitHead += bitsToWrite; + if (writeBitHead == BITS_PER_WORD) { + writeBitHead = 0; + writeHead++; + ensureCapacity(writeHead); + cmd[writeHead] = 0; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } + + if(VALIDATE) { + int originalReadHead = readHead; + int originalReadBitHead = readBitHead; + + readHead = originalWriteHead; + readBitHead = originalWriteBitHead; + + long decoded = readBits(originalNumBits); + assert decoded == originalValue : "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; + + readHead = originalReadHead; + readBitHead = originalReadBitHead; + } + + int remainder = writeBitHead & 7; + if (remainder != 0) writeBits(0, 8 - remainder); + } + + protected void appendToValidationLog(String str) { + validation_log.append(str); + } + + private void logWriteBits(long value, int numBits, int wordIndex, int bitHead, int bitsToWrite) { + validation_log.append("writeBits("); + validation_log.append(value); + validation_log.append(", "); + validation_log.append(numBits); + validation_log.append(") wordIndex: "); + validation_log.append(wordIndex); + validation_log.append(" bitHead: "); + validation_log.append(bitHead); + validation_log.append(" bitsToWrite: "); + validation_log.append(bitsToWrite); + validation_log.append("\n"); + } + + private void logReadBits(int numBits, int wordIndex, int bitHead, int bitsToRead) { + validation_log.append("readBits("); + validation_log.append(numBits); + validation_log.append(") wordIndex: "); + validation_log.append(wordIndex); + validation_log.append(" bitHead: "); + validation_log.append(bitHead); + validation_log.append(" bitsToRead: "); + validation_log.append(bitsToRead); + validation_log.append("\n"); + } + + private void logReadResult(long result) { + validation_log.append("readBits() Result: "); + validation_log.append(result); + validation_log.append("\n"); + } + public void reset() { writeHead = 0; + writeBitHead = 0; + readHead = 0; + readBitHead = 0; + + validation_log.setLength(0); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java index d6a7040afb..5d9a4a0e4e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -5,7 +5,7 @@ import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; import static org.lwjgl.opengl.GL15.glBindBuffer; -public class BindElementsArrayCommand extends BaseCommand { +public final class BindElementsArrayCommand extends BaseCommand { public int ebo; @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java index 470491c873..ecd7526239 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -4,7 +4,7 @@ import static org.lwjgl.opengl.GL30.glBindVertexArray; -public class BindVertexArrayCommand extends BaseCommand { +public final class BindVertexArrayCommand extends BaseCommand { public int vao; @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java index 65805e634a..4468884adc 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -4,27 +4,26 @@ import static org.lwjgl.opengl.GL11.glColorMask; -public class ColorMaskCommand extends BaseCommand { +public final class ColorMaskCommand extends BaseCommand { public boolean red; public boolean green; public boolean blue; public boolean alpha; - @Override protected void doWrite() { - write1(red ? 1 : 0); - write1(green ? 1 : 0); - write1(blue ? 1 : 0); - write1(alpha ? 1 : 0); + writeFlag(red); + writeFlag(green); + writeFlag(blue); + writeFlag(alpha); } @Override protected void doRead() { - red = read1() == 1; - green = read1() == 1; - blue = read1() == 1; - alpha = read1() == 1; + red = readFlag(); + green = readFlag(); + blue = readFlag(); + alpha = readFlag(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java index 3d3aa48567..45f2ff862c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -4,19 +4,19 @@ import static org.lwjgl.opengl.GL11.glDepthMask; -public class DepthMaskCommand extends BaseCommand { +public final class DepthMaskCommand extends BaseCommand { public static boolean SKIP_DEPTH_MASKING; public boolean flag; @Override protected void doWrite() { - write1(flag ? 1 : 0); + writeFlag(flag); } @Override protected void doRead() { - flag = read1() == 1; + flag = readFlag(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java index 17aebbce38..37d90a3baf 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -2,9 +2,9 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; -import static org.lwjgl.opengl.GL11.glDrawArrays; +import static org.lwjgl.opengl.GL11C.glDrawArrays; -public class DrawArraysCommand extends BaseCommand { +public final class DrawArraysCommand extends BaseCommand { public int mode; public int vertexCount; public int offset; @@ -13,9 +13,13 @@ public class DrawArraysCommand extends BaseCommand { @Override public void doWrite() { + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert offset >= 0 : "offset must be >= 0"; + write16(mode); write32(vertexCount); - write64(offset); + write32(offset); } @Override @@ -23,10 +27,15 @@ public void doRead() { mode = read16(); vertexCount = read32(); offset = read32(); + + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert offset >= 0 : "offset must be >= 0"; } @Override public void execute() { + glDrawArrays(mode, offset, vertexCount); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java index 22980ba5f0..8096ba5518 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -5,30 +5,38 @@ import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; import static org.lwjgl.opengl.GL11.glDrawElements; -public class DrawElementsCommand extends BaseCommand { +public final class DrawElementsCommand extends BaseCommand { public int mode; public int vertexCount; - public long offset; + public long bytesOffset; public DrawElementsCommand() { super(true); } @Override public void doWrite() { + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert bytesOffset >= 0 : "bytesOffset must be >= 0"; + write16(mode); write32(vertexCount); - write64(offset); + write64(bytesOffset); } @Override public void doRead() { mode = read16(); vertexCount = read32(); - offset = read64(); + bytesOffset = read64(); + + assert mode >= 0 : "mode must be >= 0"; + assert vertexCount >= 0 : "vertexCount must be >= 0"; + assert bytesOffset >= 0 : "bytesOffset must be >= 0"; } @Override public void execute() { - glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, offset); + glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, bytesOffset); } @Override @@ -38,7 +46,7 @@ public void print(StringBuilder sb) { sb.append(", "); sb.append(vertexCount); sb.append(", GL_UNSIGNED_INT, "); - sb.append(offset); + sb.append(bytesOffset); sb.append(");"); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index 37434ca292..002db9f493 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -6,7 +6,7 @@ import static org.lwjgl.opengl.GL14.glMultiDrawArrays; -public class MultiDrawArraysCommand extends BaseCommand { +public final class MultiDrawArraysCommand extends BaseCommand { public int mode; public int[] offsets; public int[] counts; @@ -23,7 +23,8 @@ public void doWrite() { write16(mode); write32(offsets.length); for(int i = 0; i < offsets.length; i++) { - assert offsets[i] >= 0 && counts[i] >= 0; + assert offsets[i] >= 0 : "offset must be >= 0"; + assert counts[i] >= 0 : "vertexCount must be >= 0"; write32(offsets[i]); write32(counts[i]); } @@ -59,6 +60,7 @@ public void doRead() { @Override public void execute() { + glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java index cdbe16a1b5..3051238355 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -2,24 +2,23 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; -import static org.lwjgl.opengl.GL11C.glDisable; -import static org.lwjgl.opengl.GL11C.glEnable; +import static org.lwjgl.opengl.GL11.glDisable; +import static org.lwjgl.opengl.GL11.glEnable; -public class ToggleCommand extends BaseCommand { +public final class ToggleCommand extends BaseCommand { public int capability; public boolean state; - @Override public void doWrite() { write32(capability); - write1(state ? 1 : 0); + writeFlag(state); } @Override public void doRead() { capability = read32(); - state = read1() == 1; + state = readFlag(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java index 3e6f9b4659..cced2a5369 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java @@ -2,14 +2,14 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; -public class UpdateCMDUBOCommand extends BaseCommand { +public final class UpdateCMDUBOCommand extends BaseCommand { public boolean isBaseOffset; public int x, y, z; public int worldViewId; @Override protected void doWrite() { - write1(isBaseOffset ? 1 : 0); + writeFlag(isBaseOffset); if(isBaseOffset) { write32(x); write32(y); @@ -21,7 +21,7 @@ protected void doWrite() { @Override protected void doRead() { - isBaseOffset = read1() == 1; + isBaseOffset = readFlag(); if(isBaseOffset) { x = read32(); y = read32(); diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 0decbc6535..f20c80e2dc 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,6 +17,7 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), + COMMAND_BUFFER_EXECUTE, UPDATE_ENVIRONMENT, UPDATE_LIGHTS, IMPOSTOR_TRACKING, diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 232c26da57..e185ddf16e 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -245,8 +245,8 @@ public void initialize() { uboCommandBuffer.initialize(UNIFORM_BLOCK_COMMAND_BUFFER); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); - sceneCmd.setUboCommandBuffer(uboCommandBuffer); - directionalCmd.setUboCommandBuffer(uboCommandBuffer); + sceneCmd.uboCommandBuffer = uboCommandBuffer; + directionalCmd.uboCommandBuffer = uboCommandBuffer; } @Override @@ -795,9 +795,11 @@ private void postDrawTopLevel() { glDepthFunc(GL_LEQUAL); glDisable(GL_CULL_FACE); + frameTimer.begin(Timer.COMMAND_BUFFER_EXECUTE); DepthMaskCommand.SKIP_DEPTH_MASKING = true; directionalCmd.execute(); DepthMaskCommand.SKIP_DEPTH_MASKING = false; + frameTimer.end(Timer.COMMAND_BUFFER_EXECUTE); glDisable(GL_DEPTH_TEST); @@ -838,7 +840,9 @@ private void postDrawTopLevel() { glDepthFunc(GL_GREATER); // Render the scene + frameTimer.begin(Timer.COMMAND_BUFFER_EXECUTE); sceneCmd.execute(); + frameTimer.end(Timer.COMMAND_BUFFER_EXECUTE); // TODO: Filler tiles From cbc1024d77e269bf1d35811397753756c0675336 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 00:25:58 +0100 Subject: [PATCH 11/50] Removed old workaround --- .../java/rs117/hd/opengl/commandbuffer/CommandBuffer.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index aee12be8dc..80e71aa8a0 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -202,9 +202,6 @@ protected long readBits(int numBits) { if(VALIDATE) logReadResult(result); - int remainder = readBitHead & 7; - if (remainder != 0) readBits(8 - remainder); - return result; } @@ -251,9 +248,6 @@ protected void writeBits(long value, int numBits) { readHead = originalReadHead; readBitHead = originalReadBitHead; } - - int remainder = writeBitHead & 7; - if (remainder != 0) writeBits(0, 8 - remainder); } protected void appendToValidationLog(String str) { From b262b4f15c825282b814b9c254a8801c7f8a129d Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:48:23 +0100 Subject: [PATCH 12/50] Optimisations --- .../opengl/commandbuffer/CommandBuffer.java | 154 +++++++++++------- 1 file changed, 99 insertions(+), 55 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 80e71aa8a0..b5cd9bb049 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -21,6 +21,15 @@ public final class CommandBuffer { public static boolean VALIDATE = false; private static final int BITS_PER_WORD = 64; + private static final long[] MASKS = new long[65]; + + static { + MASKS[0] = 0L; + for (int i = 1; i < BITS_PER_WORD; i++) { + MASKS[i] = (1L << i) - 1L; + } + MASKS[BITS_PER_WORD] = ~0L; + } private static int COMMAND_COUNT = 0; private static final BaseCommand[] REGISTERED_COMMANDS = { @@ -63,6 +72,7 @@ private static T REGISTER_COMMAND(ICreateCommand crea private int readHead = 0; private int readBitHead = 0; + private final StringBuilder cmd_log = new StringBuilder(); private final StringBuilder validation_log = new StringBuilder(); public void ensureCapacity(int numLongs) { @@ -142,11 +152,29 @@ public void Toggle(int capability, boolean enabled) { TOGGLE_COMMAND.write(this); } + public void printCommandBuffer() { + readHead = 0; + readBitHead = 0; + validation_log.setLength(0); + cmd_log.setLength(0); + + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + int type = (int) readBits(8); + assert type < REGISTERED_COMMANDS.length : validation_log.toString(); + + BaseCommand command = REGISTERED_COMMANDS[type]; + command.read(this); + command.print(cmd_log); + } + log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", cmd_log); + } + @SneakyThrows public void execute() { readHead = 0; readBitHead = 0; - if(VALIDATE) validation_log.setLength(0); + validation_log.setLength(0); + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { int type = (int)readBits(8); assert type < REGISTERED_COMMANDS.length : validation_log.toString(); @@ -157,47 +185,51 @@ public void execute() { } command.read(this); - - command.print(validation_log); - validation_log.append("\n"); - command.execute(); if(HdPlugin.checkGLErrors(command.getName())) { - log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", validation_log); - validation_log.setLength(0); + printCommandBuffer(); break; } } } protected long readBits(int numBits) { - long result = 0; - int shift = 0; - if (readHead >= cmd.length) throw new BufferUnderflowException(); - while (numBits > 0) { - long word = cmd[readHead]; - int bitsLeftInWord = BITS_PER_WORD - readBitHead; - int bitsToRead = Math.min(bitsLeftInWord, numBits); - - if(VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); - - long mask = bitsToRead == BITS_PER_WORD ? ~0L : (1L << bitsToRead) - 1; - long bits = (word >>> readBitHead) & mask; - - result |= bits << shift; - - readBitHead += bitsToRead; - if (readBitHead == BITS_PER_WORD) { - readBitHead = 0; + long result = 0; + long word = cmd[readHead]; + if(readBitHead == 0) { + if (numBits == BITS_PER_WORD){ readHead++; + result = word; + } else { + readBitHead += numBits; + result = word & MASKS[numBits]; + } + } else { + int shift = 0; + while (numBits > 0) { + int bitsLeftInWord = BITS_PER_WORD - readBitHead; + int bitsToRead = Math.min(bitsLeftInWord, numBits); + + if (VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); + + long mask = (bitsToRead == BITS_PER_WORD) ? ~0L : MASKS[bitsToRead]; + long bits = (word >>> readBitHead) & mask; + result |= (bits << shift); + + readBitHead += bitsToRead; + if (readBitHead == BITS_PER_WORD) { + readBitHead = 0; + readHead++; + word = cmd[readHead]; + } + + shift += bitsToRead; + numBits -= bitsToRead; } - - shift += bitsToRead; - numBits -= bitsToRead; } if(VALIDATE) logReadResult(result); @@ -211,42 +243,54 @@ protected void writeBits(long value, int numBits) { int originalWriteHead = writeHead; int originalWriteBitHead = writeBitHead; - while (numBits > 0) { - int bitsLeftInWord = BITS_PER_WORD - writeBitHead; - int bitsToWrite = Math.min(bitsLeftInWord, numBits); + if (writeBitHead == 0) { + if (numBits == BITS_PER_WORD) { + cmd[writeHead++] = value; + } else { + writeBitHead += numBits; + cmd[writeHead] = value & MASKS[numBits]; + } + } else { + long word = cmd[writeHead]; + while (numBits > 0) { + int bitsLeftInWord = BITS_PER_WORD - writeBitHead; + int bitsToWrite = Math.min(bitsLeftInWord, numBits); - long valueMask = bitsToWrite == BITS_PER_WORD ? ~0L : (1L << bitsToWrite) - 1L; - long bits = value & valueMask; + long bits = (bitsToWrite == BITS_PER_WORD) ? value : (value & MASKS[bitsToWrite]); - if(VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); + if (VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); - long destMask = bitsToWrite == BITS_PER_WORD ? ~0L : valueMask << writeBitHead; - cmd[writeHead] = (cmd[writeHead] & ~destMask) | ((bits << writeBitHead) & destMask); + long destMask = (bitsToWrite == BITS_PER_WORD) ? ~0L : (MASKS[bitsToWrite] << writeBitHead); + word = (word & ~destMask) | ((bits << writeBitHead) & destMask); + cmd[writeHead] = word; - writeBitHead += bitsToWrite; - if (writeBitHead == BITS_PER_WORD) { - writeBitHead = 0; - writeHead++; - ensureCapacity(writeHead); - cmd[writeHead] = 0; - } + writeBitHead += bitsToWrite; + if (writeBitHead == BITS_PER_WORD) { + writeHead++; + ensureCapacity(writeHead); - value >>>= bitsToWrite; - numBits -= bitsToWrite; - } + writeBitHead = 0; + word = 0; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } - if(VALIDATE) { - int originalReadHead = readHead; - int originalReadBitHead = readBitHead; + if (VALIDATE) { + int originalReadHead = readHead; + int originalReadBitHead = readBitHead; - readHead = originalWriteHead; - readBitHead = originalWriteBitHead; + readHead = originalWriteHead; + readBitHead = originalWriteBitHead; - long decoded = readBits(originalNumBits); - assert decoded == originalValue : "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; + long decoded = readBits(originalNumBits); + assert decoded == originalValue : + "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; - readHead = originalReadHead; - readBitHead = originalReadBitHead; + readHead = originalReadHead; + readBitHead = originalReadBitHead; + } } } From 0e6d7ecd4670c4035caad365957cb4564ce7c517 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:00:01 +0100 Subject: [PATCH 13/50] More Improvements to perf --- .../commands/ColorMaskCommand.java | 4 +--- .../commands/DrawArraysCommand.java | 17 +++-------------- .../commands/DrawElementsCommand.java | 12 ++---------- .../commands/MultiDrawArraysCommand.java | 11 +++-------- src/main/java/rs117/hd/renderer/zone/Zone.java | 18 +++++++++++++++--- 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java index 4468884adc..588b2e1b97 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -27,9 +27,7 @@ protected void doRead() { } @Override - public void execute() { - glColorMask(red, green, blue, alpha); - } + public void execute() { glColorMask(red, green, blue, alpha); } @Override public void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java index 37d90a3baf..d9626530f4 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -13,31 +13,20 @@ public final class DrawArraysCommand extends BaseCommand { @Override public void doWrite() { - assert mode >= 0 : "mode must be >= 0"; - assert vertexCount >= 0 : "vertexCount must be >= 0"; - assert offset >= 0 : "offset must be >= 0"; - - write16(mode); + write8(mode); write32(vertexCount); write32(offset); } @Override public void doRead() { - mode = read16(); + mode = read8(); vertexCount = read32(); offset = read32(); - - assert mode >= 0 : "mode must be >= 0"; - assert vertexCount >= 0 : "vertexCount must be >= 0"; - assert offset >= 0 : "offset must be >= 0"; } @Override - public void execute() { - - glDrawArrays(mode, offset, vertexCount); - } + public void execute() { glDrawArrays(mode, offset, vertexCount); } @Override public void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java index 8096ba5518..6c008b6c8c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -14,24 +14,16 @@ public final class DrawElementsCommand extends BaseCommand { @Override public void doWrite() { - assert mode >= 0 : "mode must be >= 0"; - assert vertexCount >= 0 : "vertexCount must be >= 0"; - assert bytesOffset >= 0 : "bytesOffset must be >= 0"; - - write16(mode); + write8(mode); write32(vertexCount); write64(bytesOffset); } @Override public void doRead() { - mode = read16(); + mode = read8(); vertexCount = read32(); bytesOffset = read64(); - - assert mode >= 0 : "mode must be >= 0"; - assert vertexCount >= 0 : "vertexCount must be >= 0"; - assert bytesOffset >= 0 : "bytesOffset must be >= 0"; } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index 002db9f493..31329aeae6 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -20,11 +20,9 @@ public final class MultiDrawArraysCommand extends BaseCommand { public void doWrite() { assert offsets.length == counts.length; - write16(mode); + write8(mode); write32(offsets.length); for(int i = 0; i < offsets.length; i++) { - assert offsets[i] >= 0 : "offset must be >= 0"; - assert counts[i] >= 0 : "vertexCount must be >= 0"; write32(offsets[i]); write32(counts[i]); } @@ -35,7 +33,7 @@ public void doWrite() { @Override public void doRead() { - mode = read16(); + mode = read8(); int length = read32(); if(offsetsBuffer == null || offsetsBuffer.capacity() < length) { @@ -59,10 +57,7 @@ public void doRead() { } @Override - public void execute() { - - glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); - } + public void execute() { glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); } @Override public void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 67fe145a86..829cc79a37 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -238,7 +238,11 @@ void renderOpaque(CommandBuffer cmd, int zx, int zz, int minLevel, int currentLe cmd.SetBaseOffset(zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); - cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + if(glDrawOffset.length > 1) { + cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + } else { + cmd.DrawArrays(GL_TRIANGLES, glDrawOffset[0], glDrawLength[0]); + } } void renderOpaqueLevel(CommandBuffer cmd, int zx, int zz, int level) { @@ -254,7 +258,11 @@ void renderOpaqueLevel(CommandBuffer cmd, int zx, int zz, int level) { cmd.SetBaseOffset(zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); - cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + if(glDrawOffset.length > 1) { + cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + } else { + cmd.DrawArrays(GL_TRIANGLES, glDrawOffset[0], glDrawLength[0]); + } } private static void pushRange(int start, int end) { @@ -625,7 +633,11 @@ private void flush(CommandBuffer cmd) { } else if (drawIdx != 0) { convertForDraw(VAO.VERT_SIZE); cmd.BindVertexArray(lastVao); - cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + if(glDrawOffset.length > 1) { + cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + } else { + cmd.DrawArrays(GL_TRIANGLES, glDrawOffset[0], glDrawLength[0]); + } drawIdx = 0; } } From 9b67b2bdc1e0d9de5bb4a1bd3a6315a1f537238f Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:10:58 +0100 Subject: [PATCH 14/50] Even more Optimizations --- .../hd/opengl/commandbuffer/BaseCommand.java | 12 -- .../opengl/commandbuffer/CommandBuffer.java | 149 +++++------------- 2 files changed, 38 insertions(+), 123 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index 8e7c1be669..c56becfd9c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -33,12 +33,6 @@ protected UBOCommandBuffer getUBOCommandBuffer() { public final void write(CommandBuffer buffer) { this.buffer = buffer; - - if(CommandBuffer.VALIDATE) { - buffer.appendToValidationLog(name); - buffer.appendToValidationLog("::write\n"); - } - write8(id); doWrite(); } @@ -47,12 +41,6 @@ public final void write(CommandBuffer buffer) { public final void read(CommandBuffer buffer) { this.buffer = buffer; - - if(CommandBuffer.VALIDATE) { - buffer.appendToValidationLog(name); - buffer.appendToValidationLog("::read\n"); - } - doRead(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index b5cd9bb049..2af5640880 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -18,8 +18,6 @@ @Slf4j public final class CommandBuffer { - public static boolean VALIDATE = false; - private static final int BITS_PER_WORD = 64; private static final long[] MASKS = new long[65]; @@ -73,7 +71,6 @@ private static T REGISTER_COMMAND(ICreateCommand crea private int readBitHead = 0; private final StringBuilder cmd_log = new StringBuilder(); - private final StringBuilder validation_log = new StringBuilder(); public void ensureCapacity(int numLongs) { if (writeHead + numLongs >= cmd.length) @@ -155,12 +152,11 @@ public void Toggle(int capability, boolean enabled) { public void printCommandBuffer() { readHead = 0; readBitHead = 0; - validation_log.setLength(0); cmd_log.setLength(0); while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { int type = (int) readBits(8); - assert type < REGISTERED_COMMANDS.length : validation_log.toString(); + assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; BaseCommand command = REGISTERED_COMMANDS[type]; command.read(this); @@ -173,11 +169,10 @@ public void printCommandBuffer() { public void execute() { readHead = 0; readBitHead = 0; - validation_log.setLength(0); while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { int type = (int)readBits(8); - assert type < REGISTERED_COMMANDS.length : validation_log.toString(); + assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; BaseCommand command = REGISTERED_COMMANDS[type]; if (command.isDrawCall() && uboCommandBuffer != null && uboCommandBuffer.isDirty()) { @@ -198,51 +193,40 @@ protected long readBits(int numBits) { if (readHead >= cmd.length) throw new BufferUnderflowException(); - long result = 0; long word = cmd[readHead]; if(readBitHead == 0) { if (numBits == BITS_PER_WORD){ readHead++; - result = word; + return word; } else { readBitHead += numBits; - result = word & MASKS[numBits]; - } - } else { - int shift = 0; - while (numBits > 0) { - int bitsLeftInWord = BITS_PER_WORD - readBitHead; - int bitsToRead = Math.min(bitsLeftInWord, numBits); - - if (VALIDATE) logReadBits(numBits, readHead, readBitHead, bitsToRead); - - long mask = (bitsToRead == BITS_PER_WORD) ? ~0L : MASKS[bitsToRead]; - long bits = (word >>> readBitHead) & mask; - result |= (bits << shift); - - readBitHead += bitsToRead; - if (readBitHead == BITS_PER_WORD) { - readBitHead = 0; - readHead++; - word = cmd[readHead]; - } - - shift += bitsToRead; - numBits -= bitsToRead; + return word & MASKS[numBits]; } } - if(VALIDATE) logReadResult(result); + long result = 0; + int shift = 0; + while (numBits > 0) { + int bitsToRead = Math.min(BITS_PER_WORD - readBitHead, numBits); + long bits = (word >>> readBitHead) & MASKS[bitsToRead]; + + result |= (bits << shift); + + readBitHead += bitsToRead; + if (readBitHead == BITS_PER_WORD) { + readBitHead = 0; + readHead++; + word = cmd[readHead]; + } + + shift += bitsToRead; + numBits -= bitsToRead; + } return result; } protected void writeBits(long value, int numBits) { - long originalValue = value; - int originalNumBits = numBits; - int originalWriteHead = writeHead; - int originalWriteBitHead = writeBitHead; - if (writeBitHead == 0) { if (numBits == BITS_PER_WORD) { cmd[writeHead++] = value; @@ -250,93 +234,36 @@ protected void writeBits(long value, int numBits) { writeBitHead += numBits; cmd[writeHead] = value & MASKS[numBits]; } - } else { - long word = cmd[writeHead]; - while (numBits > 0) { - int bitsLeftInWord = BITS_PER_WORD - writeBitHead; - int bitsToWrite = Math.min(bitsLeftInWord, numBits); - - long bits = (bitsToWrite == BITS_PER_WORD) ? value : (value & MASKS[bitsToWrite]); - - if (VALIDATE) logWriteBits(value, numBits, writeHead, writeBitHead, bitsToWrite); + return; + } - long destMask = (bitsToWrite == BITS_PER_WORD) ? ~0L : (MASKS[bitsToWrite] << writeBitHead); - word = (word & ~destMask) | ((bits << writeBitHead) & destMask); - cmd[writeHead] = word; + long word = cmd[writeHead]; + while (numBits > 0) { + int bitsToWrite = Math.min(BITS_PER_WORD - writeBitHead, numBits); + long bits = value & MASKS[bitsToWrite]; + long destMask = (bitsToWrite == BITS_PER_WORD) ? ~0L : (MASKS[bitsToWrite] << writeBitHead); - writeBitHead += bitsToWrite; - if (writeBitHead == BITS_PER_WORD) { - writeHead++; - ensureCapacity(writeHead); + cmd[writeHead] = (word & ~destMask) | ((bits << writeBitHead) & destMask); - writeBitHead = 0; - word = 0; - } + writeBitHead += bitsToWrite; + if (writeBitHead == BITS_PER_WORD) { + writeHead++; + ensureCapacity(writeHead); - value >>>= bitsToWrite; - numBits -= bitsToWrite; + writeBitHead = 0; + word = 0; } - if (VALIDATE) { - int originalReadHead = readHead; - int originalReadBitHead = readBitHead; - - readHead = originalWriteHead; - readBitHead = originalWriteBitHead; - - long decoded = readBits(originalNumBits); - assert decoded == originalValue : - "read: " + decoded + " but expected: " + originalValue + "\nValidation Log:" + validation_log; - - readHead = originalReadHead; - readBitHead = originalReadBitHead; - } + value >>>= bitsToWrite; + numBits -= bitsToWrite; } } - protected void appendToValidationLog(String str) { - validation_log.append(str); - } - - private void logWriteBits(long value, int numBits, int wordIndex, int bitHead, int bitsToWrite) { - validation_log.append("writeBits("); - validation_log.append(value); - validation_log.append(", "); - validation_log.append(numBits); - validation_log.append(") wordIndex: "); - validation_log.append(wordIndex); - validation_log.append(" bitHead: "); - validation_log.append(bitHead); - validation_log.append(" bitsToWrite: "); - validation_log.append(bitsToWrite); - validation_log.append("\n"); - } - - private void logReadBits(int numBits, int wordIndex, int bitHead, int bitsToRead) { - validation_log.append("readBits("); - validation_log.append(numBits); - validation_log.append(") wordIndex: "); - validation_log.append(wordIndex); - validation_log.append(" bitHead: "); - validation_log.append(bitHead); - validation_log.append(" bitsToRead: "); - validation_log.append(bitsToRead); - validation_log.append("\n"); - } - - private void logReadResult(long result) { - validation_log.append("readBits() Result: "); - validation_log.append(result); - validation_log.append("\n"); - } - public void reset() { writeHead = 0; writeBitHead = 0; readHead = 0; readBitHead = 0; - - validation_log.setLength(0); } } From 80aca4d834ce02c3e84a95dceb044502ff7b732d Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:21:14 +0100 Subject: [PATCH 15/50] Make Commands Local to the CommandBuffer so we don't need to pass a reference each time --- .../hd/opengl/commandbuffer/BaseCommand.java | 13 ++--- .../opengl/commandbuffer/CommandBuffer.java | 49 ++++++++++--------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index c56becfd9c..fa1c2bdd5d 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -28,22 +28,15 @@ protected UBOCommandBuffer getUBOCommandBuffer() { return buffer.uboCommandBuffer; } - public abstract void execute(); - public abstract void print(StringBuilder sb); + protected abstract void execute(); + protected abstract void print(StringBuilder sb); - public final void write(CommandBuffer buffer) { - this.buffer = buffer; + protected final void write() { write8(id); doWrite(); } protected abstract void doWrite(); - - public final void read(CommandBuffer buffer) { - this.buffer = buffer; - doRead(); - } - protected abstract void doRead(); protected final void write1(int value) { buffer.writeBits(value & 0x1L, 1); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 2af5640880..e7514e9113 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -29,8 +29,8 @@ public final class CommandBuffer { MASKS[BITS_PER_WORD] = ~0L; } - private static int COMMAND_COUNT = 0; - private static final BaseCommand[] REGISTERED_COMMANDS = { + private int COMMAND_COUNT = 0; + private final BaseCommand[] REGISTERED_COMMANDS = { (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), (DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new)), (MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new)), @@ -42,21 +42,22 @@ public final class CommandBuffer { (UPDATE_CMD_UBO_COMMAND = REGISTER_COMMAND(UpdateCMDUBOCommand::new)), }; - private static final DrawArraysCommand DRAW_ARRAYS_COMMAND; - private static final DrawElementsCommand DRAW_ELEMENTS_COMMAND; - private static final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND; - private static final ToggleCommand TOGGLE_COMMAND; - private static final ColorMaskCommand COLOR_MASK_COMMAND; - private static final DepthMaskCommand DEPTH_MASK_COMMAND; - private static final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; - private static final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; - private static final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; + private final DrawArraysCommand DRAW_ARRAYS_COMMAND; + private final DrawElementsCommand DRAW_ELEMENTS_COMMAND; + private final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND; + private final ToggleCommand TOGGLE_COMMAND; + private final ColorMaskCommand COLOR_MASK_COMMAND; + private final DepthMaskCommand DEPTH_MASK_COMMAND; + private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; + private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; + private final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; interface ICreateCommand { T construct(); } - private static T REGISTER_COMMAND(ICreateCommand createCommand) { + private T REGISTER_COMMAND(ICreateCommand createCommand) { T newCommand = createCommand.construct(); newCommand.id = COMMAND_COUNT; + newCommand.buffer = this; COMMAND_COUNT++; return newCommand; } @@ -82,28 +83,28 @@ public void SetBaseOffset(int x, int y, int z) { UPDATE_CMD_UBO_COMMAND.x = x; UPDATE_CMD_UBO_COMMAND.y = y; UPDATE_CMD_UBO_COMMAND.z = z; - UPDATE_CMD_UBO_COMMAND.write(this); + UPDATE_CMD_UBO_COMMAND.write(); } public void SetWorldViewIndex(int index) { UPDATE_CMD_UBO_COMMAND.isBaseOffset = false; UPDATE_CMD_UBO_COMMAND.worldViewId = index; - UPDATE_CMD_UBO_COMMAND.write(this); + UPDATE_CMD_UBO_COMMAND.write(); } public void BindVertexArray(int vao) { BIND_VERTEX_ARRAY_COMMAND.vao = vao; - BIND_VERTEX_ARRAY_COMMAND.write(this); + BIND_VERTEX_ARRAY_COMMAND.write(); } public void BindElementsArray(int ebo) { BIND_ELEMENTS_ARRAY_COMMAND.ebo = ebo; - BIND_ELEMENTS_ARRAY_COMMAND.write(this); + BIND_ELEMENTS_ARRAY_COMMAND.write(); } public void DepthMask(boolean writeDepth) { DEPTH_MASK_COMMAND.flag = writeDepth; - DEPTH_MASK_COMMAND.write(this); + DEPTH_MASK_COMMAND.write(); } public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { @@ -111,28 +112,28 @@ public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, b COLOR_MASK_COMMAND.green = writeGreen; COLOR_MASK_COMMAND.blue = writeBlue; COLOR_MASK_COMMAND.alpha = writeAlpha; - COLOR_MASK_COMMAND.write(this); + COLOR_MASK_COMMAND.write(); } public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { MULTI_DRAW_ARRAYS_COMMAND.mode = mode; MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; MULTI_DRAW_ARRAYS_COMMAND.counts = counts; - MULTI_DRAW_ARRAYS_COMMAND.write(this); + MULTI_DRAW_ARRAYS_COMMAND.write(); } public void DrawElements(int mode, int vertexCount, long bytesOffset) { DRAW_ELEMENTS_COMMAND.mode = mode; DRAW_ELEMENTS_COMMAND.vertexCount = vertexCount; DRAW_ELEMENTS_COMMAND.bytesOffset = bytesOffset; - DRAW_ELEMENTS_COMMAND.write(this); + DRAW_ELEMENTS_COMMAND.write(); } public void DrawArrays(int mode, int offset, int vertexCount) { DRAW_ARRAYS_COMMAND.mode = mode; DRAW_ARRAYS_COMMAND.offset = offset; DRAW_ARRAYS_COMMAND.vertexCount = vertexCount; - DRAW_ARRAYS_COMMAND.write(this); + DRAW_ARRAYS_COMMAND.write(); } public void Enable(int capability) { @@ -146,7 +147,7 @@ public void Disable(int capability) { public void Toggle(int capability, boolean enabled) { TOGGLE_COMMAND.capability = capability; TOGGLE_COMMAND.state = enabled; - TOGGLE_COMMAND.write(this); + TOGGLE_COMMAND.write(); } public void printCommandBuffer() { @@ -159,7 +160,7 @@ public void printCommandBuffer() { assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; BaseCommand command = REGISTERED_COMMANDS[type]; - command.read(this); + command.doRead(); command.print(cmd_log); } log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", cmd_log); @@ -179,7 +180,7 @@ public void execute() { uboCommandBuffer.upload(); } - command.read(this); + command.doRead(); command.execute(); if(HdPlugin.checkGLErrors(command.getName())) { From b168b127ae927fecb872ae519eb4e6eae5f78fc3 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:34:39 +0100 Subject: [PATCH 16/50] More optimizations --- .../hd/opengl/commandbuffer/BaseCommand.java | 10 ++++++++-- .../opengl/commandbuffer/CommandBuffer.java | 20 ++++++++----------- .../commands/BindElementsArrayCommand.java | 2 ++ .../commands/BindVertexArrayCommand.java | 2 ++ .../commands/ColorMaskCommand.java | 2 ++ .../commands/DepthMaskCommand.java | 2 ++ .../commandbuffer/commands/ToggleCommand.java | 2 ++ 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index fa1c2bdd5d..da291f99ed 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -15,14 +15,20 @@ public abstract class BaseCommand { @Getter private final boolean isDrawCall; + @Getter + private final boolean isGLCommand; + protected CommandBuffer buffer; - protected BaseCommand(boolean isDrawCall) { + protected BaseCommand(boolean isDrawCall, boolean isGLCommand) { this.name = getClass().getSimpleName(); this.isDrawCall = isDrawCall; + this.isGLCommand = isDrawCall || isGLCommand; } - protected BaseCommand() { this(false); } + protected BaseCommand(boolean isDrawCall) { this(isDrawCall, false); } + + protected BaseCommand() { this(false, false); } protected UBOCommandBuffer getUBOCommandBuffer() { return buffer.uboCommandBuffer; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index e7514e9113..ff03dff14c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -1,6 +1,5 @@ package rs117.hd.opengl.commandbuffer; -import java.nio.BufferUnderflowException; import java.util.Arrays; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -172,7 +171,7 @@ public void execute() { readBitHead = 0; while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { - int type = (int)readBits(8); + final int type = (int)readBits(8); assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; BaseCommand command = REGISTERED_COMMANDS[type]; @@ -183,7 +182,7 @@ public void execute() { command.doRead(); command.execute(); - if(HdPlugin.checkGLErrors(command.getName())) { + if(command.isGLCommand() && HdPlugin.checkGLErrors(command.getName())) { printCommandBuffer(); break; } @@ -191,9 +190,6 @@ public void execute() { } protected long readBits(int numBits) { - if (readHead >= cmd.length) - throw new BufferUnderflowException(); - long word = cmd[readHead]; if(readBitHead == 0) { if (numBits == BITS_PER_WORD){ @@ -208,8 +204,8 @@ protected long readBits(int numBits) { long result = 0; int shift = 0; while (numBits > 0) { - int bitsToRead = Math.min(BITS_PER_WORD - readBitHead, numBits); - long bits = (word >>> readBitHead) & MASKS[bitsToRead]; + final int bitsToRead = Math.min(BITS_PER_WORD - readBitHead, numBits); + final long bits = (word >>> readBitHead) & MASKS[bitsToRead]; result |= (bits << shift); @@ -240,11 +236,11 @@ protected void writeBits(long value, int numBits) { long word = cmd[writeHead]; while (numBits > 0) { - int bitsToWrite = Math.min(BITS_PER_WORD - writeBitHead, numBits); - long bits = value & MASKS[bitsToWrite]; - long destMask = (bitsToWrite == BITS_PER_WORD) ? ~0L : (MASKS[bitsToWrite] << writeBitHead); + final int bitsToWrite = Math.min(BITS_PER_WORD - writeBitHead, numBits); + final long bits = value & MASKS[bitsToWrite]; - cmd[writeHead] = (word & ~destMask) | ((bits << writeBitHead) & destMask); + word |= bits << writeBitHead; + cmd[writeHead] = word; writeBitHead += bitsToWrite; if (writeBitHead == BITS_PER_WORD) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java index 5d9a4a0e4e..abf4c1efa8 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -8,6 +8,8 @@ public final class BindElementsArrayCommand extends BaseCommand { public int ebo; + public BindElementsArrayCommand() { super(false, true); } + @Override protected void doWrite() { write32(ebo); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java index ecd7526239..be9659fae0 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -7,6 +7,8 @@ public final class BindVertexArrayCommand extends BaseCommand { public int vao; + public BindVertexArrayCommand() { super(false, true); } + @Override protected void doWrite() { write32(vao); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java index 588b2e1b97..7091b5851e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -10,6 +10,8 @@ public final class ColorMaskCommand extends BaseCommand { public boolean blue; public boolean alpha; + public ColorMaskCommand() { super(false, true); } + @Override protected void doWrite() { writeFlag(red); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java index 45f2ff862c..ea95c5958b 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -9,6 +9,8 @@ public final class DepthMaskCommand extends BaseCommand { public boolean flag; + public DepthMaskCommand() { super(false, true); } + @Override protected void doWrite() { writeFlag(flag); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java index 3051238355..9fd80e8814 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -9,6 +9,8 @@ public final class ToggleCommand extends BaseCommand { public int capability; public boolean state; + public ToggleCommand() { super(false, true); } + @Override public void doWrite() { write32(capability); From c8c7b332b2e46a02de735fa3eaaf8b8be1a0fe77 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:37:06 +0100 Subject: [PATCH 17/50] Big Command Buffer Update Added Directional & Scene Draw Pass CommandBuffers Added missing commands needed for the two passes Added ability for command buffer to track objects --- .../hd/opengl/commandbuffer/BaseCommand.java | 32 ++-- .../opengl/commandbuffer/CommandBuffer.java | 116 +++++++++++++- .../commands/BindFrameBufferCommand.java | 36 +++++ .../commands/BlendFuncCommand.java | 48 ++++++ .../commandbuffer/commands/ClearCommand.java | 92 ++++++++++++ .../commands/DepthFuncCommand.java | 28 ++++ .../commands/ExecuteCommandBufferCommand.java | 29 ++++ .../commands/ShaderProgramCommand.java | 31 ++++ .../commands/ViewportCommand.java | 46 ++++++ .../rs117/hd/renderer/zone/ZoneRenderer.java | 141 ++++++++---------- 10 files changed, 506 insertions(+), 93 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index da291f99ed..012cc3b01a 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -18,7 +18,7 @@ public abstract class BaseCommand { @Getter private final boolean isGLCommand; - protected CommandBuffer buffer; + protected CommandBuffer owner; protected BaseCommand(boolean isDrawCall, boolean isGLCommand) { this.name = getClass().getSimpleName(); @@ -31,7 +31,7 @@ protected BaseCommand(boolean isDrawCall, boolean isGLCommand) { protected BaseCommand() { this(false, false); } protected UBOCommandBuffer getUBOCommandBuffer() { - return buffer.uboCommandBuffer; + return owner.uboCommandBuffer; } protected abstract void execute(); @@ -45,17 +45,23 @@ protected final void write() { protected abstract void doWrite(); protected abstract void doRead(); - protected final void write1(int value) { buffer.writeBits(value & 0x1L, 1); } - protected final void write8(int value) { buffer.writeBits(value & 0xFFL, 8); } - protected final void write16(int value) { buffer.writeBits(value & 0xFFFFL, 16); } - protected final void write32(int value) { buffer.writeBits(value & 0xFFFFFFFFL, 32); } - protected final void write64(long value) { buffer.writeBits(value, 64); } - - protected final int read1() { return (int) buffer.readBits(1); } - protected final int read8() { return (int) buffer.readBits(8); } - protected final int read16() { return (int) buffer.readBits(16); } - protected final int read32() { return (int) buffer.readBits(32); } - protected final long read64() { return buffer.readBits(64); } + protected final void write1(int value) { owner.writeBits(value & 0x1L, 1); } + protected final void write8(int value) { owner.writeBits(value & 0xFFL, 8); } + protected final void write16(int value) { owner.writeBits(value & 0xFFFFL, 16); } + protected final void write32(int value) { owner.writeBits(value & 0xFFFFFFFFL, 32); } + protected final void write64(long value) { owner.writeBits(value, 64); } + + protected final int read1() { return (int) owner.readBits(1); } + protected final int read8() { return (int) owner.readBits(8); } + protected final int read16() { return (int) owner.readBits(16); } + protected final int read32() { return (int) owner.readBits(32); } + protected final long read64() { return owner.readBits(64); } + + protected final void writeObject(Object value) {owner.writeObject(value);} + protected final T readObject() { return owner.readObject(); } + + protected final void write32F(float value) { write32(Float.floatToIntBits(value)); } + protected final float read32F() { return Float.intBitsToFloat(read32()); } protected final void writeFlag(boolean value) { write1(value ? 1 : 0); } protected final boolean readFlag() { return read1() != 0; } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index ff03dff14c..a3a4383a24 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -5,14 +5,22 @@ import lombok.extern.slf4j.Slf4j; import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.BindFrameBufferCommand; import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.BlendFuncCommand; +import rs117.hd.opengl.commandbuffer.commands.ClearCommand; import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DepthFuncCommand; import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; import rs117.hd.opengl.commandbuffer.commands.DrawArraysCommand; import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; +import rs117.hd.opengl.commandbuffer.commands.ExecuteCommandBufferCommand; import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; import rs117.hd.opengl.commandbuffer.commands.UpdateCMDUBOCommand; +import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; +import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.uniforms.UBOCommandBuffer; @Slf4j @@ -33,30 +41,44 @@ public final class CommandBuffer { (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), (DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new)), (MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new)), + (BIND_FRAMEBUFFER_COMMAND = REGISTER_COMMAND(BindFrameBufferCommand::new)), + (VIEWPORT_COMMAND = REGISTER_COMMAND(ViewportCommand::new)), + (CLEAR_COMMAND = REGISTER_COMMAND(ClearCommand::new)), (TOGGLE_COMMAND = REGISTER_COMMAND(ToggleCommand::new)), + (SHADER_PROGRAM_COMMAND = REGISTER_COMMAND(ShaderProgramCommand::new)), (COLOR_MASK_COMMAND = REGISTER_COMMAND(ColorMaskCommand::new)), + (DEPTH_FUNC_COMMAND = REGISTER_COMMAND(DepthFuncCommand::new)), (DEPTH_MASK_COMMAND = REGISTER_COMMAND(DepthMaskCommand::new)), + (BLENDED_FUNC_COMMAND = REGISTER_COMMAND(BlendFuncCommand::new)), (BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new)), (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), (UPDATE_CMD_UBO_COMMAND = REGISTER_COMMAND(UpdateCMDUBOCommand::new)), + (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), }; private final DrawArraysCommand DRAW_ARRAYS_COMMAND; private final DrawElementsCommand DRAW_ELEMENTS_COMMAND; private final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND; private final ToggleCommand TOGGLE_COMMAND; + private final BindFrameBufferCommand BIND_FRAMEBUFFER_COMMAND; + private final ViewportCommand VIEWPORT_COMMAND; + private final ClearCommand CLEAR_COMMAND; private final ColorMaskCommand COLOR_MASK_COMMAND; + private final DepthFuncCommand DEPTH_FUNC_COMMAND; private final DepthMaskCommand DEPTH_MASK_COMMAND; + private final BlendFuncCommand BLENDED_FUNC_COMMAND; private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; private final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; + private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; + private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; interface ICreateCommand { T construct(); } private T REGISTER_COMMAND(ICreateCommand createCommand) { T newCommand = createCommand.construct(); newCommand.id = COMMAND_COUNT; - newCommand.buffer = this; + newCommand.owner = this; COMMAND_COUNT++; return newCommand; } @@ -64,6 +86,10 @@ private T REGISTER_COMMAND(ICreateCommand createComma public UBOCommandBuffer uboCommandBuffer; private long[] cmd = new long[1 << 20]; // ~1 million calls + + private Object[] objects = new Object[100]; + private int objectCount = 0; + private int writeHead = 0; private int writeBitHead = 0; @@ -101,11 +127,64 @@ public void BindElementsArray(int ebo) { BIND_ELEMENTS_ARRAY_COMMAND.write(); } + public void SetDepthFunc(int DepthFunc) { + DEPTH_FUNC_COMMAND.mode = DepthFunc; + DEPTH_FUNC_COMMAND.write(); + } + + public void ExecuteCommandBuffer(CommandBuffer cmd) { + assert cmd != this; + EXECUTE_COMMAND_BUFFER_COMMAND.cmd = cmd; + EXECUTE_COMMAND_BUFFER_COMMAND.write(); + } + + public void SetShaderProgram(ShaderProgram program) { + assert program != null; + SHADER_PROGRAM_COMMAND.program = program; + SHADER_PROGRAM_COMMAND.write(); + } + + public void ClearDepth(float depth) { + CLEAR_COMMAND.clearColor = false; + CLEAR_COMMAND.clearDepth = true; + CLEAR_COMMAND.depth = depth; + CLEAR_COMMAND.write(); + } + + public void ClearColor(float red, float green, float blue, float alpha) { + CLEAR_COMMAND.clearColor = true; + CLEAR_COMMAND.clearDepth = false; + CLEAR_COMMAND.red = red; + CLEAR_COMMAND.green = green; + CLEAR_COMMAND.blue = blue; + CLEAR_COMMAND.alpha = alpha; + CLEAR_COMMAND.write(); + } + + public void ClearColorAndDepth(float red, float green, float blue, float alpha, float depth) { + CLEAR_COMMAND.clearColor = true; + CLEAR_COMMAND.clearDepth = true; + CLEAR_COMMAND.depth = depth; + CLEAR_COMMAND.red = red; + CLEAR_COMMAND.green = green; + CLEAR_COMMAND.blue = blue; + CLEAR_COMMAND.alpha = alpha; + CLEAR_COMMAND.write(); + } + public void DepthMask(boolean writeDepth) { DEPTH_MASK_COMMAND.flag = writeDepth; DEPTH_MASK_COMMAND.write(); } + public void BlendFunc(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) { + BLENDED_FUNC_COMMAND.sfactorRGB = sfactorRGB; + BLENDED_FUNC_COMMAND.dfactorRGB = dfactorRGB; + BLENDED_FUNC_COMMAND.sfactorAlpha = sfactorAlpha; + BLENDED_FUNC_COMMAND.dfactorAlpha = dfactorAlpha; + BLENDED_FUNC_COMMAND.write(); + } + public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { COLOR_MASK_COMMAND.red = writeRed; COLOR_MASK_COMMAND.green = writeGreen; @@ -135,6 +214,20 @@ public void DrawArrays(int mode, int offset, int vertexCount) { DRAW_ARRAYS_COMMAND.write(); } + public void BindFrameBuffer(int target, int fbo) { + BIND_FRAMEBUFFER_COMMAND.target = target; + BIND_FRAMEBUFFER_COMMAND.fbo = fbo; + BIND_FRAMEBUFFER_COMMAND.write(); + } + + public void Viewport(int x, int y, int width, int height) { + VIEWPORT_COMMAND.x = x; + VIEWPORT_COMMAND.y = y; + VIEWPORT_COMMAND.width = width; + VIEWPORT_COMMAND.height = height; + VIEWPORT_COMMAND.write(); + } + public void Enable(int capability) { Toggle(capability, true); } @@ -166,7 +259,7 @@ public void printCommandBuffer() { } @SneakyThrows - public void execute() { + public void submit() { readHead = 0; readBitHead = 0; @@ -189,6 +282,21 @@ public void execute() { } } + protected void writeObject(Object object) { + if (objectCount >= objects.length) { + objects = Arrays.copyOf(objects, objects.length * 2); + } + writeBits(objectCount, 32); + objects[objectCount++] = object; + } + + protected T readObject() { + int index = (int)readBits(32); + Object object = objects[index]; + objects[index] = null; + return (T) object; + } + protected long readBits(int numBits) { long word = cmd[readHead]; if(readBitHead == 0) { @@ -262,5 +370,9 @@ public void reset() { readHead = 0; readBitHead = 0; + + // Objects need to be cleared to avoid holding onto a reference and preventing garbage collection + Arrays.fill(objects, null); + objectCount = 0; } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java new file mode 100644 index 0000000000..79010399f4 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java @@ -0,0 +1,36 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30C.glBindFramebuffer; + +public class BindFrameBufferCommand extends BaseCommand { + public int target; + public int fbo; + + public BindFrameBufferCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(target); + write32(fbo); + } + + @Override + protected void doRead() { + target = read32(); + fbo = read32(); + } + + @Override + protected void execute() { glBindFramebuffer(target, fbo); } + + @Override + protected void print(StringBuilder sb) { + sb.append("glBindFramebuffer("); + sb.append(target); + sb.append(", "); + sb.append(fbo); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java new file mode 100644 index 0000000000..77bf541902 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java @@ -0,0 +1,48 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; + +public class BlendFuncCommand extends BaseCommand { + public int sfactorRGB; + public int dfactorRGB; + public int sfactorAlpha; + public int dfactorAlpha; + + public BlendFuncCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(sfactorRGB); + write32(dfactorRGB); + write32(sfactorAlpha); + write32(dfactorAlpha); + } + + @Override + protected void doRead() { + sfactorRGB = read32(); + dfactorRGB = read32(); + sfactorAlpha = read32(); + dfactorAlpha = read32(); + } + + @Override + protected void execute() { + glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("glBlendFuncSeparate("); + sb.append(sfactorRGB); + sb.append(", "); + sb.append(dfactorRGB); + sb.append(", "); + sb.append(sfactorAlpha); + sb.append(", "); + sb.append(dfactorAlpha); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java new file mode 100644 index 0000000000..faba8331df --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java @@ -0,0 +1,92 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.glClear; +import static org.lwjgl.opengl.GL11.glClearColor; +import static org.lwjgl.opengl.GL11C.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL11C.glClearDepth; + +public class ClearCommand extends BaseCommand { + public boolean clearColor; + public boolean clearDepth; + + public float depth; + + public float red; + public float green; + public float blue; + public float alpha; + + public ClearCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeFlag(clearColor); + if(clearColor) { + write32F(red); + write32F(green); + write32F(blue); + write32F(alpha); + } + + writeFlag(clearDepth); + if(clearDepth) { + write32F(depth); + } + } + + @Override + protected void doRead() { + clearColor = readFlag(); + + if(clearColor) { + red = read32F(); + green = read32F(); + blue = read32F(); + alpha = read32F(); + } + + clearDepth = readFlag(); + if(clearDepth) { + depth = read32F(); + } + } + + @Override + protected void execute() { + if(clearColor) { + glClearColor(red, green, blue, alpha); + } + if(clearDepth) { + glClearDepth(depth); + } + glClear((clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0)); + } + + @Override + protected void print(StringBuilder sb) { + if(clearColor) { + sb.append("glClearColor("); + sb.append(red); + sb.append(", "); + sb.append(green); + sb.append(", "); + sb.append(blue); + sb.append(", "); + sb.append(alpha); + sb.append(");"); + } + + if(clearDepth) { + sb.append("glClearDepth("); + sb.append(depth); + sb.append(");"); + } + + sb.append("glClear("); + sb.append((clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0)); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java new file mode 100644 index 0000000000..5f9ef44802 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java @@ -0,0 +1,28 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glDepthFunc; + +public class DepthFuncCommand extends BaseCommand { + + public int mode; + + public DepthFuncCommand() { super(false, true); } + + @Override + protected void doWrite() { write32(mode); } + + @Override + protected void doRead() { mode = read32(); } + + @Override + protected void execute() { glDepthFunc(mode); } + + @Override + protected void print(StringBuilder sb) { + sb.append("glDepthFunc("); + sb.append(mode); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java new file mode 100644 index 0000000000..930641d9f3 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java @@ -0,0 +1,29 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.commandbuffer.CommandBuffer; + +public class ExecuteCommandBufferCommand extends BaseCommand { + + public CommandBuffer cmd; + + @Override + protected void doWrite() { + writeObject(cmd); + } + + @Override + protected void doRead() { + cmd = readObject(); + } + + @Override + protected void execute() { + cmd.submit(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("ExecuteCommandBufferCommand"); // TODO: Make CommandBuffer able to take a StringBuilder + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java new file mode 100644 index 0000000000..aa1b4fc5f8 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java @@ -0,0 +1,31 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.shader.ShaderProgram; + +public class ShaderProgramCommand extends BaseCommand { + public ShaderProgram program; + + public ShaderProgramCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeObject(program); + } + + @Override + protected void doRead() { + program = readObject(); + } + + @Override + protected void execute() { + program.use(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append(program.getClass().getSimpleName()); + sb.append(".use();"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java new file mode 100644 index 0000000000..2ed02d1cbd --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java @@ -0,0 +1,46 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glViewport; + +public class ViewportCommand extends BaseCommand { + public int x; + public int y; + public int width; + public int height; + + public ViewportCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(x); + write32(y); + write32(width); + write32(height); + } + + @Override + protected void doRead() { + x = read32(); + y = read32(); + width = read32(); + height = read32(); + } + + @Override + protected void execute() { glViewport(x, y, width, height); } + + @Override + protected void print(StringBuilder sb) { + sb.append("glViewport("); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(", "); + sb.append(width); + sb.append(", "); + sb.append(height); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index e185ddf16e..ba49991a31 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -49,7 +49,6 @@ import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; import rs117.hd.opengl.commandbuffer.CommandBuffer; -import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -153,8 +152,11 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; - private final CommandBuffer sceneCmd = new CommandBuffer(); - private final CommandBuffer directionalCmd = new CommandBuffer(); + private final CommandBuffer scenePassCmd = new CommandBuffer(); + private final CommandBuffer sceneDrawCmd = new CommandBuffer(); + + private final CommandBuffer directionalPassCmd = new CommandBuffer(); + private final CommandBuffer directionalDrawCmd = new CommandBuffer(); private VAO.VAOList vaoO; private VAO.VAOList vaoA; @@ -245,8 +247,8 @@ public void initialize() { uboCommandBuffer.initialize(UNIFORM_BLOCK_COMMAND_BUFFER); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); - sceneCmd.uboCommandBuffer = uboCommandBuffer; - directionalCmd.uboCommandBuffer = uboCommandBuffer; + sceneDrawCmd.uboCommandBuffer = uboCommandBuffer; + directionalDrawCmd.uboCommandBuffer = uboCommandBuffer; } @Override @@ -352,8 +354,8 @@ public void preSceneDraw( vaoO.addRange(topLevel); vaoPO.addRange(topLevel); vaoPOShadow.addRange(topLevel); - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); } } @@ -741,11 +743,11 @@ private void preSceneDrawTopLevel( // Reset buffers for the next frame eboAlphaStaging.clear(); - sceneCmd.reset(); - directionalCmd.reset(); + sceneDrawCmd.reset(); + directionalDrawCmd.reset(); - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); checkGLErrors(); } @@ -756,8 +758,8 @@ public void postSceneDraw(Scene scene) { if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); } } @@ -783,41 +785,31 @@ private void postDrawTopLevel() { frameTimer.begin(Timer.RENDER_SHADOWS); // Render to the shadow depth map - glViewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); - glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); - glClearDepth(1); - glClear(GL_DEPTH_BUFFER_BIT); - - plugin.shadowProgram.use(); + directionalPassCmd.reset(); + directionalPassCmd.Viewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); + directionalPassCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); + directionalPassCmd.ClearDepth(1.0f); - // TODO: Depth test will get changed by the command buffer, but we'll be adding a shadowCmd anyway - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - glDisable(GL_CULL_FACE); + directionalPassCmd.SetShaderProgram(plugin.shadowProgram); + directionalPassCmd.Enable(GL_DEPTH_TEST); + directionalPassCmd.Disable(GL_CULL_FACE); + directionalPassCmd.SetDepthFunc(GL_LEQUAL); - frameTimer.begin(Timer.COMMAND_BUFFER_EXECUTE); - DepthMaskCommand.SKIP_DEPTH_MASKING = true; - directionalCmd.execute(); - DepthMaskCommand.SKIP_DEPTH_MASKING = false; - frameTimer.end(Timer.COMMAND_BUFFER_EXECUTE); + directionalPassCmd.ExecuteCommandBuffer(directionalDrawCmd); - glDisable(GL_DEPTH_TEST); + directionalPassCmd.Disable(GL_DEPTH_TEST); + directionalPassCmd.submit(); frameTimer.end(Timer.RENDER_SHADOWS); } - sceneProgram.use(); - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); - if (plugin.msaaSamples > 1) { - glEnable(GL_MULTISAMPLE); - } else { - glDisable(GL_MULTISAMPLE); - } - glViewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + frameTimer.begin(Timer.RENDER_SCENE); + scenePassCmd.reset(); + scenePassCmd.SetShaderProgram(sceneProgram); - // Clear scene - frameTimer.begin(Timer.CLEAR_SCENE); + scenePassCmd.BindFrameBuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); + scenePassCmd.Toggle(GL_MULTISAMPLE, plugin.msaaSamples > 1); + scenePassCmd.Viewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); @@ -827,33 +819,26 @@ private void postDrawTopLevel() { gammaCorrectedFogColor[2], 1f ); - glClearDepth(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - frameTimer.end(Timer.CLEAR_SCENE); + scenePassCmd.ClearColorAndDepth(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1f, 0.0f); + scenePassCmd.Enable(GL_CULL_FACE); + scenePassCmd.Enable(GL_DEPTH_TEST); + scenePassCmd.SetDepthFunc(GL_GREATER); - frameTimer.begin(Timer.RENDER_SCENE); + scenePassCmd.Enable(GL_BLEND); + scenePassCmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_GREATER); + scenePassCmd.ExecuteCommandBuffer(sceneDrawCmd); - // Render the scene - frameTimer.begin(Timer.COMMAND_BUFFER_EXECUTE); - sceneCmd.execute(); - frameTimer.end(Timer.COMMAND_BUFFER_EXECUTE); + scenePassCmd.Disable(GL_CULL_FACE); + scenePassCmd.Disable(GL_DEPTH_TEST); + scenePassCmd.Disable(GL_BLEND); - // TODO: Filler tiles + scenePassCmd.submit(); frameTimer.end(Timer.DRAW_SCENE); frameTimer.end(Timer.RENDER_SCENE); frameTimer.begin(Timer.RENDER_FRAME); - // Done rendering the scene - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); // The client only updates animations once per client tick, so we can skip updating geometry buffers, // but the compute shaders should still be executed in case the camera angle has changed. @@ -906,14 +891,14 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int int offset = ctx.sceneContext.sceneOffset >> 3; if (z.inSceneFrustum) { - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); - z.renderOpaque(sceneCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); + sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + z.renderOpaque(sceneDrawCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); } if (z.inShadowFrustum) { - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); z.renderOpaque( - directionalCmd, + directionalDrawCmd, zx - offset, zz - offset, minLevel, @@ -942,11 +927,11 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; if (renderWater || hasAlpha) - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); int offset = ctx.sceneContext.sceneOffset >> 3; if (renderWater) - z.renderOpaqueLevel(sceneCmd, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); + z.renderOpaqueLevel(sceneDrawCmd, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); if (!hasAlpha) return; @@ -958,7 +943,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i if (z.inSceneFrustum) { z.renderAlpha( - sceneCmd, + sceneDrawCmd, zx - offset, zz - offset, minLevel, @@ -971,9 +956,9 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i } if (z.inShadowFrustum) { - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); z.renderAlpha( - directionalCmd, + directionalDrawCmd, zx - offset, zz - offset, minLevel, @@ -1002,31 +987,31 @@ public void drawPass(Projection projection, Scene scene, int pass) { vaoPOShadow.addRange(scene); if (scene.getWorldViewId() == -1) { - sceneCmd.SetBaseOffset(0, 0, 0); - directionalCmd.SetBaseOffset(0, 0, 0); + sceneDrawCmd.SetBaseOffset(0, 0, 0); + directionalDrawCmd.SetBaseOffset(0, 0, 0); // Draw opaque vaoO.unmap(); - vaoO.drawAll(this, sceneCmd); - vaoO.drawAll(this, directionalCmd); + vaoO.drawAll(this, sceneDrawCmd); + vaoO.drawAll(this, directionalDrawCmd); vaoO.resetAll(); vaoPO.unmap(); // Draw player shadows vaoPOShadow.unmap(); - vaoPOShadow.drawAll(this, directionalCmd); + vaoPOShadow.drawAll(this, directionalDrawCmd); vaoPOShadow.resetAll(); // Draw players opaque, without depth writes - sceneCmd.DepthMask(false); - vaoPO.drawAll(this, sceneCmd); - sceneCmd.DepthMask(true); + sceneDrawCmd.DepthMask(false); + vaoPO.drawAll(this, sceneDrawCmd); + sceneDrawCmd.DepthMask(true); // Draw players opaque, writing only depth - sceneCmd.ColorMask(false, false, false, false); - vaoPO.drawAll(this, sceneCmd); - sceneCmd.ColorMask(true, true, true, true); + sceneDrawCmd.ColorMask(false, false, false, false); + vaoPO.drawAll(this, sceneDrawCmd); + sceneDrawCmd.ColorMask(true, true, true, true); vaoPO.resetAll(); } From c2598dbcb3008057cd7dc049e81204adba199fd1 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 16:59:16 +0100 Subject: [PATCH 18/50] Fix Alpha Shadows --- src/main/java/rs117/hd/renderer/zone/Zone.java | 9 +++++++-- src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 829cc79a37..afbfff97a7 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -495,13 +495,16 @@ void renderAlpha( int currentLevel, int maxLevel, int level, + boolean isShadow, Camera camera, Set hiddenRoofIds ) { if (alphaModels.isEmpty()) return; - cmd.DepthMask(false); + if(!isShadow) { + cmd.DepthMask(false); + } drawIdx = 0; lastDrawMode = lastVao = 0; @@ -612,7 +615,9 @@ void renderAlpha( } flush(cmd); - cmd.DepthMask(true); + if(!isShadow) { + cmd.DepthMask(true); + } } private void flush(CommandBuffer cmd) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index ba49991a31..d70ed80880 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -790,11 +790,12 @@ private void postDrawTopLevel() { directionalPassCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); directionalPassCmd.ClearDepth(1.0f); - directionalPassCmd.SetShaderProgram(plugin.shadowProgram); directionalPassCmd.Enable(GL_DEPTH_TEST); directionalPassCmd.Disable(GL_CULL_FACE); directionalPassCmd.SetDepthFunc(GL_LEQUAL); + directionalPassCmd.SetShaderProgram(plugin.shadowProgram); + directionalPassCmd.ExecuteCommandBuffer(directionalDrawCmd); directionalPassCmd.Disable(GL_DEPTH_TEST); @@ -950,6 +951,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i this.level, maxLevel, level, + false, sceneCamera, hideRoofIds ); @@ -965,6 +967,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i this.level, plugin.configRoofShadows ? 3 : maxLevel, level, + true, sceneCamera, plugin.configRoofShadows ? Collections.emptySet() : hideRoofIds ); From fe9aaf6348c3d0f3054d0284cac6b213180eb8c6 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:18:04 +0100 Subject: [PATCH 19/50] Added BlitFrameBufferCommand with MSAA support --- .../opengl/commandbuffer/CommandBuffer.java | 28 ++++++ .../commands/BlitFrameBufferCommand.java | 94 +++++++++++++++++++ .../rs117/hd/renderer/zone/ZoneRenderer.java | 43 +++------ 3 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index a3a4383a24..0c53fd3ba5 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -8,6 +8,7 @@ import rs117.hd.opengl.commandbuffer.commands.BindFrameBufferCommand; import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; import rs117.hd.opengl.commandbuffer.commands.BlendFuncCommand; +import rs117.hd.opengl.commandbuffer.commands.BlitFrameBufferCommand; import rs117.hd.opengl.commandbuffer.commands.ClearCommand; import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; import rs117.hd.opengl.commandbuffer.commands.DepthFuncCommand; @@ -52,6 +53,7 @@ public final class CommandBuffer { (BLENDED_FUNC_COMMAND = REGISTER_COMMAND(BlendFuncCommand::new)), (BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new)), (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), + (BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new)), (UPDATE_CMD_UBO_COMMAND = REGISTER_COMMAND(UpdateCMDUBOCommand::new)), (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), }; @@ -69,6 +71,7 @@ public final class CommandBuffer { private final BlendFuncCommand BLENDED_FUNC_COMMAND; private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; + private final BlitFrameBufferCommand BLIT_FRAME_BUFFER_COMMAND; private final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; @@ -228,6 +231,31 @@ public void Viewport(int x, int y, int width, int height) { VIEWPORT_COMMAND.write(); } + public void BlitFramebuffer( + int srcFbo, + int resolveFbo, + int dstFbo, + int srcWidth, + int srcHeight, + int dstX, + int dstY, + int dstWidth, + int dstHeight, + int filter + ) { + BLIT_FRAME_BUFFER_COMMAND.srcFbo = srcFbo; + BLIT_FRAME_BUFFER_COMMAND.resolveFbo = resolveFbo; + BLIT_FRAME_BUFFER_COMMAND.dstFbo = dstFbo; + BLIT_FRAME_BUFFER_COMMAND.srcWidth = srcWidth; + BLIT_FRAME_BUFFER_COMMAND.srcHeight = srcHeight; + BLIT_FRAME_BUFFER_COMMAND.dstX = dstX; + BLIT_FRAME_BUFFER_COMMAND.dstY = dstY; + BLIT_FRAME_BUFFER_COMMAND.dstWidth = dstWidth; + BLIT_FRAME_BUFFER_COMMAND.dstHeight = dstHeight; + BLIT_FRAME_BUFFER_COMMAND.filter = filter; + BLIT_FRAME_BUFFER_COMMAND.write(); + } + public void Enable(int capability) { Toggle(capability, true); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java new file mode 100644 index 0000000000..ea2d0d4068 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java @@ -0,0 +1,94 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL30.GL_DRAW_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.glBindFramebuffer; +import static org.lwjgl.opengl.GL30.glBlitFramebuffer; + + +public class BlitFrameBufferCommand extends BaseCommand { + public int srcFbo; + public int resolveFbo; // optional intermediate FBO for MSAA resolve + public int dstFbo; + + public int srcWidth; + public int srcHeight; + + public int dstX; + public int dstY; + public int dstWidth; + public int dstHeight; + + public int filter; // GL_NEAREST or GL_LINEAR + + public BlitFrameBufferCommand() { super(false, true); } + + @Override + protected void execute() { + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); + + if (resolveFbo != 0) { + // Optional MSAA resolve step + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFbo); + glBlitFramebuffer( + 0, 0, srcWidth, srcHeight, + 0, 0, srcWidth, srcHeight, + GL_COLOR_BUFFER_BIT, filter + ); + glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFbo); + } + + // Blit from (resolved) source to destination framebuffer + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); + glBlitFramebuffer( + 0, 0, srcWidth, srcHeight, + dstX, dstY, + dstX + dstWidth, dstY + dstHeight, + GL_COLOR_BUFFER_BIT, filter + ); + } + + @Override + protected void doWrite() { + write32(srcFbo); + write32(resolveFbo); + write32(dstFbo); + write32(srcWidth); + write32(srcHeight); + write32(dstX); + write32(dstY); + write32(dstWidth); + write32(dstHeight); + write32(filter); + } + + @Override + protected void doRead() { + srcFbo = read32(); + resolveFbo = read32(); + dstFbo = read32(); + srcWidth = read32(); + srcHeight = read32(); + dstX = read32(); + dstY = read32(); + dstWidth = read32(); + dstHeight = read32(); + filter = read32(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("BlitFrameBufferCommand(") + .append("srcFbo=").append(srcFbo).append(", ") + .append("resolveFbo=").append(resolveFbo).append(", ") + .append("dstFbo=").append(dstFbo).append(", ") + .append("srcSize=").append(srcWidth).append("x").append(srcHeight).append(", ") + .append("dstRect=[").append(dstX).append(", ").append(dstY) + .append(", ").append(dstWidth).append(", ").append(dstHeight).append("], ") + .append("filter=").append(filter) + .append(");"); + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index d70ed80880..763cf8ca7c 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -158,6 +158,8 @@ public class ZoneRenderer implements Renderer { private final CommandBuffer directionalPassCmd = new CommandBuffer(); private final CommandBuffer directionalDrawCmd = new CommandBuffer(); + private final CommandBuffer backbufferCmd = new CommandBuffer(); + private VAO.VAOList vaoO; private VAO.VAOList vaoA; private VAO.VAOList vaoPO; @@ -1274,38 +1276,21 @@ public void draw(int overlayColor) { } if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene); - if (plugin.fboSceneResolve != 0) { - // Blit from the scene FBO to the multisample resolve FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboSceneResolve); - glBlitFramebuffer( - 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], - 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], - GL_COLOR_BUFFER_BIT, GL_NEAREST - ); - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneResolve); - } - - // Blit from the resolved FBO to the default FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - glBlitFramebuffer( - 0, - 0, - plugin.sceneResolution[0], - plugin.sceneResolution[1], - plugin.sceneViewport[0], - plugin.sceneViewport[1], - plugin.sceneViewport[0] + plugin.sceneViewport[2], - plugin.sceneViewport[1] + plugin.sceneViewport[3], - GL_COLOR_BUFFER_BIT, - config.sceneScalingMode().glFilter - ); + backbufferCmd.reset(); + backbufferCmd.BlitFramebuffer( + plugin.fboScene, plugin.fboSceneResolve, plugin.awtContext.getFramebuffer(false), + plugin.sceneResolution[0], plugin.sceneResolution[1], + plugin.sceneViewport[0], plugin.sceneViewport[1], + plugin.sceneViewport[2], plugin.sceneViewport[3], + config.sceneScalingMode().glFilter); } else { - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); + backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + backbufferCmd.ClearColor(0, 0, 0, 1); } + backbufferCmd.submit(); + + // TODO: Move drawUI over to a command buffer plugin.drawUi(overlayColor); try { From fc350ab41808bd68c3d6773fc6a2496b3bb361b2 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:08:07 +0100 Subject: [PATCH 20/50] Replaced UpdateCMDUBOCommand with SetUniformBufferPropertyCommand --- .../opengl/commandbuffer/CommandBuffer.java | 29 +++-- .../SetUniformBufferPropertyCommand.java | 113 ++++++++++++++++++ .../commands/UpdateCMDUBOCommand.java | 59 --------- .../hd/opengl/uniforms/UniformBuffer.java | 1 + src/main/java/rs117/hd/renderer/zone/VAO.java | 2 +- .../java/rs117/hd/renderer/zone/Zone.java | 20 ++-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 33 ++--- 7 files changed, 160 insertions(+), 97 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java delete mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 0c53fd3ba5..13aa6a72e6 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -17,12 +17,13 @@ import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; import rs117.hd.opengl.commandbuffer.commands.ExecuteCommandBufferCommand; import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.SetUniformBufferPropertyCommand; import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; -import rs117.hd.opengl.commandbuffer.commands.UpdateCMDUBOCommand; import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.uniforms.UBOCommandBuffer; +import rs117.hd.opengl.uniforms.UniformBuffer; @Slf4j public final class CommandBuffer { @@ -54,7 +55,7 @@ public final class CommandBuffer { (BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new)), (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), (BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new)), - (UPDATE_CMD_UBO_COMMAND = REGISTER_COMMAND(UpdateCMDUBOCommand::new)), + (SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new)), (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), }; @@ -72,7 +73,7 @@ public final class CommandBuffer { private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; private final BlitFrameBufferCommand BLIT_FRAME_BUFFER_COMMAND; - private final UpdateCMDUBOCommand UPDATE_CMD_UBO_COMMAND; + private final SetUniformBufferPropertyCommand SET_UNIFORM_BUFFER_PROPERTY_COMMAND; private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; @@ -106,18 +107,20 @@ public void ensureCapacity(int numLongs) { cmd = Arrays.copyOf(cmd, cmd.length * 2); } - public void SetBaseOffset(int x, int y, int z) { - UPDATE_CMD_UBO_COMMAND.isBaseOffset = true; - UPDATE_CMD_UBO_COMMAND.x = x; - UPDATE_CMD_UBO_COMMAND.y = y; - UPDATE_CMD_UBO_COMMAND.z = z; - UPDATE_CMD_UBO_COMMAND.write(); + public void SetUniformProperty(UniformBuffer.Property property, boolean upload, int... values) { + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.property = property; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.upload = upload; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.intValues = values; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.isFloat = false; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); } - public void SetWorldViewIndex(int index) { - UPDATE_CMD_UBO_COMMAND.isBaseOffset = false; - UPDATE_CMD_UBO_COMMAND.worldViewId = index; - UPDATE_CMD_UBO_COMMAND.write(); + public void SetUniformProperty(UniformBuffer.Property property, boolean upload, float... values) { + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.property = property; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.upload = upload; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.floatValues = values; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.isFloat = true; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); } public void BindVertexArray(int vao) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java new file mode 100644 index 0000000000..57ea8f64e9 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java @@ -0,0 +1,113 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.uniforms.UniformBuffer; + +public class SetUniformBufferPropertyCommand extends BaseCommand { + + public UniformBuffer.Property property; + public int[] intValues; + public float[] floatValues; + public boolean isFloat; + public boolean upload; + + private int stagingSize; + private int[] stagingIntValues; + private float[] stagingFloatValues; + + public SetUniformBufferPropertyCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeObject(property); + writeFlag(isFloat); + writeFlag(upload); + if(isFloat) { + write32(floatValues.length); + for(float f : floatValues) { + write32F(f); + } + } else { + write32(intValues.length); + for(int i : intValues) { + write32(i); + } + } + floatValues = null; + intValues = null; + } + + @Override + protected void doRead() { + property = readObject(); + isFloat = readFlag(); + upload = readFlag(); + stagingSize = read32(); + if(isFloat) { + if(stagingFloatValues == null || stagingFloatValues.length < stagingSize) { + stagingFloatValues = new float[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + floatValues[i] = read32F(); + } + } else { + if(stagingIntValues == null || stagingIntValues.length < stagingSize) { + stagingIntValues = new int[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingIntValues[i] = read32(); + } + } + } + + @Override + protected void execute() { + if(isFloat) { + switch (stagingSize) { + case 1: + property.set( stagingFloatValues[0] ); + break; + case 2: + property.set( stagingFloatValues[0], stagingFloatValues[1] ); + break; + case 3: + property.set( stagingFloatValues[0], stagingFloatValues[1], stagingFloatValues[2] ); + break; + case 4: + property.set( stagingFloatValues[0], stagingFloatValues[1], stagingFloatValues[2], stagingFloatValues[3] ); + break; + default: + property.set( stagingFloatValues ); + break; + } + } else { + switch (stagingSize) { + case 1: + property.set( stagingIntValues[0] ); + break; + case 2: + property.set( stagingIntValues[0], stagingIntValues[1] ); + break; + case 3: + property.set( stagingIntValues[0], stagingIntValues[1], stagingIntValues[2] ); + break; + case 4: + property.set( stagingIntValues[0], stagingIntValues[1], stagingIntValues[2], stagingIntValues[3] ); + break; + default: + property.set( stagingIntValues ); + break; + } + } + + if(upload) { + // TODO: Upload should only be done once before the next draw call + property.getOwner().upload(); + } + } + + @Override + protected void print(StringBuilder sb) { + // TODO: Something useful + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java deleted file mode 100644 index cced2a5369..0000000000 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UpdateCMDUBOCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -package rs117.hd.opengl.commandbuffer.commands; - -import rs117.hd.opengl.commandbuffer.BaseCommand; - -public final class UpdateCMDUBOCommand extends BaseCommand { - public boolean isBaseOffset; - public int x, y, z; - public int worldViewId; - - @Override - protected void doWrite() { - writeFlag(isBaseOffset); - if(isBaseOffset) { - write32(x); - write32(y); - write32(z); - } else { - write32(worldViewId); - } - } - - @Override - protected void doRead() { - isBaseOffset = readFlag(); - if(isBaseOffset) { - x = read32(); - y = read32(); - z = read32(); - } else { - worldViewId = read32(); - } - } - - @Override - public void execute() { - if(isBaseOffset) { - getUBOCommandBuffer().sceneBase.set(x, y, z); - } else { - getUBOCommandBuffer().worldViewIndex.set(worldViewId); - } - } - - @Override - public void print(StringBuilder sb) { - if(isBaseOffset) { - sb.append("UBOCommandBuffer::sceneBase = ("); - sb.append(x); - sb.append(", "); - sb.append(y); - sb.append(", "); - sb.append(z); - sb.append(");"); - } else { - sb.append("UBOCommandBuffer::worldViewIndex = ("); - sb.append(worldViewId); - sb.append(");"); - } - } -} diff --git a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java index c5c11964c6..ca4c90a0c2 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java @@ -42,6 +42,7 @@ protected enum PropertyType { @AllArgsConstructor @RequiredArgsConstructor public static class Property { + @Getter private UniformBuffer owner; private int position; private int offset = -1; diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 0b4af634b2..01fbf99aa4 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -103,7 +103,7 @@ void draw(ZoneRenderer renderer, CommandBuffer cmd) { int count = end - start; - cmd.SetWorldViewIndex(renderer.uboWorldViews.getIndex(scene)); + cmd.SetUniformProperty(renderer.uboCommandBuffer.worldViewIndex, true, renderer.uboWorldViews.getIndex(scene)); cmd.BindVertexArray(vao); cmd.DrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VAO.VERT_SIZE / 4)); diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index afbfff97a7..633ba845fe 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -12,6 +12,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.uniforms.UBOCommandBuffer; import rs117.hd.scene.SceneContext; import rs117.hd.scene.materials.Material; import rs117.hd.utils.Camera; @@ -191,7 +192,7 @@ private void convertForDraw(int vertSize) { glDrawLength = Arrays.copyOfRange(drawEnd, 0, drawIdx); } - void renderOpaque(CommandBuffer cmd, int zx, int zz, int minLevel, int currentLevel, int maxLevel, Set hiddenRoofIds) { + void renderOpaque(UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, int minLevel, int currentLevel, int maxLevel, Set hiddenRoofIds) { drawIdx = 0; for (int level = minLevel; level <= maxLevel; ++level) { @@ -236,7 +237,7 @@ void renderOpaque(CommandBuffer cmd, int zx, int zz, int minLevel, int currentLe convertForDraw(VERT_SIZE); - cmd.SetBaseOffset(zx << 10, 0, zz << 10); + cmd.SetUniformProperty(ubo.sceneBase, true, zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); if(glDrawOffset.length > 1) { cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); @@ -245,7 +246,7 @@ void renderOpaque(CommandBuffer cmd, int zx, int zz, int minLevel, int currentLe } } - void renderOpaqueLevel(CommandBuffer cmd, int zx, int zz, int level) { + void renderOpaqueLevel(UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, int level) { drawIdx = 0; // draw the specific level @@ -256,7 +257,7 @@ void renderOpaqueLevel(CommandBuffer cmd, int zx, int zz, int level) { convertForDraw(VERT_SIZE); - cmd.SetBaseOffset(zx << 10, 0, zz << 10); + cmd.SetUniformProperty(ubo.sceneBase, true, zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); if(glDrawOffset.length > 1) { cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); @@ -488,6 +489,7 @@ void alphaSort(int zx, int zz, Camera camera) { } void renderAlpha( + UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, @@ -523,7 +525,7 @@ void renderAlpha( continue; if (lastVao != m.vao || lastzx != (zx - m.zofx) || lastzz != (zz - m.zofz)) - flush(cmd); + flush(ubo, cmd); lastVao = m.vao; lastzx = zx - m.zofx; @@ -614,17 +616,17 @@ void renderAlpha( } } - flush(cmd); + flush(ubo, cmd); if(!isShadow) { cmd.DepthMask(true); } } - private void flush(CommandBuffer cmd) { + private void flush(UBOCommandBuffer ubo, CommandBuffer cmd) { if (lastDrawMode == TEMP) { - cmd.SetBaseOffset(0, 0, 0); + cmd.SetUniformProperty(ubo.sceneBase, true, 0, 0, 0); } else { - cmd.SetBaseOffset(lastzx << 10, 0, lastzz << 10); + cmd.SetUniformProperty(ubo.sceneBase, true, lastzx << 10, 0, lastzz << 10); } if (lastDrawMode == STATIC) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 763cf8ca7c..f0d0757f08 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -215,7 +215,7 @@ WorldViewContext context(int worldViewId) { private boolean sceneFboValid; - private final UBOCommandBuffer uboCommandBuffer = new UBOCommandBuffer(); + public final UBOCommandBuffer uboCommandBuffer = new UBOCommandBuffer(); public final UBOWorldViews uboWorldViews = new UBOWorldViews(MAX_WORLDVIEWS); private final WorldViewContext root = new WorldViewContext(null, NUM_ZONES, NUM_ZONES); @@ -356,8 +356,8 @@ public void preSceneDraw( vaoO.addRange(topLevel); vaoPO.addRange(topLevel); vaoPOShadow.addRange(topLevel); - sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); - directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); } } @@ -748,8 +748,8 @@ private void preSceneDrawTopLevel( sceneDrawCmd.reset(); directionalDrawCmd.reset(); - sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); - directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); checkGLErrors(); } @@ -760,8 +760,8 @@ public void postSceneDraw(Scene scene) { if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { - sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); - directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); } } @@ -894,13 +894,14 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int int offset = ctx.sceneContext.sceneOffset >> 3; if (z.inSceneFrustum) { - sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); - z.renderOpaque(sceneDrawCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + z.renderOpaque(uboCommandBuffer, sceneDrawCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); } if (z.inShadowFrustum) { - directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); z.renderOpaque( + uboCommandBuffer, directionalDrawCmd, zx - offset, zz - offset, @@ -930,11 +931,11 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; if (renderWater || hasAlpha) - sceneDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); int offset = ctx.sceneContext.sceneOffset >> 3; if (renderWater) - z.renderOpaqueLevel(sceneDrawCmd, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); + z.renderOpaqueLevel(uboCommandBuffer, sceneDrawCmd, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); if (!hasAlpha) return; @@ -946,6 +947,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i if (z.inSceneFrustum) { z.renderAlpha( + uboCommandBuffer, sceneDrawCmd, zx - offset, zz - offset, @@ -960,8 +962,9 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i } if (z.inShadowFrustum) { - directionalDrawCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); z.renderAlpha( + uboCommandBuffer, directionalDrawCmd, zx - offset, zz - offset, @@ -992,8 +995,8 @@ public void drawPass(Projection projection, Scene scene, int pass) { vaoPOShadow.addRange(scene); if (scene.getWorldViewId() == -1) { - sceneDrawCmd.SetBaseOffset(0, 0, 0); - directionalDrawCmd.SetBaseOffset(0, 0, 0); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, true, 0, 0, 0); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, true, 0, 0, 0); // Draw opaque vaoO.unmap(); From aff6bf0c32dca9fda90bf64e8a4ef89f126a2195 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:27:18 +0100 Subject: [PATCH 21/50] UBOs are now tracked and uploaded before the next drawcall generically --- .../hd/opengl/commandbuffer/BaseCommand.java | 8 ++- .../opengl/commandbuffer/CommandBuffer.java | 49 ++++++++++++++----- .../SetUniformBufferPropertyCommand.java | 9 +--- src/main/java/rs117/hd/renderer/zone/VAO.java | 2 +- .../java/rs117/hd/renderer/zone/Zone.java | 8 +-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 27 +++++----- 6 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index 012cc3b01a..cf3935eee2 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -2,7 +2,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import rs117.hd.opengl.uniforms.UBOCommandBuffer; +import rs117.hd.opengl.uniforms.UniformBuffer; @Slf4j public abstract class BaseCommand { @@ -30,10 +30,6 @@ protected BaseCommand(boolean isDrawCall, boolean isGLCommand) { protected BaseCommand() { this(false, false); } - protected UBOCommandBuffer getUBOCommandBuffer() { - return owner.uboCommandBuffer; - } - protected abstract void execute(); protected abstract void print(StringBuilder sb); @@ -45,6 +41,8 @@ protected final void write() { protected abstract void doWrite(); protected abstract void doRead(); + protected final void markUniformBufferDirty(UniformBuffer buffer) { owner.markUniformBufferDirty(buffer); } + protected final void write1(int value) { owner.writeBits(value & 0x1L, 1); } protected final void write8(int value) { owner.writeBits(value & 0xFFL, 8); } protected final void write16(int value) { owner.writeBits(value & 0xFFFFL, 16); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 13aa6a72e6..2c9c0a3002 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -22,7 +22,6 @@ import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; import rs117.hd.opengl.shader.ShaderProgram; -import rs117.hd.opengl.uniforms.UBOCommandBuffer; import rs117.hd.opengl.uniforms.UniformBuffer; @Slf4j @@ -87,10 +86,11 @@ private T REGISTER_COMMAND(ICreateCommand createComma return newCommand; } - public UBOCommandBuffer uboCommandBuffer; - private long[] cmd = new long[1 << 20]; // ~1 million calls + private UniformBuffer[] pendingUBOUploads = new UniformBuffer[100]; + private int pendingUBOUploadsCount = 0; + private Object[] objects = new Object[100]; private int objectCount = 0; @@ -107,17 +107,15 @@ public void ensureCapacity(int numLongs) { cmd = Arrays.copyOf(cmd, cmd.length * 2); } - public void SetUniformProperty(UniformBuffer.Property property, boolean upload, int... values) { + public void SetUniformProperty(UniformBuffer.Property property, int... values) { SET_UNIFORM_BUFFER_PROPERTY_COMMAND.property = property; - SET_UNIFORM_BUFFER_PROPERTY_COMMAND.upload = upload; SET_UNIFORM_BUFFER_PROPERTY_COMMAND.intValues = values; SET_UNIFORM_BUFFER_PROPERTY_COMMAND.isFloat = false; SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); } - public void SetUniformProperty(UniformBuffer.Property property, boolean upload, float... values) { + public void SetUniformProperty(UniformBuffer.Property property, float... values) { SET_UNIFORM_BUFFER_PROPERTY_COMMAND.property = property; - SET_UNIFORM_BUFFER_PROPERTY_COMMAND.upload = upload; SET_UNIFORM_BUFFER_PROPERTY_COMMAND.floatValues = values; SET_UNIFORM_BUFFER_PROPERTY_COMMAND.isFloat = true; SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); @@ -299,8 +297,15 @@ public void submit() { assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; BaseCommand command = REGISTERED_COMMANDS[type]; - if (command.isDrawCall() && uboCommandBuffer != null && uboCommandBuffer.isDirty()) { - uboCommandBuffer.upload(); + + if (command.isDrawCall()) { + for(int i = 0; i < pendingUBOUploadsCount; i++) { + if(pendingUBOUploads[i].isDirty()) { + pendingUBOUploads[i].upload(); + pendingUBOUploads[i] = null; + } + } + pendingUBOUploadsCount = 0; } command.doRead(); @@ -313,19 +318,34 @@ public void submit() { } } + protected void markUniformBufferDirty(UniformBuffer buffer) { + for(int i = 0; i < pendingUBOUploadsCount; i++) { + if(pendingUBOUploads[i] == buffer) { + return; + } + } + pendingUBOUploads[pendingUBOUploadsCount++] = buffer; + } + protected void writeObject(Object object) { + for(int i = 0; i < objectCount; i++) { + if(objects[i] == object) { + writeBits(i, 32); + return; + } + } + if (objectCount >= objects.length) { objects = Arrays.copyOf(objects, objects.length * 2); } + writeBits(objectCount, 32); objects[objectCount++] = object; } protected T readObject() { int index = (int)readBits(32); - Object object = objects[index]; - objects[index] = null; - return (T) object; + return (T) objects[index]; } protected long readBits(int numBits) { @@ -403,7 +423,10 @@ public void reset() { readBitHead = 0; // Objects need to be cleared to avoid holding onto a reference and preventing garbage collection - Arrays.fill(objects, null); + Arrays.fill(objects, 0, objectCount, null); objectCount = 0; + + Arrays.fill(pendingUBOUploads, 0, pendingUBOUploadsCount, null); + pendingUBOUploadsCount = 0; } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java index 57ea8f64e9..0a3ec6d444 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java @@ -9,7 +9,6 @@ public class SetUniformBufferPropertyCommand extends BaseCommand { public int[] intValues; public float[] floatValues; public boolean isFloat; - public boolean upload; private int stagingSize; private int[] stagingIntValues; @@ -21,7 +20,6 @@ public class SetUniformBufferPropertyCommand extends BaseCommand { protected void doWrite() { writeObject(property); writeFlag(isFloat); - writeFlag(upload); if(isFloat) { write32(floatValues.length); for(float f : floatValues) { @@ -41,7 +39,6 @@ protected void doWrite() { protected void doRead() { property = readObject(); isFloat = readFlag(); - upload = readFlag(); stagingSize = read32(); if(isFloat) { if(stagingFloatValues == null || stagingFloatValues.length < stagingSize) { @@ -99,11 +96,7 @@ protected void execute() { break; } } - - if(upload) { - // TODO: Upload should only be done once before the next draw call - property.getOwner().upload(); - } + markUniformBufferDirty(property.getOwner()); } @Override diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 01fbf99aa4..089800989e 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -103,7 +103,7 @@ void draw(ZoneRenderer renderer, CommandBuffer cmd) { int count = end - start; - cmd.SetUniformProperty(renderer.uboCommandBuffer.worldViewIndex, true, renderer.uboWorldViews.getIndex(scene)); + cmd.SetUniformProperty(renderer.uboCommandBuffer.worldViewIndex, renderer.uboWorldViews.getIndex(scene)); cmd.BindVertexArray(vao); cmd.DrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VAO.VERT_SIZE / 4)); diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 633ba845fe..0e0e8be690 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -237,7 +237,7 @@ void renderOpaque(UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, int m convertForDraw(VERT_SIZE); - cmd.SetUniformProperty(ubo.sceneBase, true, zx << 10, 0, zz << 10); + cmd.SetUniformProperty(ubo.sceneBase, zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); if(glDrawOffset.length > 1) { cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); @@ -257,7 +257,7 @@ void renderOpaqueLevel(UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, convertForDraw(VERT_SIZE); - cmd.SetUniformProperty(ubo.sceneBase, true, zx << 10, 0, zz << 10); + cmd.SetUniformProperty(ubo.sceneBase, zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); if(glDrawOffset.length > 1) { cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); @@ -624,9 +624,9 @@ void renderAlpha( private void flush(UBOCommandBuffer ubo, CommandBuffer cmd) { if (lastDrawMode == TEMP) { - cmd.SetUniformProperty(ubo.sceneBase, true, 0, 0, 0); + cmd.SetUniformProperty(ubo.sceneBase, 0, 0, 0); } else { - cmd.SetUniformProperty(ubo.sceneBase, true, lastzx << 10, 0, lastzz << 10); + cmd.SetUniformProperty(ubo.sceneBase, lastzx << 10, 0, lastzz << 10); } if (lastDrawMode == STATIC) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index f0d0757f08..e6145eea98 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -248,9 +248,6 @@ public void initialize() { uboCommandBuffer.initialize(UNIFORM_BLOCK_COMMAND_BUFFER); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); - - sceneDrawCmd.uboCommandBuffer = uboCommandBuffer; - directionalDrawCmd.uboCommandBuffer = uboCommandBuffer; } @Override @@ -356,8 +353,8 @@ public void preSceneDraw( vaoO.addRange(topLevel); vaoPO.addRange(topLevel); vaoPOShadow.addRange(topLevel); - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); } } @@ -748,8 +745,8 @@ private void preSceneDrawTopLevel( sceneDrawCmd.reset(); directionalDrawCmd.reset(); - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); checkGLErrors(); } @@ -760,8 +757,8 @@ public void postSceneDraw(Scene scene) { if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(null)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); } } @@ -894,12 +891,12 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int int offset = ctx.sceneContext.sceneOffset >> 3; if (z.inSceneFrustum) { - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderOpaque(uboCommandBuffer, sceneDrawCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); } if (z.inShadowFrustum) { - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderOpaque( uboCommandBuffer, directionalDrawCmd, @@ -931,7 +928,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; if (renderWater || hasAlpha) - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); int offset = ctx.sceneContext.sceneOffset >> 3; if (renderWater) @@ -962,7 +959,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i } if (z.inShadowFrustum) { - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, true, uboWorldViews.getIndex(scene)); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderAlpha( uboCommandBuffer, directionalDrawCmd, @@ -995,8 +992,8 @@ public void drawPass(Projection projection, Scene scene, int pass) { vaoPOShadow.addRange(scene); if (scene.getWorldViewId() == -1) { - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, true, 0, 0, 0); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, true, 0, 0, 0); + sceneDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); + directionalDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); // Draw opaque vaoO.unmap(); From 4a52669a234c130961e781f95ffd6ec13839f9ec Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 19:44:06 +0100 Subject: [PATCH 22/50] Tweaks --- .../hd/opengl/commandbuffer/CommandBuffer.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 2c9c0a3002..335a5940df 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -24,6 +24,8 @@ import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.uniforms.UniformBuffer; +import static rs117.hd.utils.MathUtils.*; + @Slf4j public final class CommandBuffer { private static final int BITS_PER_WORD = 64; @@ -330,7 +332,7 @@ protected void markUniformBufferDirty(UniformBuffer buffer) { protected void writeObject(Object object) { for(int i = 0; i < objectCount; i++) { if(objects[i] == object) { - writeBits(i, 32); + writeBits(i, 8); return; } } @@ -339,12 +341,13 @@ protected void writeObject(Object object) { objects = Arrays.copyOf(objects, objects.length * 2); } - writeBits(objectCount, 32); + assert objectCount < 255 : "Too many objects in command buffer, limit needs increasing: " + objectCount + " > 255"; + writeBits(objectCount, 8); objects[objectCount++] = object; } protected T readObject() { - int index = (int)readBits(32); + int index = (int)readBits(8); return (T) objects[index]; } @@ -363,7 +366,7 @@ protected long readBits(int numBits) { long result = 0; int shift = 0; while (numBits > 0) { - final int bitsToRead = Math.min(BITS_PER_WORD - readBitHead, numBits); + final int bitsToRead = min(BITS_PER_WORD - readBitHead, numBits); final long bits = (word >>> readBitHead) & MASKS[bitsToRead]; result |= (bits << shift); @@ -395,7 +398,7 @@ protected void writeBits(long value, int numBits) { long word = cmd[writeHead]; while (numBits > 0) { - final int bitsToWrite = Math.min(BITS_PER_WORD - writeBitHead, numBits); + final int bitsToWrite = min(BITS_PER_WORD - writeBitHead, numBits); final long bits = value & MASKS[bitsToWrite]; word |= bits << writeBitHead; From 4a8a3bce8abab30bcf0cd4793ee84a4b067265ad Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:01:45 +0100 Subject: [PATCH 23/50] UI & Overlays now render using CommandBuffers --- src/main/java/rs117/hd/HdPlugin.java | 39 +++---- .../opengl/commandbuffer/CommandBuffer.java | 17 +++ .../commands/SetShaderUniformCommand.java | 101 ++++++++++++++++++ .../SetUniformBufferPropertyCommand.java | 2 +- .../rs117/hd/opengl/shader/ShaderProgram.java | 2 +- .../hd/overlays/GammaCalibrationOverlay.java | 5 +- .../java/rs117/hd/overlays/ShaderOverlay.java | 34 +++--- .../hd/renderer/legacy/LegacyRenderer.java | 2 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 5 +- 9 files changed, 163 insertions(+), 44 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 20b237ad98..0c95ff1545 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -81,6 +81,7 @@ import rs117.hd.config.ShadowMode; import rs117.hd.config.VanillaShadowMode; import rs117.hd.opengl.AsyncUICopy; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.ShadowShaderProgram; @@ -1367,7 +1368,7 @@ public void prepareInterfaceTexture() { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } - public void drawUi(int overlayColor) { + public void drawUi(CommandBuffer cmd, int overlayColor) { if (uiResolution == null || developerTools.isHideUiEnabled() && hasLoggedIn) return; @@ -1377,19 +1378,19 @@ public void drawUi(int overlayColor) { frameTimer.begin(Timer.RENDER_UI); - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); // Disable alpha writes, just in case the default FBO has an alpha channel - glColorMask(true, true, true, false); + cmd.ColorMask(true, true, true, false); + + cmd.Viewport(0, 0, uiResolution[0], uiResolution[1]); - glViewport(0, 0, actualUiResolution[0], actualUiResolution[1]); + tiledLightingOverlay.render(cmd); - tiledLightingOverlay.render(); + cmd.SetShaderProgram(uiProgram); - uiProgram.use(); - uboUI.sourceDimensions.set(uiResolution); - uboUI.targetDimensions.set(actualUiResolution); - uboUI.alphaOverlay.set(ColorUtils.srgba(overlayColor)); - uboUI.upload(); + cmd.SetUniformProperty(uboUI.sourceDimensions, uiResolution); + cmd.SetUniformProperty(uboUI.targetDimensions, actualUiResolution); + cmd.SetUniformProperty(uboUI.alphaOverlay, ColorUtils.srgba(overlayColor)); // Set the sampling function used when stretching the UI. // This is probably better done with sampler objects instead of texture parameters, but this is easier and likely more portable. @@ -1401,18 +1402,18 @@ public void drawUi(int overlayColor) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, function); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, function); - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - glBindVertexArray(vaoTri); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.BindVertexArray(vaoTri); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); - shadowMapOverlay.render(); - gammaCalibrationOverlay.render(); + shadowMapOverlay.render(cmd); + gammaCalibrationOverlay.render(cmd); // Reset - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - glDisable(GL_BLEND); - glColorMask(true, true, true, true); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.Disable(GL_BLEND); + cmd.ColorMask(true, true, true, true); frameTimer.end(Timer.RENDER_UI); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 335a5940df..cb4f05a660 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -17,6 +17,7 @@ import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; import rs117.hd.opengl.commandbuffer.commands.ExecuteCommandBufferCommand; import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.SetShaderUniformCommand; import rs117.hd.opengl.commandbuffer.commands.SetUniformBufferPropertyCommand; import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; @@ -57,6 +58,7 @@ public final class CommandBuffer { (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), (BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new)), (SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new)), + (SET_SHADER_PROPERTY_COMMAND = REGISTER_COMMAND(SetShaderUniformCommand::new)), (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), }; @@ -75,6 +77,7 @@ public final class CommandBuffer { private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; private final BlitFrameBufferCommand BLIT_FRAME_BUFFER_COMMAND; private final SetUniformBufferPropertyCommand SET_UNIFORM_BUFFER_PROPERTY_COMMAND; + private final SetShaderUniformCommand SET_SHADER_PROPERTY_COMMAND; private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; @@ -123,6 +126,20 @@ public void SetUniformProperty(UniformBuffer.Property property, float... values) SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); } + public void SetUniformProperty(ShaderProgram.UniformProperty property, int... values) { + SET_SHADER_PROPERTY_COMMAND.property = property; + SET_SHADER_PROPERTY_COMMAND.intValues = values; + SET_SHADER_PROPERTY_COMMAND.isFloat = false; + SET_SHADER_PROPERTY_COMMAND.write(); + } + + public void SetUniformProperty(ShaderProgram.UniformProperty property, float... values) { + SET_SHADER_PROPERTY_COMMAND.property = property; + SET_SHADER_PROPERTY_COMMAND.floatValues = values; + SET_SHADER_PROPERTY_COMMAND.isFloat = true; + SET_SHADER_PROPERTY_COMMAND.write(); + } + public void BindVertexArray(int vao) { BIND_VERTEX_ARRAY_COMMAND.vao = vao; BIND_VERTEX_ARRAY_COMMAND.write(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java new file mode 100644 index 0000000000..2e86cb1e1d --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java @@ -0,0 +1,101 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.shader.ShaderProgram; + +public class SetShaderUniformCommand extends BaseCommand { + + public ShaderProgram.UniformProperty property; + public int[] intValues; + public float[] floatValues; + public boolean isFloat; + + private int stagingSize; + private int[] stagingIntValues; + private float[] stagingFloatValues; + + @Override + protected void doWrite() { + writeObject(property); + writeFlag(isFloat); + if(isFloat) { + write32(floatValues.length); + for(float f : floatValues) { + write32F(f); + } + } else { + write32(intValues.length); + for(int i : intValues) { + write32(i); + } + } + floatValues = null; + intValues = null; + } + + @Override + protected void doRead() { + property = readObject(); + isFloat = readFlag(); + stagingSize = read32(); + if(isFloat) { + if(stagingFloatValues == null || stagingFloatValues.length < stagingSize) { + stagingFloatValues = new float[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingFloatValues[i] = read32F(); + } + } else { + if(stagingIntValues == null || stagingIntValues.length < stagingSize) { + stagingIntValues = new int[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingIntValues[i] = read32(); + } + } + } + + @Override + protected void execute() { + if(isFloat) { + if (property instanceof ShaderProgram.Uniform1f) { + ((ShaderProgram.Uniform1f) property).set(stagingFloatValues[0]); + } else if (property instanceof ShaderProgram.Uniform2f) { + ((ShaderProgram.Uniform2f) property).set(stagingFloatValues[0], stagingFloatValues[1]); + } else if (property instanceof ShaderProgram.Uniform3f) { + ((ShaderProgram.Uniform3f) property).set(stagingFloatValues[0], stagingFloatValues[1], stagingFloatValues[2]); + } else if (property instanceof ShaderProgram.Uniform4f) { + ((ShaderProgram.Uniform4f) property).set( + stagingFloatValues[0], + stagingFloatValues[1], + stagingFloatValues[2], + stagingFloatValues[3] + ); + } + } else { + if (property instanceof ShaderProgram.UniformBool) { + ((ShaderProgram.UniformBool) property).set(stagingIntValues[0] == 1); + } else if (property instanceof ShaderProgram.UniformTexture) { + ((ShaderProgram.UniformTexture) property).set(stagingIntValues[0]); + } else if (property instanceof ShaderProgram.Uniform1i) { + ((ShaderProgram.Uniform1i) property).set(stagingIntValues[0]); + } else if (property instanceof ShaderProgram.Uniform2i) { + ((ShaderProgram.Uniform2i) property).set(stagingIntValues[0], stagingIntValues[1]); + } else if (property instanceof ShaderProgram.Uniform3i) { + ((ShaderProgram.Uniform3i) property).set(stagingIntValues[0], stagingIntValues[1], stagingIntValues[2]); + } else if (property instanceof ShaderProgram.Uniform4i) { + ((ShaderProgram.Uniform4i) property).set( + stagingIntValues[0], + stagingIntValues[1], + stagingIntValues[2], + stagingIntValues[3] + ); + } + } + } + + @Override + protected void print(StringBuilder sb) { + // TODO: Something useful + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java index 0a3ec6d444..70f215533a 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java @@ -45,7 +45,7 @@ protected void doRead() { stagingFloatValues = new float[stagingSize]; } for(int i = 0; i < stagingSize; i++) { - floatValues[i] = read32F(); + stagingFloatValues[i] = read32F(); } } else { if(stagingIntValues == null || stagingIntValues.length < stagingSize) { diff --git a/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java index fbfba55fbf..1a4fbfdc4a 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java @@ -111,7 +111,7 @@ public void destroy() { uniformBlockMappings.clear(); } - private static class UniformProperty { + public static class UniformProperty { ShaderProgram program; String uniformName; int uniformIndex; diff --git a/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java b/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java index a2bafe33be..5ae81c2be5 100644 --- a/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java +++ b/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java @@ -7,6 +7,7 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import rs117.hd.HdPluginConfig; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPluginConfig.*; @@ -47,8 +48,8 @@ public boolean isHidden() { } @Override - protected void updateUniforms() { - shader.uniCalibrationTimer.set(getTimeout()); + protected void updateUniforms(CommandBuffer cmd) { + cmd.SetUniformProperty(shader.uniCalibrationTimer, getTimeout()); } @Subscribe diff --git a/src/main/java/rs117/hd/overlays/ShaderOverlay.java b/src/main/java/rs117/hd/overlays/ShaderOverlay.java index f01799b15b..f907a66ce0 100644 --- a/src/main/java/rs117/hd/overlays/ShaderOverlay.java +++ b/src/main/java/rs117/hd/overlays/ShaderOverlay.java @@ -47,6 +47,7 @@ import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayPosition; import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.shader.ShaderTemplate; @@ -372,10 +373,9 @@ public boolean isManageable() { return isMovable() || isResizable(); } - private void updateTransform() { - assert shader.isActive(); + private void updateTransform(CommandBuffer cmd) { if (isFullscreen()) { - shader.uniTransform.set(0, 0, 1, 1); + cmd.SetUniformProperty(shader.uniTransform, 0.0f, 0.0f, 1.0f, 1.0f); } else { int[] resolution = plugin.getUiResolution(); if (resolution == null) @@ -391,31 +391,31 @@ private void updateTransform() { rect[i] -= 1; } rect[1] *= -1; - shader.uniTransform.set(rect); + cmd.SetUniformProperty(shader.uniTransform, rect); } } - public void render() { + public void render(CommandBuffer cmd) { if (isHidden()) return; - shader.use(); - updateTransform(); - updateUniforms(); - renderShader(); + cmd.SetShaderProgram( shader); + updateTransform(cmd); + updateUniforms(cmd); + renderShader(cmd); } - protected void updateUniforms() {} + protected void updateUniforms(CommandBuffer cmd) {} - protected void renderShader() { - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + protected void renderShader(CommandBuffer cmd) { + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); if (fullscreen) { - glBindVertexArray(plugin.vaoTri); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.BindVertexArray(plugin.vaoTri); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); } else { - glBindVertexArray(plugin.vaoQuad); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + cmd.BindVertexArray(plugin.vaoQuad); + cmd.DrawArrays(GL_TRIANGLE_FAN, 0, 4); } } diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index c0b58dd72f..d519bfb537 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -1291,7 +1291,7 @@ public void draw(int overlayColor) { glClear(GL_COLOR_BUFFER_BIT); } - plugin.drawUi(overlayColor); + //plugin.drawUi(overlayColor); TODO: FIX ME! try { frameTimer.begin(Timer.SWAP_BUFFERS); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index e6145eea98..7286d36fe3 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -1288,10 +1288,9 @@ public void draw(int overlayColor) { backbufferCmd.ClearColor(0, 0, 0, 1); } - backbufferCmd.submit(); + plugin.drawUi(backbufferCmd, overlayColor); - // TODO: Move drawUI over to a command buffer - plugin.drawUi(overlayColor); + backbufferCmd.submit(); try { frameTimer.begin(Timer.SWAP_BUFFERS); From d1d05026370fa0af6c601b5ed5fe9c67bcebdbc4 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 20:05:19 +0100 Subject: [PATCH 24/50] Fixed Legacy --- src/main/java/rs117/hd/HdPlugin.java | 38 ++++++++++--------- .../hd/renderer/legacy/LegacyRenderer.java | 4 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 14 +++---- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 0c95ff1545..4256e93a45 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -345,6 +345,8 @@ public class HdPlugin extends Plugin { public final UBOLights uboLightsCulling = new UBOLights(true); public final UBOUI uboUI = new UBOUI(); + public final CommandBuffer backbufferCmd = new CommandBuffer(); + // Configs used frequently enough to be worth caching public boolean configGroundTextures; public boolean configGroundBlending; @@ -1368,7 +1370,7 @@ public void prepareInterfaceTexture() { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } - public void drawUi(CommandBuffer cmd, int overlayColor) { + public void drawUi(int overlayColor) { if (uiResolution == null || developerTools.isHideUiEnabled() && hasLoggedIn) return; @@ -1378,19 +1380,19 @@ public void drawUi(CommandBuffer cmd, int overlayColor) { frameTimer.begin(Timer.RENDER_UI); - cmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); // Disable alpha writes, just in case the default FBO has an alpha channel - cmd.ColorMask(true, true, true, false); + backbufferCmd.ColorMask(true, true, true, false); - cmd.Viewport(0, 0, uiResolution[0], uiResolution[1]); + backbufferCmd.Viewport(0, 0, uiResolution[0], uiResolution[1]); - tiledLightingOverlay.render(cmd); + tiledLightingOverlay.render(backbufferCmd); - cmd.SetShaderProgram(uiProgram); + backbufferCmd.SetShaderProgram(uiProgram); - cmd.SetUniformProperty(uboUI.sourceDimensions, uiResolution); - cmd.SetUniformProperty(uboUI.targetDimensions, actualUiResolution); - cmd.SetUniformProperty(uboUI.alphaOverlay, ColorUtils.srgba(overlayColor)); + backbufferCmd.SetUniformProperty(uboUI.sourceDimensions, uiResolution); + backbufferCmd.SetUniformProperty(uboUI.targetDimensions, actualUiResolution); + backbufferCmd.SetUniformProperty(uboUI.alphaOverlay, ColorUtils.srgba(overlayColor)); // Set the sampling function used when stretching the UI. // This is probably better done with sampler objects instead of texture parameters, but this is easier and likely more portable. @@ -1402,18 +1404,18 @@ public void drawUi(CommandBuffer cmd, int overlayColor) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, function); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, function); - cmd.Enable(GL_BLEND); - cmd.BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - cmd.BindVertexArray(vaoTri); - cmd.DrawArrays(GL_TRIANGLES, 0, 3); + backbufferCmd.Enable(GL_BLEND); + backbufferCmd.BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + backbufferCmd.BindVertexArray(vaoTri); + backbufferCmd.DrawArrays(GL_TRIANGLES, 0, 3); - shadowMapOverlay.render(cmd); - gammaCalibrationOverlay.render(cmd); + shadowMapOverlay.render(backbufferCmd); + gammaCalibrationOverlay.render(backbufferCmd); // Reset - cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - cmd.Disable(GL_BLEND); - cmd.ColorMask(true, true, true, true); + backbufferCmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + backbufferCmd.Disable(GL_BLEND); + backbufferCmd.ColorMask(true, true, true, true); frameTimer.end(Timer.RENDER_UI); } diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index d519bfb537..9c2510821f 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -1291,7 +1291,9 @@ public void draw(int overlayColor) { glClear(GL_COLOR_BUFFER_BIT); } - //plugin.drawUi(overlayColor); TODO: FIX ME! + plugin.backbufferCmd.reset(); + plugin.drawUi(overlayColor); + plugin.backbufferCmd.submit(); try { frameTimer.begin(Timer.SWAP_BUFFERS); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 7286d36fe3..cfa264c90d 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -158,8 +158,6 @@ public class ZoneRenderer implements Renderer { private final CommandBuffer directionalPassCmd = new CommandBuffer(); private final CommandBuffer directionalDrawCmd = new CommandBuffer(); - private final CommandBuffer backbufferCmd = new CommandBuffer(); - private VAO.VAOList vaoO; private VAO.VAOList vaoA; private VAO.VAOList vaoPO; @@ -1276,21 +1274,21 @@ public void draw(int overlayColor) { } if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { - backbufferCmd.reset(); - backbufferCmd.BlitFramebuffer( + plugin.backbufferCmd.reset(); + plugin.backbufferCmd.BlitFramebuffer( plugin.fboScene, plugin.fboSceneResolve, plugin.awtContext.getFramebuffer(false), plugin.sceneResolution[0], plugin.sceneResolution[1], plugin.sceneViewport[0], plugin.sceneViewport[1], plugin.sceneViewport[2], plugin.sceneViewport[3], config.sceneScalingMode().glFilter); } else { - backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - backbufferCmd.ClearColor(0, 0, 0, 1); + plugin.backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + plugin.backbufferCmd.ClearColor(0, 0, 0, 1); } - plugin.drawUi(backbufferCmd, overlayColor); + plugin.drawUi(overlayColor); - backbufferCmd.submit(); + plugin.backbufferCmd.submit(); try { frameTimer.begin(Timer.SWAP_BUFFERS); From 9184bd6fe7d66df876225ac00db85be6a4269a41 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:05:10 +0100 Subject: [PATCH 25/50] Added FrameTimer Support for CommandBuffers --- src/main/java/rs117/hd/HdPlugin.java | 7 +- .../opengl/commandbuffer/CommandBuffer.java | 66 +++++++++++++++---- .../commands/ExecuteCommandBufferCommand.java | 3 +- .../commands/FrameTimerCommand.java | 36 ++++++++++ .../commands/SwapBuffersCommand.java | 29 ++++++++ .../java/rs117/hd/overlays/FrameTimer.java | 7 ++ src/main/java/rs117/hd/overlays/Timer.java | 1 + .../rs117/hd/renderer/zone/ZoneRenderer.java | 47 ++++++------- 8 files changed, 152 insertions(+), 44 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 4256e93a45..0341e16106 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -345,7 +345,7 @@ public class HdPlugin extends Plugin { public final UBOLights uboLightsCulling = new UBOLights(true); public final UBOUI uboUI = new UBOUI(); - public final CommandBuffer backbufferCmd = new CommandBuffer(); + public final CommandBuffer backbufferCmd = new CommandBuffer().SetTimeExecution(true); // Configs used frequently enough to be worth caching public boolean configGroundTextures; @@ -1378,8 +1378,7 @@ public void drawUi(int overlayColor) { if (client.getGameState().getState() < GameState.LOADING.getState()) overlayColor = 0; - frameTimer.begin(Timer.RENDER_UI); - + backbufferCmd.BeginTimer(Timer.RENDER_UI); backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); // Disable alpha writes, just in case the default FBO has an alpha channel backbufferCmd.ColorMask(true, true, true, false); @@ -1417,7 +1416,7 @@ public void drawUi(int overlayColor) { backbufferCmd.Disable(GL_BLEND); backbufferCmd.ColorMask(true, true, true, true); - frameTimer.end(Timer.RENDER_UI); + backbufferCmd.EndTimer(Timer.RENDER_UI); } /** diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index cb4f05a660..4601773270 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -1,8 +1,8 @@ package rs117.hd.opengl.commandbuffer; import java.util.Arrays; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import net.runelite.rlawt.AWTContext; import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; import rs117.hd.opengl.commandbuffer.commands.BindFrameBufferCommand; @@ -16,14 +16,18 @@ import rs117.hd.opengl.commandbuffer.commands.DrawArraysCommand; import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; import rs117.hd.opengl.commandbuffer.commands.ExecuteCommandBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.FrameTimerCommand; import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; import rs117.hd.opengl.commandbuffer.commands.SetShaderUniformCommand; import rs117.hd.opengl.commandbuffer.commands.SetUniformBufferPropertyCommand; import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; +import rs117.hd.opengl.commandbuffer.commands.SwapBuffersCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.uniforms.UniformBuffer; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; import static rs117.hd.utils.MathUtils.*; @@ -60,6 +64,8 @@ public final class CommandBuffer { (SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new)), (SET_SHADER_PROPERTY_COMMAND = REGISTER_COMMAND(SetShaderUniformCommand::new)), (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), + (SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new)), + (FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new)), }; private final DrawArraysCommand DRAW_ARRAYS_COMMAND; @@ -80,6 +86,8 @@ public final class CommandBuffer { private final SetShaderUniformCommand SET_SHADER_PROPERTY_COMMAND; private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; + private final SwapBuffersCommand SWAP_BUFFER_COMMAND; + private final FrameTimerCommand FRAME_TIMER_COMMAND; interface ICreateCommand { T construct(); } @@ -93,7 +101,7 @@ private T REGISTER_COMMAND(ICreateCommand createComma private long[] cmd = new long[1 << 20]; // ~1 million calls - private UniformBuffer[] pendingUBOUploads = new UniformBuffer[100]; + private final UniformBuffer[] pendingUBOUploads = new UniformBuffer[100]; private int pendingUBOUploadsCount = 0; private Object[] objects = new Object[100]; @@ -105,11 +113,11 @@ private T REGISTER_COMMAND(ICreateCommand createComma private int readHead = 0; private int readBitHead = 0; - private final StringBuilder cmd_log = new StringBuilder(); + private boolean timeExecution = false; - public void ensureCapacity(int numLongs) { - if (writeHead + numLongs >= cmd.length) - cmd = Arrays.copyOf(cmd, cmd.length * 2); + public CommandBuffer SetTimeExecution(boolean timeExecution) { + this.timeExecution = timeExecution; + return this; } public void SetUniformProperty(UniformBuffer.Property property, int... values) { @@ -276,6 +284,23 @@ public void BlitFramebuffer( BLIT_FRAME_BUFFER_COMMAND.write(); } + public void BeginTimer(Timer timer) { + FRAME_TIMER_COMMAND.timer = timer; + FRAME_TIMER_COMMAND.begin = true; + FRAME_TIMER_COMMAND.write(); + } + + public void EndTimer(Timer timer) { + FRAME_TIMER_COMMAND.timer = timer; + FRAME_TIMER_COMMAND.begin = false; + FRAME_TIMER_COMMAND.write(); + } + + public void SwapBuffers(AWTContext context) { + SWAP_BUFFER_COMMAND.awtContext = context; + SWAP_BUFFER_COMMAND.write(); + } + public void Enable(int capability) { Toggle(capability, true); } @@ -290,10 +315,9 @@ public void Toggle(int capability, boolean enabled) { TOGGLE_COMMAND.write(); } - public void printCommandBuffer() { + public void printCommandBuffer(StringBuilder sb) { readHead = 0; readBitHead = 0; - cmd_log.setLength(0); while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { int type = (int) readBits(8); @@ -301,16 +325,22 @@ public void printCommandBuffer() { BaseCommand command = REGISTERED_COMMANDS[type]; command.doRead(); - command.print(cmd_log); + command.print(sb); } - log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", cmd_log); } - @SneakyThrows public void submit() { readHead = 0; readBitHead = 0; + long readElapsed = 0; + long readTimestamp = 0; + long executeTimestamp = 0; + + if(timeExecution) { + executeTimestamp = System.nanoTime(); + } + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { final int type = (int)readBits(8); assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; @@ -327,14 +357,23 @@ public void submit() { pendingUBOUploadsCount = 0; } + readTimestamp = System.nanoTime(); command.doRead(); + readElapsed += System.nanoTime() - readTimestamp; + command.execute(); if(command.isGLCommand() && HdPlugin.checkGLErrors(command.getName())) { - printCommandBuffer(); + StringBuilder sb = new StringBuilder(); + printCommandBuffer(sb); + log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", sb); break; } } + if(timeExecution) { + FrameTimer.getInstance().add(Timer.COMMAND_BUFFER_EXECUTE, System.nanoTime() - executeTimestamp); + } + FrameTimer.getInstance().add(Timer.COMMAND_BUFFER_READ, readElapsed); } protected void markUniformBufferDirty(UniformBuffer buffer) { @@ -424,7 +463,8 @@ protected void writeBits(long value, int numBits) { writeBitHead += bitsToWrite; if (writeBitHead == BITS_PER_WORD) { writeHead++; - ensureCapacity(writeHead); + if (writeHead >= cmd.length) + cmd = Arrays.copyOf(cmd, cmd.length * 2); writeBitHead = 0; word = 0; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java index 930641d9f3..34e84abdfe 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java @@ -24,6 +24,7 @@ protected void execute() { @Override protected void print(StringBuilder sb) { - sb.append("ExecuteCommandBufferCommand"); // TODO: Make CommandBuffer able to take a StringBuilder + sb.append("ExecuteCommandBufferCommand"); + cmd.printCommandBuffer(sb); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java new file mode 100644 index 0000000000..f89cd7f748 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java @@ -0,0 +1,36 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; + +public class FrameTimerCommand extends BaseCommand { + public Timer timer; + public boolean begin; + + @Override + protected void doWrite() { + write32(timer.ordinal()); + writeFlag(begin); + } + + @Override + protected void doRead() { + timer = Timer.TIMERS[read32()]; + begin = readFlag(); + } + + @Override + protected void execute() { + if(begin) { + FrameTimer.getInstance().begin(timer); + } else { + FrameTimer.getInstance().end(timer); + } + } + + @Override + protected void print(StringBuilder sb) { + // EEEEERM + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java new file mode 100644 index 0000000000..5fdf1182d2 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java @@ -0,0 +1,29 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import net.runelite.rlawt.AWTContext; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public class SwapBuffersCommand extends BaseCommand { + + public AWTContext awtContext; + + @Override + protected void doWrite() { + writeObject(awtContext); + } + + @Override + protected void doRead() { + awtContext = readObject(); + } + + @Override + protected void execute() { + awtContext.swapBuffers(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("awtContext.swapBuffers();"); + } +} diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index ca0091a8ba..880c222865 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -15,6 +15,9 @@ @Slf4j @Singleton public class FrameTimer { + @Getter + private static FrameTimer instance; + @Inject private ClientThread clientThread; @@ -37,6 +40,10 @@ public class FrameTimer { public long cumulativeError; public long errorCompensation; + public FrameTimer() { + instance = this; + } + private void initialize() { clientThread.invoke(() -> { int[] queryNames = new int[NUM_GPU_TIMERS * 2]; diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index f20c80e2dc..5f17289f1e 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,6 +17,7 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), + COMMAND_BUFFER_READ, COMMAND_BUFFER_EXECUTE, UPDATE_ENVIRONMENT, UPDATE_LIGHTS, diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index cfa264c90d..f203dba34d 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -740,6 +740,9 @@ private void preSceneDrawTopLevel( // Reset buffers for the next frame eboAlphaStaging.clear(); + + // Reset Command Buffers + plugin.backbufferCmd.reset(); sceneDrawCmd.reset(); directionalDrawCmd.reset(); @@ -768,6 +771,8 @@ private void postDrawTopLevel() { vaoA.unmap(); + plugin.backbufferCmd.BeginTimer(Timer.RENDER_FRAME); + // Scene draw state to apply before all recorded commands if (eboAlphaStaging.position() > 0) { eboAlphaStaging.flip(); @@ -779,10 +784,9 @@ private void postDrawTopLevel() { plugin.fboShadowMap != 0 && environmentManager.currentDirectionalStrength > 0 ) { - frameTimer.begin(Timer.RENDER_SHADOWS); - // Render to the shadow depth map directionalPassCmd.reset(); + directionalPassCmd.BeginTimer(Timer.RENDER_SHADOWS); directionalPassCmd.Viewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); directionalPassCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); directionalPassCmd.ClearDepth(1.0f); @@ -797,12 +801,13 @@ private void postDrawTopLevel() { directionalPassCmd.Disable(GL_DEPTH_TEST); - directionalPassCmd.submit(); - frameTimer.end(Timer.RENDER_SHADOWS); + directionalPassCmd.EndTimer(Timer.RENDER_SHADOWS); + + plugin.backbufferCmd.ExecuteCommandBuffer(directionalPassCmd); } - frameTimer.begin(Timer.RENDER_SCENE); scenePassCmd.reset(); + scenePassCmd.BeginTimer(Timer.RENDER_SCENE); scenePassCmd.SetShaderProgram(sceneProgram); scenePassCmd.BindFrameBuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); @@ -817,7 +822,11 @@ private void postDrawTopLevel() { gammaCorrectedFogColor[2], 1f ); + + scenePassCmd.BeginTimer(Timer.CLEAR_SCENE); scenePassCmd.ClearColorAndDepth(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1f, 0.0f); + scenePassCmd.EndTimer(Timer.CLEAR_SCENE); + scenePassCmd.Enable(GL_CULL_FACE); scenePassCmd.Enable(GL_DEPTH_TEST); scenePassCmd.SetDepthFunc(GL_GREATER); @@ -831,24 +840,10 @@ private void postDrawTopLevel() { scenePassCmd.Disable(GL_DEPTH_TEST); scenePassCmd.Disable(GL_BLEND); - scenePassCmd.submit(); + scenePassCmd.EndTimer(Timer.RENDER_SCENE); + plugin.backbufferCmd.ExecuteCommandBuffer(scenePassCmd); frameTimer.end(Timer.DRAW_SCENE); - frameTimer.end(Timer.RENDER_SCENE); - frameTimer.begin(Timer.RENDER_FRAME); - - - // The client only updates animations once per client tick, so we can skip updating geometry buffers, - // but the compute shaders should still be executed in case the camera angle has changed. - // Technically we could skip compute shaders as well when the camera is unchanged, - // but it would only lead to micro stuttering when rotating the camera, compared to no rotation. -// if (!plugin.redrawPreviousFrame) { -// updateSceneVao(hRenderBufferVertices, hRenderBufferUvs, hRenderBufferNormals); -// } - -// frameTimer.begin(Timer.COMPUTE); -// plugin.uboCompute.upload(); -// frameTimer.end(Timer.COMPUTE); checkGLErrors(); } @@ -1274,7 +1269,6 @@ public void draw(int overlayColor) { } if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { - plugin.backbufferCmd.reset(); plugin.backbufferCmd.BlitFramebuffer( plugin.fboScene, plugin.fboSceneResolve, plugin.awtContext.getFramebuffer(false), plugin.sceneResolution[0], plugin.sceneResolution[1], @@ -1288,12 +1282,14 @@ public void draw(int overlayColor) { plugin.drawUi(overlayColor); + plugin.backbufferCmd.BeginTimer(Timer.SWAP_BUFFERS); + plugin.backbufferCmd.SwapBuffers(plugin.awtContext); + plugin.backbufferCmd.EndTimer(Timer.SWAP_BUFFERS); + plugin.backbufferCmd.EndTimer(Timer.RENDER_FRAME); + plugin.backbufferCmd.submit(); try { - frameTimer.begin(Timer.SWAP_BUFFERS); - plugin.awtContext.swapBuffers(); - frameTimer.end(Timer.SWAP_BUFFERS); drawManager.processDrawComplete(plugin::screenshot); } catch (RuntimeException ex) { // this is always fatal @@ -1308,7 +1304,6 @@ public void draw(int overlayColor) { glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); frameTimer.end(Timer.DRAW_FRAME); - frameTimer.end(Timer.RENDER_FRAME); frameTimer.endFrameAndReset(); // frameModelInfoMap.clear(); checkGLErrors(); From 022f04aa00b155ec24a55c38bb12f39520385221 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 21 Oct 2025 21:11:19 +0100 Subject: [PATCH 26/50] Finer grain timings --- src/main/java/rs117/hd/HdPlugin.java | 2 +- .../rs117/hd/opengl/commandbuffer/CommandBuffer.java | 12 ++++++------ src/main/java/rs117/hd/overlays/Timer.java | 4 +++- .../java/rs117/hd/renderer/zone/ZoneRenderer.java | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 0341e16106..e73c106c43 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -345,7 +345,7 @@ public class HdPlugin extends Plugin { public final UBOLights uboLightsCulling = new UBOLights(true); public final UBOUI uboUI = new UBOUI(); - public final CommandBuffer backbufferCmd = new CommandBuffer().SetTimeExecution(true); + public final CommandBuffer backbufferCmd = new CommandBuffer().SetExecutionTimer(Timer.BACKBUFFER_COMMAND_BUFFER_EXECUTE); // Configs used frequently enough to be worth caching public boolean configGroundTextures; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 4601773270..25f4442c29 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -113,10 +113,10 @@ private T REGISTER_COMMAND(ICreateCommand createComma private int readHead = 0; private int readBitHead = 0; - private boolean timeExecution = false; + private Timer executionTimer = null; - public CommandBuffer SetTimeExecution(boolean timeExecution) { - this.timeExecution = timeExecution; + public CommandBuffer SetExecutionTimer(Timer timer) { + this.executionTimer = timer; return this; } @@ -337,7 +337,7 @@ public void submit() { long readTimestamp = 0; long executeTimestamp = 0; - if(timeExecution) { + if(executionTimer != null) { executeTimestamp = System.nanoTime(); } @@ -370,8 +370,8 @@ public void submit() { break; } } - if(timeExecution) { - FrameTimer.getInstance().add(Timer.COMMAND_BUFFER_EXECUTE, System.nanoTime() - executeTimestamp); + if(executionTimer != null) { + FrameTimer.getInstance().add(executionTimer, System.nanoTime() - executeTimestamp); } FrameTimer.getInstance().add(Timer.COMMAND_BUFFER_READ, readElapsed); } diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 5f17289f1e..345beec7d3 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -18,7 +18,9 @@ public enum Timer { MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), COMMAND_BUFFER_READ, - COMMAND_BUFFER_EXECUTE, + SHADOW_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Shadow"), + SCENE_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Scene"), + BACKBUFFER_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Main"), UPDATE_ENVIRONMENT, UPDATE_LIGHTS, IMPOSTOR_TRACKING, diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index f203dba34d..e1f7e255bf 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -152,10 +152,10 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; - private final CommandBuffer scenePassCmd = new CommandBuffer(); + private final CommandBuffer scenePassCmd = new CommandBuffer().SetExecutionTimer(Timer.SCENE_COMMAND_BUFFER_EXECUTE); private final CommandBuffer sceneDrawCmd = new CommandBuffer(); - private final CommandBuffer directionalPassCmd = new CommandBuffer(); + private final CommandBuffer directionalPassCmd = new CommandBuffer().SetExecutionTimer(Timer.SHADOW_COMMAND_BUFFER_EXECUTE); private final CommandBuffer directionalDrawCmd = new CommandBuffer(); private VAO.VAOList vaoO; From 969ff6f56567a26058d8b6ff24a626af1d61dd78 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:52:09 +0100 Subject: [PATCH 27/50] RenderThread Implemention --- src/main/java/rs117/hd/HdPlugin.java | 61 ++++--- src/main/java/rs117/hd/HdPluginConfig.java | 11 -- .../java/rs117/hd/opengl/AsyncUICopy.java | 108 ----------- .../commandbuffer/AWTContextWrapper.java | 92 ++++++++++ .../opengl/commandbuffer/CommandBuffer.java | 23 ++- .../hd/opengl/commandbuffer/RenderThread.java | 169 ++++++++++++++++++ .../commands/UploadPixelDataCommand.java | 69 +++++++ .../java/rs117/hd/overlays/FrameTimer.java | 2 + src/main/java/rs117/hd/overlays/Timer.java | 1 + .../hd/renderer/legacy/LegacyRenderer.java | 4 + .../rs117/hd/renderer/zone/ZoneRenderer.java | 60 ++++--- 11 files changed, 428 insertions(+), 172 deletions(-) delete mode 100644 src/main/java/rs117/hd/opengl/AsyncUICopy.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index e73c106c43..ccb04101be 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -80,8 +80,8 @@ import rs117.hd.config.ShadingMode; import rs117.hd.config.ShadowMode; import rs117.hd.config.VanillaShadowMode; -import rs117.hd.opengl.AsyncUICopy; import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.ShadowShaderProgram; @@ -247,9 +247,6 @@ public class HdPlugin extends Plugin { @Inject private ModelOverrideManager modelOverrideManager; - @Inject - private AsyncUICopy asyncUICopy; - @Inject private FishingSpotReplacer fishingSpotReplacer; @@ -288,8 +285,11 @@ public class HdPlugin extends Plugin { public Renderer renderer; + public RenderThread renderThread; + public static boolean SKIP_GL_ERROR_CHECKS; public static GLCapabilities GL_CAPS; + public static GLCapabilities GL_RENDER_THREAD_CAPS; public static boolean AMD_GPU; public Canvas canvas; @@ -364,7 +364,6 @@ public class HdPlugin extends Plugin { public boolean configUseFasterModelHashing; public boolean configUndoVanillaShading; public boolean configPreserveVanillaNormals; - public boolean configAsyncUICopy; public boolean configWindDisplacement; public boolean configCharacterDisplacement; public boolean configTiledLighting; @@ -461,6 +460,8 @@ protected void startUp() { awtContext.createGLContext(); + renderThread = new RenderThread(frameTimer, awtContext); + canvas.setIgnoreRepaint(true); // lwjgl defaults to lwjgl- + user.name, but this breaks if the username would cause an invalid path @@ -640,7 +641,6 @@ protected void shutDown() { client.setUnlockedFps(false); client.setExpandedMapLoading(0); - asyncUICopy.complete(); developerTools.deactivate(); tileOverrideManager.shutDown(); groundMaterialManager.shutDown(); @@ -676,6 +676,10 @@ protected void shutDown() { renderer = null; } + if(renderThread != null) + renderThread.shutdown(); + renderThread = null; + if (awtContext != null) awtContext.destroy(); awtContext = null; @@ -1035,6 +1039,8 @@ private void destroyUiTexture() { public void updateTiledLightingFbo() { assert configTiledLighting; + renderer.waitUntilIdle(); + int[] newResolution = max(ivec(1), round(divide(vec(sceneResolution), TILED_LIGHTING_TILE_SIZE))); int newLayerCount = configDynamicLights.getTiledLightingLayers(); if (Arrays.equals(newResolution, tiledLightingResolution) && tiledLightingLayerCount == newLayerCount) @@ -1123,6 +1129,8 @@ public void updateSceneFbo() { if (Arrays.equals(sceneViewport, viewport)) return; + renderer.waitUntilIdle(); + destroySceneFbo(); sceneViewport = viewport; @@ -1206,6 +1214,8 @@ public void updateSceneFbo() { private void destroySceneFbo() { sceneViewport = null; + renderer.waitUntilIdle(); + if (fboScene != 0) glDeleteFramebuffers(fboScene); fboScene = 0; @@ -1228,6 +1238,8 @@ private void destroySceneFbo() { } private void initializeShadowMapFbo() { + renderer.waitUntilIdle(); + if (!configShadowsEnabled) { initializeDummyShadowMap(); return; @@ -1290,6 +1302,8 @@ private void initializeDummyShadowMap() { } private void destroyShadowMapFbo() { + renderer.waitUntilIdle(); + if (texShadowMap != 0) glDeleteTextures(texShadowMap); texShadowMap = 0; @@ -1307,6 +1321,7 @@ public void initializeShaderHotswapping() { } public void prepareInterfaceTexture() { + int[] resolution = { max(1, client.getCanvasWidth()), max(1, client.getCanvasHeight()) @@ -1315,6 +1330,8 @@ public void prepareInterfaceTexture() { if (resize) { uiResolution = resolution; + renderer.waitUntilIdle(); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); @@ -1333,20 +1350,14 @@ public void prepareInterfaceTexture() { } round(actualUiResolution, multiply(vec(actualUiResolution), getDpiScaling())); - if (configAsyncUICopy) { - // Start copying the UI on a different thread, to be uploaded during the next frame - asyncUICopy.prepare(pboUi, texUi); - // If the window was just resized, upload once synchronously so there is something to show - if (resize) - asyncUICopy.complete(); - return; - } - final BufferProvider bufferProvider = client.getBufferProvider(); final int[] pixels = bufferProvider.getPixels(); final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); + backbufferCmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels); + + /* frameTimer.begin(Timer.MAP_UI_BUFFER); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); ByteBuffer mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); @@ -1367,7 +1378,7 @@ public void prepareInterfaceTexture() { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); frameTimer.end(Timer.UPLOAD_UI); } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);*/ } public void drawUi(int overlayColor) { @@ -1420,8 +1431,8 @@ public void drawUi(int overlayColor) { } /** - * Convert the front framebuffer to an Image - */ + * Convert the front framebuffer to an Image + */ public Image screenshot() { if (uiResolution == null) return null; @@ -1487,7 +1498,6 @@ private void updateCachedConfigs() { configUseFasterModelHashing = config.fasterModelHashing(); configUndoVanillaShading = config.shadingMode() != ShadingMode.VANILLA; configPreserveVanillaNormals = config.preserveVanillaNormals(); - configAsyncUICopy = config.asyncUICopy(); configWindDisplacement = config.windDisplacement(); configCharacterDisplacement = config.characterDisplacement(); configSeasonalTheme = config.seasonalTheme(); @@ -1561,6 +1571,8 @@ public void processPendingConfigChanges() { if (pendingConfigChanges.isEmpty()) return; + renderer.waitUntilIdle(); + try { // Synchronize with scene loading synchronized (this) { @@ -1599,9 +1611,6 @@ public void processPendingConfigChanges() { if (configColorFilter == ColorFilter.CEL_SHADING || configColorFilterPrevious == ColorFilter.CEL_SHADING) reloadScene = true; break; - case KEY_ASYNC_UI_COPY: - asyncUICopy.complete(); - break; } switch (key) { @@ -1684,9 +1693,6 @@ public void processPendingConfigChanges() { } } - if (reloadTexturesAndMaterials || recompilePrograms) - renderer.waitUntilIdle(); - if (reloadTexturesAndMaterials) { materialManager.reload(false); modelOverrideManager.reload(); @@ -1795,9 +1801,7 @@ public int getExpandedMapLoadingChunks() { public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); - // Upload the UI which we began copying during the previous frame - if (configAsyncUICopy) - asyncUICopy.complete(); + renderThread.waitForRenderingCompleted(); if (client.getScene() == null) return; @@ -1809,6 +1813,7 @@ public void onBeforeRender(BeforeRender beforeRender) { public void onClientTick(ClientTick clientTick) { elapsedClientTime += 1 / 50f; + if (!enableFreezeFrame && skipScene != client.getScene()) redrawPreviousFrame = false; } diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index 9018a9b231..09f0bd70ca 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -1098,17 +1098,6 @@ default boolean wireframe() { return false; } - String KEY_ASYNC_UI_COPY = "experimentalAsyncUICopy"; - @ConfigItem( - keyName = KEY_ASYNC_UI_COPY, - name = "Perform UI copy asynchronously", - description = "Slightly improves performance by delaying the UI by one frame.", - section = experimentalSettings - ) - default boolean asyncUICopy() { - return false; - } - String KEY_TILED_LIGHTING_IMAGE_STORE = "experimentalTiledLightingImageStore"; @ConfigItem( keyName = KEY_TILED_LIGHTING_IMAGE_STORE, diff --git a/src/main/java/rs117/hd/opengl/AsyncUICopy.java b/src/main/java/rs117/hd/opengl/AsyncUICopy.java deleted file mode 100644 index 4f8a347789..0000000000 --- a/src/main/java/rs117/hd/opengl/AsyncUICopy.java +++ /dev/null @@ -1,108 +0,0 @@ -package rs117.hd.opengl; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import javax.inject.Inject; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.*; -import rs117.hd.HdPlugin; -import rs117.hd.overlays.FrameTimer; -import rs117.hd.overlays.Timer; - -import static org.lwjgl.opengl.GL33C.*; - -@Slf4j -public class AsyncUICopy implements Runnable { - @Inject - private Client client; - - @Inject - private HdPlugin plugin; - - @Inject - private FrameTimer timer; - - private final ExecutorService executor = Executors.newSingleThreadExecutor(); - private final Semaphore completionSemaphore = new Semaphore(0); - - private IntBuffer mappedBuffer; - private int[] pixels; - private int interfacePbo; - private int interfaceTexture; - private int width; - private int height; - - @Override - public void run() { - long time = System.nanoTime(); - mappedBuffer.put(pixels, 0, width * height); - time = System.nanoTime() - time; - completionSemaphore.release(); - timer.add(Timer.COPY_UI, time); - } - - public void prepare(int interfacePbo, int interfaceTex) { - // Ensure there isn't already another UI copy in progress - if (mappedBuffer != null) - return; - - timer.begin(Timer.MAP_UI_BUFFER); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - ByteBuffer buffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - timer.end(Timer.MAP_UI_BUFFER); - if (buffer == null) { - log.error("Unable to map interface PBO. Skipping UI..."); - return; - } - - this.interfacePbo = interfacePbo; - this.interfaceTexture = interfaceTex; - this.mappedBuffer = buffer.asIntBuffer(); - - var provider = client.getBufferProvider(); - this.pixels = provider.getPixels(); - this.width = provider.getWidth(); - this.height = provider.getHeight(); - - executor.execute(this); - } - - public boolean complete() { - if (mappedBuffer == null) - return false; - - try { - // It shouldn't take this long even in the worst case - boolean acquired = completionSemaphore.tryAcquire(1, 100, TimeUnit.MILLISECONDS); - if (!acquired) - return false; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - var uiResolution = plugin.getUiResolution(); - if (uiResolution == null || width > uiResolution[0] || height > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", width, height, uiResolution); - return false; - } - - timer.begin(Timer.UPLOAD_UI); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glActiveTexture(HdPlugin.TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, interfaceTexture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); - timer.end(Timer.UPLOAD_UI); - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - mappedBuffer = null; - pixels = null; - return true; - } -} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java new file mode 100644 index 0000000000..20bd4f7e53 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java @@ -0,0 +1,92 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.concurrent.ConcurrentLinkedQueue; +import lombok.extern.slf4j.Slf4j; +import net.runelite.rlawt.AWTContext; +import org.lwjgl.opengl.*; +import rs117.hd.HdPlugin; + +@Slf4j +public final class AWTContextWrapper { + public enum Owner { CLIENT, RENDER_THREAD, NONE } + + private final AWTContext context; + private volatile Owner currentOwner = Owner.CLIENT; + + private static final int MAX_OWNERSHIP_LOG = 8; + private final ConcurrentLinkedQueue ownershipLog = new ConcurrentLinkedQueue<>(); + + private final StringBuilder sb = new StringBuilder(); + private final boolean validate; + + public AWTContextWrapper(AWTContext context, boolean validate) { + this.context = context; + this.validate = validate; + } + + public synchronized Owner getOwner() { + return currentOwner; + } + + public synchronized void makeCurrent(Owner newOwner) { + if (currentOwner == newOwner) + return; + + try { + context.makeCurrent(); + if(newOwner == Owner.RENDER_THREAD && HdPlugin.GL_RENDER_THREAD_CAPS == null){ + HdPlugin.GL_RENDER_THREAD_CAPS = GL.createCapabilities(); + } + updateOwner(newOwner, "makeCurrent"); + } catch (Exception e) { + printOwnershipLog(); + log.error("Failed to make OpenGL context current for {}", newOwner, e); + throw new RuntimeException(e); + } + } + + + public synchronized void detachCurrent(String reason) { + try { + context.detachCurrent(); + updateOwner(Owner.NONE, reason); + } catch (Exception e) { + printOwnershipLog(); + log.error("Failed to detach OpenGL context: " + reason, e); + throw new RuntimeException(e); + } + } + + private void updateOwner(Owner newOwner, String action) { + if (currentOwner != newOwner && validate) { + if (ownershipLog.size() >= MAX_OWNERSHIP_LOG) + ownershipLog.poll(); + + sb.append(currentOwner) + .append(" -> ") + .append(newOwner) + .append(" via ") + .append(action); + + ownershipLog.add(sb.toString()); + log.debug("Context Ownership: {}", sb); + + sb.setLength(0); + } + + currentOwner = newOwner; + } + + private void printOwnershipLog() { + if (!validate) return; + + if (ownershipLog.isEmpty()) { + log.error("No recent ownership transitions recorded."); + } else { + log.error("Recent ownership transitions:"); + for (String entry : ownershipLog) { + log.error(" - {}", entry); + } + } + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 25f4442c29..96e352b42e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -23,6 +23,7 @@ import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; import rs117.hd.opengl.commandbuffer.commands.SwapBuffersCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; +import rs117.hd.opengl.commandbuffer.commands.UploadPixelDataCommand; import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.uniforms.UniformBuffer; @@ -63,6 +64,7 @@ public final class CommandBuffer { (BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new)), (SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new)), (SET_SHADER_PROPERTY_COMMAND = REGISTER_COMMAND(SetShaderUniformCommand::new)), + (UPLOAD_PIXEL_DATA_COMMAND = REGISTER_COMMAND(UploadPixelDataCommand::new)), (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), (SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new)), (FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new)), @@ -85,6 +87,7 @@ public final class CommandBuffer { private final SetUniformBufferPropertyCommand SET_UNIFORM_BUFFER_PROPERTY_COMMAND; private final SetShaderUniformCommand SET_SHADER_PROPERTY_COMMAND; private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; + private final UploadPixelDataCommand UPLOAD_PIXEL_DATA_COMMAND; private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; private final SwapBuffersCommand SWAP_BUFFER_COMMAND; private final FrameTimerCommand FRAME_TIMER_COMMAND; @@ -175,6 +178,17 @@ public void SetShaderProgram(ShaderProgram program) { SHADER_PROGRAM_COMMAND.write(); } + public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data) { + assert UPLOAD_PIXEL_DATA_COMMAND.data == null; + UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; + UPLOAD_PIXEL_DATA_COMMAND.tex = tex; + UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; + UPLOAD_PIXEL_DATA_COMMAND.width = width; + UPLOAD_PIXEL_DATA_COMMAND.height = height; + UPLOAD_PIXEL_DATA_COMMAND.data = data; + UPLOAD_PIXEL_DATA_COMMAND.write(); + } + public void ClearDepth(float depth) { CLEAR_COMMAND.clearColor = false; CLEAR_COMMAND.clearDepth = true; @@ -326,6 +340,7 @@ public void printCommandBuffer(StringBuilder sb) { BaseCommand command = REGISTERED_COMMANDS[type]; command.doRead(); command.print(sb); + sb.append('\n'); } } @@ -364,10 +379,10 @@ public void submit() { command.execute(); if(command.isGLCommand() && HdPlugin.checkGLErrors(command.getName())) { - StringBuilder sb = new StringBuilder(); - printCommandBuffer(sb); - log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", sb); - break; + //StringBuilder sb = new StringBuilder(); + //printCommandBuffer(sb); + //log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", sb); + //break; } } if(executionTimer != null) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java new file mode 100644 index 0000000000..ee51b3c52d --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -0,0 +1,169 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.extern.slf4j.Slf4j; +import net.runelite.rlawt.AWTContext; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; + +@Slf4j +public final class RenderThread implements Runnable { + private static final boolean VALIDATE = false; + + private static final RenderTask POISON_PILL = new RenderTask(); + private static final ArrayDeque TASK_BIN = new ArrayDeque<>(); + + private final BlockingQueue queue = new LinkedBlockingQueue<>(); + private final List completedTasks = new ArrayList<>(); + + private final AtomicInteger pendingCount = new AtomicInteger(0); + private final Object completionLock = new Object(); + private final AtomicBoolean running = new AtomicBoolean(true); + + private final FrameTimer timer; + private final AWTContextWrapper contextWrapper; + private final Thread thread; + + public RenderThread(FrameTimer timer, AWTContext context) { + this.timer = timer; + this.contextWrapper = new AWTContextWrapper(context, VALIDATE); + this.thread = new Thread(this, "HD-RenderThread"); + this.thread.setPriority(Thread.MAX_PRIORITY); + this.thread.setDaemon(true); + this.thread.start(); + } + + public void submit(CommandBuffer buffer) { + submit(buffer, null); + } + + public void submit(CommandBuffer buffer, Runnable onComplete) { + if (buffer == null) + throw new IllegalArgumentException("CommandBuffer cannot be null"); + + pendingCount.incrementAndGet(); + + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) + contextWrapper.detachCurrent("detached for render submit"); + + RenderTask newTask = TASK_BIN.poll(); + if (newTask == null) { + newTask = new RenderTask(); + } + + newTask.buffer = buffer; + newTask.callback = onComplete; + + queue.add(newTask); + } + + public void waitForRenderingCompleted() { + synchronized (completionLock) { + try { + while (pendingCount.get() > 0) { + timer.begin(Timer.RENDER_THREAD_COMPLETION); + completionLock.wait(); + timer.end(Timer.RENDER_THREAD_COMPLETION); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); + + synchronized (completedTasks) { + for (RenderTask task : completedTasks) { + if (task.callback != null) { + try { + task.callback.run(); + } catch (Throwable t) { + log.warn("Exception in render completion callback", t); + } + } + + task.buffer = null; + task.callback = null; + + TASK_BIN.add(task); + } + completedTasks.clear(); + } + } + } + + @Override + public void run() { + log.debug("RenderThread started!"); + + while (running.get()) { + RenderTask task; + try { + task = queue.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + + if (task == POISON_PILL) + break; + + if (task.buffer == null) { + taskCompleted(task); + continue; + } + + contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); + + try { + task.buffer.submit(); + } catch (Throwable t) { + log.error("Error during CommandBuffer execution", t); + } finally { + taskCompleted(task); + } + } + + log.debug("RenderThread stopped!"); + } + + private void taskCompleted(RenderTask task) { + synchronized (completedTasks) { + completedTasks.add(task); + } + + int remaining = pendingCount.decrementAndGet(); + if (remaining <= 0) { + // Release ownership now that there is no more work pending + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.RENDER_THREAD) + contextWrapper.detachCurrent("released by render thread"); + + pendingCount.set(0); + synchronized (completionLock) { + completionLock.notifyAll(); + } + } + } + + public void shutdown() { + if (running.getAndSet(false)) { + queue.add(POISON_PILL); + try { + thread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + private static class RenderTask { + public CommandBuffer buffer = null; + public Runnable callback = null; + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java new file mode 100644 index 0000000000..96826069b7 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -0,0 +1,69 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.ByteBuffer; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11C.glBindTexture; +import static org.lwjgl.opengl.GL11C.glTexSubImage2D; +import static org.lwjgl.opengl.GL12C.GL_BGRA; +import static org.lwjgl.opengl.GL12C.GL_UNSIGNED_INT_8_8_8_8_REV; +import static org.lwjgl.opengl.GL13C.glActiveTexture; +import static org.lwjgl.opengl.GL15C.GL_WRITE_ONLY; +import static org.lwjgl.opengl.GL15C.glBindBuffer; +import static org.lwjgl.opengl.GL15C.glMapBuffer; +import static org.lwjgl.opengl.GL15C.glUnmapBuffer; +import static org.lwjgl.opengl.GL21C.GL_PIXEL_UNPACK_BUFFER; + +public class UploadPixelDataCommand extends BaseCommand { + + public int texUnit; + public int tex; + public int pbo; + public int width; + public int height; + public int[] data; + + @Override + protected void doWrite() { + write32(texUnit); + write32(tex); + write32(pbo); + write32(width); + write32(height); + writeObject(data); + data = null; + } + + @Override + protected void doRead() { + texUnit = read32(); + tex = read32(); + pbo = read32(); + width = read32(); + height = read32(); + data = readObject(); + } + + @Override + protected void execute() { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + ByteBuffer mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); + + if (mappedBuffer != null) { + mappedBuffer.asIntBuffer().put(data, 0, width * height); + data = null; + + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + glActiveTexture(texUnit); + glBindTexture(GL_TEXTURE_2D, tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + @Override + protected void print(StringBuilder sb) { + + } +} diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index 880c222865..d44fd97e54 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -46,6 +46,8 @@ public FrameTimer() { private void initialize() { clientThread.invoke(() -> { + plugin.renderThread.waitForRenderingCompleted(); + int[] queryNames = new int[NUM_GPU_TIMERS * 2]; glGenQueries(queryNames); int queryIndex = 0; diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 345beec7d3..35e80fb6ce 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,6 +17,7 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), + RENDER_THREAD_COMPLETION, COMMAND_BUFFER_READ, SHADOW_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Shadow"), SCENE_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Scene"), diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index 9c2510821f..66d964ed33 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -1325,6 +1325,8 @@ public void reloadScene() { if (client.getGameState().getState() < GameState.LOGGED_IN.getState()) return; + plugin.renderThread.waitForRenderingCompleted(); + Scene scene = client.getTopLevelWorldView().getScene(); loadScene(scene); if (plugin.skipScene == scene) @@ -1337,6 +1339,8 @@ public void loadScene(Scene scene) { if (!plugin.isActive()) return; + plugin.renderThread.waitForRenderingCompleted(); + int expandedChunks = plugin.getExpandedMapLoadingChunks(); if (HDUtils.sceneIntersects(scene, expandedChunks, areaManager.getArea("PLAYER_OWNED_HOUSE"))) { // Reload once the POH is done loading diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index e1f7e255bf..e6dd6dc683 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -271,6 +271,7 @@ public void destroy() { @Override public void waitUntilIdle() { + plugin.renderThread.waitForRenderingCompleted(); glFinish(); } @@ -360,6 +361,8 @@ private void preSceneDrawTopLevel( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { + plugin.renderThread.waitForRenderingCompleted(); + scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); @@ -771,7 +774,7 @@ private void postDrawTopLevel() { vaoA.unmap(); - plugin.backbufferCmd.BeginTimer(Timer.RENDER_FRAME); + plugin.renderThread.waitForRenderingCompleted(); // Scene draw state to apply before all recorded commands if (eboAlphaStaging.position() > 0) { @@ -780,12 +783,15 @@ private void postDrawTopLevel() { glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); } + directionalPassCmd.reset(); + scenePassCmd.reset(); + if (plugin.configShadowsEnabled && plugin.fboShadowMap != 0 && environmentManager.currentDirectionalStrength > 0 ) { // Render to the shadow depth map - directionalPassCmd.reset(); + directionalPassCmd.BeginTimer(Timer.RENDER_FRAME); directionalPassCmd.BeginTimer(Timer.RENDER_SHADOWS); directionalPassCmd.Viewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); directionalPassCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); @@ -803,10 +809,11 @@ private void postDrawTopLevel() { directionalPassCmd.EndTimer(Timer.RENDER_SHADOWS); - plugin.backbufferCmd.ExecuteCommandBuffer(directionalPassCmd); + plugin.renderThread.submit(directionalPassCmd); + } else { + scenePassCmd.BeginTimer(Timer.RENDER_FRAME); } - scenePassCmd.reset(); scenePassCmd.BeginTimer(Timer.RENDER_SCENE); scenePassCmd.SetShaderProgram(sceneProgram); @@ -841,7 +848,8 @@ private void postDrawTopLevel() { scenePassCmd.Disable(GL_BLEND); scenePassCmd.EndTimer(Timer.RENDER_SCENE); - plugin.backbufferCmd.ExecuteCommandBuffer(scenePassCmd); + + plugin.renderThread.submit(scenePassCmd); frameTimer.end(Timer.DRAW_SCENE); @@ -882,6 +890,8 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int if (!z.initialized || z.sizeO == 0) return; + plugin.renderThread.waitForRenderingCompleted(); + int offset = ctx.sceneContext.sceneOffset >> 3; if (z.inSceneFrustum) { sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); @@ -917,6 +927,8 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i if (!z.initialized) return; + plugin.renderThread.waitForRenderingCompleted(); + boolean hasAlpha = z.sizeA != 0 || !z.alphaModels.isEmpty(); boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; @@ -978,6 +990,8 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (ctx == null) return; + plugin.renderThread.waitForRenderingCompleted(); + switch (pass) { case DrawCallbacks.PASS_OPAQUE: vaoO.addRange(scene); @@ -1049,6 +1063,8 @@ public void drawDynamic( if (modelOverride.hide) return; + plugin.renderThread.waitForRenderingCompleted(); + int preOrientation = HDUtils.getModelPreOrientation(HDUtils.getObjectConfig(tileObject)); byte[] transparencies = m.getFaceTransparencies(); @@ -1102,6 +1118,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (modelOverride.hide) return; + plugin.renderThread.waitForRenderingCompleted(); + int preOrientation = HDUtils.getModelPreOrientation(gameObject.getConfig()); int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; @@ -1214,6 +1232,8 @@ private void rebuild(WorldView wv) { if (!zone.invalidate) continue; + plugin.renderThread.waitForRenderingCompleted(); + assert zone.initialized; zone.free(); zone = ctx.zones[x][z] = new Zone(); @@ -1287,26 +1307,18 @@ public void draw(int overlayColor) { plugin.backbufferCmd.EndTimer(Timer.SWAP_BUFFERS); plugin.backbufferCmd.EndTimer(Timer.RENDER_FRAME); - plugin.backbufferCmd.submit(); - - try { - drawManager.processDrawComplete(plugin::screenshot); - } catch (RuntimeException ex) { - // this is always fatal - if (!plugin.canvas.isValid()) { - // this might be AWT shutting down on VM shutdown, ignore it - return; - } + frameTimer.end(Timer.DRAW_FRAME); + plugin.renderThread.submit(plugin.backbufferCmd, this::onBackBufferSwapCompleted); - log.error("Unable to swap buffers:", ex); + if(client.getGameState().getState() < GameState.LOGGED_IN.getState() || root.sceneContext == null) { + plugin.renderThread.waitForRenderingCompleted(); } + } - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - - frameTimer.end(Timer.DRAW_FRAME); + public void onBackBufferSwapCompleted() { frameTimer.endFrameAndReset(); -// frameModelInfoMap.clear(); - checkGLErrors(); + + plugin.backbufferCmd.reset(); } @Subscribe @@ -1331,6 +1343,8 @@ public void reloadScene() { if (client.getGameState().getState() < GameState.LOGGED_IN.getState() || root.sceneContext == null) return; + plugin.renderThread.waitForRenderingCompleted(); + proceduralGenerator.generateSceneData(root.sceneContext); for (int i = 0; i < NUM_ZONES; i++) for (int j = 0; j < NUM_ZONES; j++) @@ -1498,6 +1512,8 @@ public void loadScene(WorldView worldView, Scene scene) { CountDownLatch latch = new CountDownLatch(1); clientThread.invoke(() -> { + plugin.renderThread.waitForRenderingCompleted(); + for (int x = 0; x < EXTENDED_SCENE_SIZE >> 3; ++x) { for (int z = 0; z < EXTENDED_SCENE_SIZE >> 3; ++z) { Zone zone = newZones[x][z]; @@ -1638,6 +1654,8 @@ private void loadSubScene(WorldView worldView, Scene scene) { CountDownLatch latch = new CountDownLatch(1); clientThread.invoke(() -> { + plugin.renderThread.waitForRenderingCompleted(); + for (int x = 0; x < ctx.sizeX; ++x) { for (int z = 0; z < ctx.sizeZ; ++z) { Zone zone = ctx.zones[x][z]; From 694add2071dfe610038e43580e0944ff41e1b192 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:56:24 +0100 Subject: [PATCH 28/50] CommandBuffers now create two Commands per type * Writer * Executor --- src/main/java/rs117/hd/HdPlugin.java | 2 - .../opengl/commandbuffer/CommandBuffer.java | 89 ++++++++----------- 2 files changed, 38 insertions(+), 53 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index ccb04101be..7f85fe0843 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -1801,8 +1801,6 @@ public int getExpandedMapLoadingChunks() { public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); - renderThread.waitForRenderingCompleted(); - if (client.getScene() == null) return; // The game runs significantly slower with lower planes in Chambers of Xeric diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 96e352b42e..6bdd5a0ae1 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -46,60 +46,48 @@ public final class CommandBuffer { } private int COMMAND_COUNT = 0; - private final BaseCommand[] REGISTERED_COMMANDS = { - (DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new)), - (DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new)), - (MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new)), - (BIND_FRAMEBUFFER_COMMAND = REGISTER_COMMAND(BindFrameBufferCommand::new)), - (VIEWPORT_COMMAND = REGISTER_COMMAND(ViewportCommand::new)), - (CLEAR_COMMAND = REGISTER_COMMAND(ClearCommand::new)), - (TOGGLE_COMMAND = REGISTER_COMMAND(ToggleCommand::new)), - (SHADER_PROGRAM_COMMAND = REGISTER_COMMAND(ShaderProgramCommand::new)), - (COLOR_MASK_COMMAND = REGISTER_COMMAND(ColorMaskCommand::new)), - (DEPTH_FUNC_COMMAND = REGISTER_COMMAND(DepthFuncCommand::new)), - (DEPTH_MASK_COMMAND = REGISTER_COMMAND(DepthMaskCommand::new)), - (BLENDED_FUNC_COMMAND = REGISTER_COMMAND(BlendFuncCommand::new)), - (BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new)), - (BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new)), - (BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new)), - (SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new)), - (SET_SHADER_PROPERTY_COMMAND = REGISTER_COMMAND(SetShaderUniformCommand::new)), - (UPLOAD_PIXEL_DATA_COMMAND = REGISTER_COMMAND(UploadPixelDataCommand::new)), - (EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new)), - (SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new)), - (FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new)), - }; - - private final DrawArraysCommand DRAW_ARRAYS_COMMAND; - private final DrawElementsCommand DRAW_ELEMENTS_COMMAND; - private final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND; - private final ToggleCommand TOGGLE_COMMAND; - private final BindFrameBufferCommand BIND_FRAMEBUFFER_COMMAND; - private final ViewportCommand VIEWPORT_COMMAND; - private final ClearCommand CLEAR_COMMAND; - private final ColorMaskCommand COLOR_MASK_COMMAND; - private final DepthFuncCommand DEPTH_FUNC_COMMAND; - private final DepthMaskCommand DEPTH_MASK_COMMAND; - private final BlendFuncCommand BLENDED_FUNC_COMMAND; - private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND; - private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND; - private final BlitFrameBufferCommand BLIT_FRAME_BUFFER_COMMAND; - private final SetUniformBufferPropertyCommand SET_UNIFORM_BUFFER_PROPERTY_COMMAND; - private final SetShaderUniformCommand SET_SHADER_PROPERTY_COMMAND; - private final ShaderProgramCommand SHADER_PROGRAM_COMMAND; - private final UploadPixelDataCommand UPLOAD_PIXEL_DATA_COMMAND; - private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND; - private final SwapBuffersCommand SWAP_BUFFER_COMMAND; - private final FrameTimerCommand FRAME_TIMER_COMMAND; + private final BaseCommand[] REGISTERED_COMMANDS = new BaseCommand[100]; + + private final DrawArraysCommand DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new); + private final DrawElementsCommand DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new); + private final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new); + private final ToggleCommand TOGGLE_COMMAND = REGISTER_COMMAND(ToggleCommand::new); + private final BindFrameBufferCommand BIND_FRAMEBUFFER_COMMAND = REGISTER_COMMAND(BindFrameBufferCommand::new); + private final ViewportCommand VIEWPORT_COMMAND = REGISTER_COMMAND(ViewportCommand::new); + private final ClearCommand CLEAR_COMMAND = REGISTER_COMMAND(ClearCommand::new); + private final ColorMaskCommand COLOR_MASK_COMMAND = REGISTER_COMMAND(ColorMaskCommand::new); + private final DepthFuncCommand DEPTH_FUNC_COMMAND = REGISTER_COMMAND(DepthFuncCommand::new); + private final DepthMaskCommand DEPTH_MASK_COMMAND = REGISTER_COMMAND(DepthMaskCommand::new); + private final BlendFuncCommand BLENDED_FUNC_COMMAND = REGISTER_COMMAND(BlendFuncCommand::new); + private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new); + private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new); + private final BlitFrameBufferCommand BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new); + private final SetUniformBufferPropertyCommand SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new); + private final SetShaderUniformCommand SET_SHADER_PROPERTY_COMMAND = REGISTER_COMMAND(SetShaderUniformCommand::new); + private final ShaderProgramCommand SHADER_PROGRAM_COMMAND = REGISTER_COMMAND(ShaderProgramCommand::new); + private final UploadPixelDataCommand UPLOAD_PIXEL_DATA_COMMAND = REGISTER_COMMAND(UploadPixelDataCommand::new); + private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new); + private final SwapBuffersCommand SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new); + private final FrameTimerCommand FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new); interface ICreateCommand { T construct(); } private T REGISTER_COMMAND(ICreateCommand createCommand) { - T newCommand = createCommand.construct(); - newCommand.id = COMMAND_COUNT; - newCommand.owner = this; - COMMAND_COUNT++; - return newCommand; + { + T ExecutorCommand = createCommand.construct(); + ExecutorCommand.id = COMMAND_COUNT; + ExecutorCommand.owner = this; + + REGISTERED_COMMANDS[COMMAND_COUNT] = ExecutorCommand; + } + + { + T WriterCommand = createCommand.construct(); + WriterCommand.id = COMMAND_COUNT; + WriterCommand.owner = this; + COMMAND_COUNT++; + return WriterCommand; + } } private long[] cmd = new long[1 << 20]; // ~1 million calls @@ -179,7 +167,6 @@ public void SetShaderProgram(ShaderProgram program) { } public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data) { - assert UPLOAD_PIXEL_DATA_COMMAND.data == null; UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; UPLOAD_PIXEL_DATA_COMMAND.tex = tex; UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; From f6c1cfd5105ea01ae572fb1ea3704d3807225071 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:46:52 +0100 Subject: [PATCH 29/50] Flickering UI Fixes --- src/main/java/rs117/hd/HdPlugin.java | 37 +++++++------------ .../opengl/commandbuffer/CommandBuffer.java | 20 ++++++++++ .../commandbuffer/commands/SignalCommand.java | 27 ++++++++++++++ .../commands/UploadPixelDataCommand.java | 11 +++++- src/main/java/rs117/hd/renderer/Renderer.java | 1 + 5 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 7f85fe0843..d8142dd66e 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -45,10 +45,12 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; import javax.annotation.Nullable; import javax.inject.Inject; import javax.swing.SwingUtilities; import lombok.Getter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import net.runelite.api.events.*; @@ -318,6 +320,7 @@ public class HdPlugin extends Plugin { private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling private int texUi; private int pboUi; + private Semaphore uiSemaphore = new Semaphore(1); @Nullable public int[] sceneViewport; @@ -1320,8 +1323,8 @@ public void initializeShaderHotswapping() { }); } + @SneakyThrows public void prepareInterfaceTexture() { - int[] resolution = { max(1, client.getCanvasWidth()), max(1, client.getCanvasHeight()) @@ -1355,30 +1358,9 @@ public void prepareInterfaceTexture() { final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); - backbufferCmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels); - - /* - frameTimer.begin(Timer.MAP_UI_BUFFER); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); - ByteBuffer mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - frameTimer.end(Timer.MAP_UI_BUFFER); - if (mappedBuffer == null) { - log.error("Unable to map interface PBO. Skipping UI..."); - } else if (width > uiResolution[0] || height > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", width, height, uiResolution); - } else { - frameTimer.begin(Timer.COPY_UI); - mappedBuffer.asIntBuffer().put(pixels, 0, width * height); - frameTimer.end(Timer.COPY_UI); + uiSemaphore.tryAcquire(uiSemaphore.availablePermits()); - frameTimer.begin(Timer.UPLOAD_UI); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); - frameTimer.end(Timer.UPLOAD_UI); - } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);*/ + backbufferCmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, uiSemaphore); } public void drawUi(int overlayColor) { @@ -1797,12 +1779,19 @@ public int getExpandedMapLoadingChunks() { return config.expandedMapLoadingChunks(); } + @SneakyThrows @Subscribe(priority = -1) // Run after the low detail plugin public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); + // Wait for UI Copy to finish + frameTimer.begin(Timer.COPY_UI); + uiSemaphore.acquire(); + frameTimer.end(Timer.COPY_UI); + if (client.getScene() == null) return; + // The game runs significantly slower with lower planes in Chambers of Xeric client.getScene().setMinLevel(isInChambersOfXeric ? client.getPlane() : client.getScene().getMinLevel()); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 6bdd5a0ae1..d39cc2f717 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -1,6 +1,7 @@ package rs117.hd.opengl.commandbuffer; import java.util.Arrays; +import java.util.concurrent.Semaphore; import lombok.extern.slf4j.Slf4j; import net.runelite.rlawt.AWTContext; import rs117.hd.HdPlugin; @@ -21,6 +22,7 @@ import rs117.hd.opengl.commandbuffer.commands.SetShaderUniformCommand; import rs117.hd.opengl.commandbuffer.commands.SetUniformBufferPropertyCommand; import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; +import rs117.hd.opengl.commandbuffer.commands.SignalCommand; import rs117.hd.opengl.commandbuffer.commands.SwapBuffersCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; import rs117.hd.opengl.commandbuffer.commands.UploadPixelDataCommand; @@ -69,6 +71,7 @@ public final class CommandBuffer { private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new); private final SwapBuffersCommand SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new); private final FrameTimerCommand FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new); + private final SignalCommand SIGNAL_COMMAND = REGISTER_COMMAND(SignalCommand::new); interface ICreateCommand { T construct(); } @@ -173,6 +176,18 @@ public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height UPLOAD_PIXEL_DATA_COMMAND.width = width; UPLOAD_PIXEL_DATA_COMMAND.height = height; UPLOAD_PIXEL_DATA_COMMAND.data = data; + UPLOAD_PIXEL_DATA_COMMAND.copySema = null; + UPLOAD_PIXEL_DATA_COMMAND.write(); + } + + public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data, Semaphore copySema) { + UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; + UPLOAD_PIXEL_DATA_COMMAND.tex = tex; + UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; + UPLOAD_PIXEL_DATA_COMMAND.width = width; + UPLOAD_PIXEL_DATA_COMMAND.height = height; + UPLOAD_PIXEL_DATA_COMMAND.data = data; + UPLOAD_PIXEL_DATA_COMMAND.copySema = copySema; UPLOAD_PIXEL_DATA_COMMAND.write(); } @@ -285,6 +300,11 @@ public void BlitFramebuffer( BLIT_FRAME_BUFFER_COMMAND.write(); } + public void Signal(Semaphore semaphore) { + SIGNAL_COMMAND.semaphore = semaphore; + SIGNAL_COMMAND.write(); + } + public void BeginTimer(Timer timer) { FRAME_TIMER_COMMAND.timer = timer; FRAME_TIMER_COMMAND.begin = true; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java new file mode 100644 index 0000000000..e4eec35f2a --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java @@ -0,0 +1,27 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.util.concurrent.Semaphore; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public class SignalCommand extends BaseCommand { + + public Semaphore semaphore; + + @Override + protected void doWrite() { + writeObject(semaphore); + } + + @Override + protected void doRead() { + semaphore = readObject(); + } + + @Override + protected void execute() { + semaphore.release(); + } + + @Override + protected void print(StringBuilder sb) {} +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index 96826069b7..a3962bdb62 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -1,6 +1,7 @@ package rs117.hd.opengl.commandbuffer.commands; import java.nio.ByteBuffer; +import java.util.concurrent.Semaphore; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11C.GL_TEXTURE_2D; @@ -23,6 +24,7 @@ public class UploadPixelDataCommand extends BaseCommand { public int width; public int height; public int[] data; + public Semaphore copySema; @Override protected void doWrite() { @@ -32,6 +34,10 @@ protected void doWrite() { write32(width); write32(height); writeObject(data); + if(copySema != null) { + writeFlag(true); + writeObject(copySema); + } data = null; } @@ -43,6 +49,9 @@ protected void doRead() { width = read32(); height = read32(); data = readObject(); + if(readFlag()) { + copySema = readObject(); + } } @Override @@ -52,7 +61,7 @@ protected void execute() { if (mappedBuffer != null) { mappedBuffer.asIntBuffer().put(data, 0, width * height); - data = null; + if(copySema != null) copySema.release(); glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glActiveTexture(texUnit); diff --git a/src/main/java/rs117/hd/renderer/Renderer.java b/src/main/java/rs117/hd/renderer/Renderer.java index abbf0730a0..b67cce1334 100644 --- a/src/main/java/rs117/hd/renderer/Renderer.java +++ b/src/main/java/rs117/hd/renderer/Renderer.java @@ -28,6 +28,7 @@ default boolean isLoadingScene() { } default void reloadScene() {} default void clearCaches() {} + default void onBeforeRender() {} @Nullable default SceneContext getSceneContext() { return null; From 25e986c1858e6779916b91e760d477336a6adca1 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Wed, 22 Oct 2025 14:55:52 +0100 Subject: [PATCH 30/50] More Tweaks --- .../rs117/hd/opengl/commandbuffer/AWTContextWrapper.java | 1 - .../java/rs117/hd/opengl/commandbuffer/RenderThread.java | 2 +- .../commandbuffer/commands/UploadPixelDataCommand.java | 3 ++- src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java index 20bd4f7e53..d5c23501e7 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java @@ -69,7 +69,6 @@ private void updateOwner(Owner newOwner, String action) { .append(action); ownershipLog.add(sb.toString()); - log.debug("Context Ownership: {}", sb); sb.setLength(0); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index ee51b3c52d..19b03d6a8c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -14,7 +14,7 @@ @Slf4j public final class RenderThread implements Runnable { - private static final boolean VALIDATE = false; + private static final boolean VALIDATE = log.isDebugEnabled(); private static final RenderTask POISON_PILL = new RenderTask(); private static final ArrayDeque TASK_BIN = new ArrayDeque<>(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index a3962bdb62..0395581fe7 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -25,6 +25,7 @@ public class UploadPixelDataCommand extends BaseCommand { public int height; public int[] data; public Semaphore copySema; + private ByteBuffer mappedBuffer = null; @Override protected void doWrite() { @@ -57,7 +58,7 @@ protected void doRead() { @Override protected void execute() { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); - ByteBuffer mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); + mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY, mappedBuffer); if (mappedBuffer != null) { mappedBuffer.asIntBuffer().put(data, 0, width * height); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index e6dd6dc683..6f74b2081c 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -345,6 +345,8 @@ public void preSceneDraw( this.maxLevel = maxLevel; this.hideRoofIds = hideRoofIds; + plugin.renderThread.waitForRenderingCompleted(); + if (scene.getWorldViewId() == WorldView.TOPLEVEL) { preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); } else { @@ -361,8 +363,6 @@ private void preSceneDrawTopLevel( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { - plugin.renderThread.waitForRenderingCompleted(); - scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); @@ -758,6 +758,9 @@ private void preSceneDrawTopLevel( @Override public void postSceneDraw(Scene scene) { log.trace("postSceneDraw({})", scene); + + plugin.renderThread.waitForRenderingCompleted(); + if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { @@ -774,8 +777,6 @@ private void postDrawTopLevel() { vaoA.unmap(); - plugin.renderThread.waitForRenderingCompleted(); - // Scene draw state to apply before all recorded commands if (eboAlphaStaging.position() > 0) { eboAlphaStaging.flip(); From b1bef32bbc2e90cd7329e20e74d1e0db906c3ce2 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 07:46:09 +0100 Subject: [PATCH 31/50] Improved Context Swapping Reliability Fixed Legacy no longer working Fixed Zone Loading crashing --- src/main/java/rs117/hd/HdPlugin.java | 133 +++++++------- .../commandbuffer/AWTContextWrapper.java | 78 +++++++- .../opengl/commandbuffer/CommandBuffer.java | 13 +- .../commandbuffer/CommandBufferPool.java | 28 +++ .../hd/opengl/commandbuffer/RenderThread.java | 66 +++++-- .../commands/CallbackCommand.java | 31 ++++ .../commands/ExecuteCommandBufferCommand.java | 2 +- .../commands/UploadPixelDataCommand.java | 2 +- .../java/rs117/hd/overlays/FrameTimer.java | 12 +- .../hd/renderer/legacy/LegacyRenderer.java | 30 +-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 171 ++++++++---------- 11 files changed, 356 insertions(+), 210 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index d8142dd66e..2f605183b9 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -28,7 +28,6 @@ import com.google.gson.Gson; import com.google.inject.Provides; -import java.awt.Canvas; import java.awt.Dimension; import java.awt.GraphicsConfiguration; import java.awt.Image; @@ -45,7 +44,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import javax.inject.Inject; import javax.swing.SwingUtilities; @@ -70,7 +69,6 @@ import net.runelite.client.ui.ClientUI; import net.runelite.client.util.LinkBrowser; import net.runelite.client.util.OSType; -import net.runelite.rlawt.AWTContext; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.*; import org.lwjgl.system.Callback; @@ -82,7 +80,9 @@ import rs117.hd.config.ShadingMode; import rs117.hd.config.ShadowMode; import rs117.hd.config.VanillaShadowMode; +import rs117.hd.opengl.commandbuffer.AWTContextWrapper; import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBufferPool; import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -99,6 +99,7 @@ import rs117.hd.overlays.TiledLightingOverlay; import rs117.hd.overlays.Timer; import rs117.hd.renderer.Renderer; +import rs117.hd.renderer.zone.ZoneRenderer; import rs117.hd.scene.AreaManager; import rs117.hd.scene.EnvironmentManager; import rs117.hd.scene.FishingSpotReplacer; @@ -282,20 +283,25 @@ public class HdPlugin extends Plugin { @Inject private TiledLightingOverlay tiledLightingOverlay; + @Inject + private RenderThread renderThread; + + @Inject + private AWTContextWrapper awtContextWrapper; + + @Inject + private CommandBufferPool commandBufferPool; + @Inject public HDVariables vars; public Renderer renderer; - public RenderThread renderThread; - public static boolean SKIP_GL_ERROR_CHECKS; public static GLCapabilities GL_CAPS; public static GLCapabilities GL_RENDER_THREAD_CAPS; public static boolean AMD_GPU; - public Canvas canvas; - public AWTContext awtContext; private Callback debugCallback; private static final String LINUX_VERSION_HEADER = @@ -320,7 +326,8 @@ public class HdPlugin extends Plugin { private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling private int texUi; private int pboUi; - private Semaphore uiSemaphore = new Semaphore(1); + private int[] stagingUIPixels; + private AtomicBoolean copyInFlight = new AtomicBoolean(false); @Nullable public int[] sceneViewport; @@ -348,8 +355,6 @@ public class HdPlugin extends Plugin { public final UBOLights uboLightsCulling = new UBOLights(true); public final UBOUI uboUI = new UBOUI(); - public final CommandBuffer backbufferCmd = new CommandBuffer().SetExecutionTimer(Timer.BACKBUFFER_COMMAND_BUFFER_EXECUTE); - // Configs used frequently enough to be worth caching public boolean configGroundTextures; public boolean configGroundBlending; @@ -449,23 +454,8 @@ protected void startUp() { lastFrameTimeMillis = 0; lastFrameClientTime = 0; - AWTContext.loadNatives(); - canvas = client.getCanvas(); - - synchronized (canvas.getTreeLock()) { - // Delay plugin startup until the client's canvas is valid - if (!canvas.isValid()) - return false; - - awtContext = new AWTContext(canvas); - awtContext.configurePixelFormat(0, 0, 0); - } - - awtContext.createGLContext(); - - renderThread = new RenderThread(frameTimer, awtContext); - - canvas.setIgnoreRepaint(true); + if(!awtContextWrapper.initalize()) + return false; // lwjgl defaults to lwjgl- + user.name, but this breaks if the username would cause an invalid path // to be created. @@ -565,6 +555,8 @@ protected void startUp() { } } + renderThread.initialize(); + updateCachedConfigs(); developerTools.activate(); @@ -679,13 +671,8 @@ protected void shutDown() { renderer = null; } - if(renderThread != null) - renderThread.shutdown(); - renderThread = null; - - if (awtContext != null) - awtContext.destroy(); - awtContext = null; + renderThread.shutdown(); + awtContextWrapper.shutdown(); if (debugCallback != null) debugCallback.free(); @@ -716,8 +703,6 @@ public void stopPlugin() { public void restartPlugin() { clientThread.invoke(() -> { shutDown(); - // Validate the canvas so it becomes valid without having to manually resize the client - canvas.validate(); startUp(); }); } @@ -1083,7 +1068,7 @@ public void updateTiledLightingFbo() { ARBShaderImageLoadStore.glBindImageTexture( IMAGE_UNIT_TILED_LIGHTING, texTiledLighting, 0, true, 0, GL_WRITE_ONLY, GL_RGBA16UI); - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); checkGLErrors(); @@ -1138,7 +1123,7 @@ public void updateSceneFbo() { sceneViewport = viewport; // Bind default FBO to check whether anti-aliasing is forced - int defaultFramebuffer = awtContext.getFramebuffer(false); + int defaultFramebuffer = awtContextWrapper.getBackBuffer(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); final int forcedAASamples = glGetInteger(GL_SAMPLES); msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); @@ -1210,7 +1195,7 @@ public void updateSceneFbo() { } // Reset - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); glBindRenderbuffer(GL_RENDERBUFFER, 0); } @@ -1289,7 +1274,7 @@ private void initializeShadowMapFbo() { glReadBuffer(GL_NONE); // Reset FBO - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); } private void initializeDummyShadowMap() { @@ -1358,12 +1343,27 @@ public void prepareInterfaceTexture() { final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); - uiSemaphore.tryAcquire(uiSemaphore.availablePermits()); + if(stagingUIPixels == null || stagingUIPixels.length < width * height) { + stagingUIPixels = new int[width * height]; + } - backbufferCmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, uiSemaphore); + copyInFlight.set(true); + + CommandBuffer cmd = commandBufferPool.acquire(); + cmd.Callback(() -> { + System.arraycopy(pixels, 0, stagingUIPixels, 0, width * height); + copyInFlight.set(false); + }); + cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, stagingUIPixels); + if(renderer instanceof ZoneRenderer) { + renderThread.submit(cmd); + } else { + cmd.execute(); + commandBufferPool.release(cmd); + } } - public void drawUi(int overlayColor) { + public void drawUi(int overlayColor, CommandBuffer cmd) { if (uiResolution == null || developerTools.isHideUiEnabled() && hasLoggedIn) return; @@ -1371,20 +1371,20 @@ public void drawUi(int overlayColor) { if (client.getGameState().getState() < GameState.LOADING.getState()) overlayColor = 0; - backbufferCmd.BeginTimer(Timer.RENDER_UI); - backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + cmd.BeginTimer(Timer.RENDER_UI); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); // Disable alpha writes, just in case the default FBO has an alpha channel - backbufferCmd.ColorMask(true, true, true, false); + cmd.ColorMask(true, true, true, false); - backbufferCmd.Viewport(0, 0, uiResolution[0], uiResolution[1]); + cmd.Viewport(0, 0, uiResolution[0], uiResolution[1]); - tiledLightingOverlay.render(backbufferCmd); + tiledLightingOverlay.render(cmd); - backbufferCmd.SetShaderProgram(uiProgram); + cmd.SetShaderProgram(uiProgram); - backbufferCmd.SetUniformProperty(uboUI.sourceDimensions, uiResolution); - backbufferCmd.SetUniformProperty(uboUI.targetDimensions, actualUiResolution); - backbufferCmd.SetUniformProperty(uboUI.alphaOverlay, ColorUtils.srgba(overlayColor)); + cmd.SetUniformProperty(uboUI.sourceDimensions, uiResolution); + cmd.SetUniformProperty(uboUI.targetDimensions, actualUiResolution); + cmd.SetUniformProperty(uboUI.alphaOverlay, ColorUtils.srgba(overlayColor)); // Set the sampling function used when stretching the UI. // This is probably better done with sampler objects instead of texture parameters, but this is easier and likely more portable. @@ -1396,20 +1396,20 @@ public void drawUi(int overlayColor) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, function); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, function); - backbufferCmd.Enable(GL_BLEND); - backbufferCmd.BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - backbufferCmd.BindVertexArray(vaoTri); - backbufferCmd.DrawArrays(GL_TRIANGLES, 0, 3); + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.BindVertexArray(vaoTri); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); - shadowMapOverlay.render(backbufferCmd); - gammaCalibrationOverlay.render(backbufferCmd); + shadowMapOverlay.render(cmd); + gammaCalibrationOverlay.render(cmd); // Reset - backbufferCmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - backbufferCmd.Disable(GL_BLEND); - backbufferCmd.ColorMask(true, true, true, true); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.Disable(GL_BLEND); + cmd.ColorMask(true, true, true, true); - backbufferCmd.EndTimer(Timer.RENDER_UI); + cmd.EndTimer(Timer.RENDER_UI); } /** @@ -1424,7 +1424,7 @@ public Image screenshot() { ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4); - glReadBuffer(awtContext.getBufferMode()); + glReadBuffer(awtContextWrapper.getBufferMode()); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); @@ -1745,7 +1745,7 @@ public void setupSyncMode() { break; } - int actualSwapInterval = awtContext.setSwapInterval(swapInterval); + int actualSwapInterval = awtContextWrapper.setSwapInterval(swapInterval); if (actualSwapInterval != swapInterval) { log.info("unsupported swap interval {}, got {}", swapInterval, actualSwapInterval); } @@ -1784,9 +1784,10 @@ public int getExpandedMapLoadingChunks() { public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); - // Wait for UI Copy to finish frameTimer.begin(Timer.COPY_UI); - uiSemaphore.acquire(); + while (copyInFlight.get()) { + Thread.sleep(1); + } frameTimer.end(Timer.COPY_UI); if (client.getScene() == null) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java index d5c23501e7..8d45485eaf 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java @@ -1,29 +1,75 @@ package rs117.hd.opengl.commandbuffer; +import java.awt.Canvas; import java.util.concurrent.ConcurrentLinkedQueue; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; import net.runelite.rlawt.AWTContext; import org.lwjgl.opengl.*; import rs117.hd.HdPlugin; @Slf4j +@Singleton public final class AWTContextWrapper { + private static final boolean VALIDATE = log.isDebugEnabled(); + public enum Owner { CLIENT, RENDER_THREAD, NONE } - private final AWTContext context; + private final Object ownershipLock = new Object(); private volatile Owner currentOwner = Owner.CLIENT; private static final int MAX_OWNERSHIP_LOG = 8; private final ConcurrentLinkedQueue ownershipLog = new ConcurrentLinkedQueue<>(); private final StringBuilder sb = new StringBuilder(); - private final boolean validate; - public AWTContextWrapper(AWTContext context, boolean validate) { - this.context = context; - this.validate = validate; + @Inject + private Client client; + + @Getter + private AWTContext context; + + @Getter + private Canvas canvas; + + public boolean initalize() { + AWTContext.loadNatives(); + canvas = client.getCanvas(); + + synchronized (canvas.getTreeLock()) { + // Delay plugin startup until the client's canvas is valid + if (!canvas.isValid()) + return false; + + context = new AWTContext(canvas); + context.configurePixelFormat(0, 0, 0); + } + + context.createGLContext(); + canvas.setIgnoreRepaint(true); + return true; + } + + public void shutdown() { + if (context != null) + context.destroy(); + context = null; + + // Validate the canvas so it becomes valid without having to manually resize the client + if(canvas != null) + canvas.validate(); + canvas = null; } + public int setSwapInterval(int interval) { return context.setSwapInterval(interval); } + + public int getBufferMode() { return context.getBufferMode(); } + + public int getBackBuffer() { return context.getFramebuffer(false); } + public synchronized Owner getOwner() { return currentOwner; } @@ -45,7 +91,6 @@ public synchronized void makeCurrent(Owner newOwner) { } } - public synchronized void detachCurrent(String reason) { try { context.detachCurrent(); @@ -57,8 +102,21 @@ public synchronized void detachCurrent(String reason) { } } + public void awaitOwnership(Owner desiredOwner) { + synchronized (ownershipLock) { + while (currentOwner != desiredOwner) { + try { + ownershipLock.wait(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + } + } + private void updateOwner(Owner newOwner, String action) { - if (currentOwner != newOwner && validate) { + if (currentOwner != newOwner && VALIDATE) { if (ownershipLog.size() >= MAX_OWNERSHIP_LOG) ownershipLog.poll(); @@ -74,10 +132,14 @@ private void updateOwner(Owner newOwner, String action) { } currentOwner = newOwner; + + synchronized (ownershipLock) { + ownershipLock.notifyAll(); + } } private void printOwnershipLog() { - if (!validate) return; + if (!VALIDATE) return; if (ownershipLog.isEmpty()) { log.error("No recent ownership transitions recorded."); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index d39cc2f717..58d2ff4acb 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -10,6 +10,7 @@ import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; import rs117.hd.opengl.commandbuffer.commands.BlendFuncCommand; import rs117.hd.opengl.commandbuffer.commands.BlitFrameBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.CallbackCommand; import rs117.hd.opengl.commandbuffer.commands.ClearCommand; import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; import rs117.hd.opengl.commandbuffer.commands.DepthFuncCommand; @@ -71,6 +72,7 @@ public final class CommandBuffer { private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new); private final SwapBuffersCommand SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new); private final FrameTimerCommand FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new); + private final CallbackCommand CALLBACK_COMMAND = REGISTER_COMMAND(CallbackCommand::new); private final SignalCommand SIGNAL_COMMAND = REGISTER_COMMAND(SignalCommand::new); interface ICreateCommand { T construct(); } @@ -93,7 +95,7 @@ private T REGISTER_COMMAND(ICreateCommand createComma } } - private long[] cmd = new long[1 << 20]; // ~1 million calls + private long[] cmd = new long[1000]; private final UniformBuffer[] pendingUBOUploads = new UniformBuffer[100]; private int pendingUBOUploadsCount = 0; @@ -107,6 +109,8 @@ private T REGISTER_COMMAND(ICreateCommand createComma private int readHead = 0; private int readBitHead = 0; + protected boolean pooled; + private Timer executionTimer = null; public CommandBuffer SetExecutionTimer(Timer timer) { @@ -300,6 +304,11 @@ public void BlitFramebuffer( BLIT_FRAME_BUFFER_COMMAND.write(); } + public void Callback(CallbackCommand.ICallback callback) { + CALLBACK_COMMAND.callback = callback; + CALLBACK_COMMAND.write(); + } + public void Signal(Semaphore semaphore) { SIGNAL_COMMAND.semaphore = semaphore; SIGNAL_COMMAND.write(); @@ -351,7 +360,7 @@ public void printCommandBuffer(StringBuilder sb) { } } - public void submit() { + public void execute() { readHead = 0; readBitHead = 0; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java new file mode 100644 index 0000000000..770d6355a1 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java @@ -0,0 +1,28 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.ArrayDeque; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class CommandBufferPool { + + private final ArrayDeque pool = new ArrayDeque<>(); + + public CommandBuffer acquire() { + if (pool.isEmpty()) { + CommandBuffer buffer = new CommandBuffer(); + buffer.pooled = true; + return buffer; + } + + return pool.pop(); + } + + public void release(CommandBuffer buffer) { + assert buffer.pooled; + buffer.reset(); + pool.push(buffer); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 19b03d6a8c..5634e3b0d0 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -7,15 +7,16 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import net.runelite.rlawt.AWTContext; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @Slf4j +@Singleton public final class RenderThread implements Runnable { - private static final boolean VALIDATE = log.isDebugEnabled(); - private static final RenderTask POISON_PILL = new RenderTask(); private static final ArrayDeque TASK_BIN = new ArrayDeque<>(); @@ -25,18 +26,26 @@ public final class RenderThread implements Runnable { private final AtomicInteger pendingCount = new AtomicInteger(0); private final Object completionLock = new Object(); private final AtomicBoolean running = new AtomicBoolean(true); + private Thread thread; + + @Inject + private FrameTimer timer; + + private Thread clientThread; + + @Inject + private AWTContextWrapper contextWrapper; - private final FrameTimer timer; - private final AWTContextWrapper contextWrapper; - private final Thread thread; - - public RenderThread(FrameTimer timer, AWTContext context) { - this.timer = timer; - this.contextWrapper = new AWTContextWrapper(context, VALIDATE); - this.thread = new Thread(this, "HD-RenderThread"); - this.thread.setPriority(Thread.MAX_PRIORITY); - this.thread.setDaemon(true); - this.thread.start(); + @Inject + private CommandBufferPool pool; + + public void initialize() { + clientThread = Thread.currentThread(); + + thread = new Thread(this, "HD-RenderThread"); + thread.setPriority(Thread.MAX_PRIORITY); + thread.setDaemon(true); + thread.start(); } public void submit(CommandBuffer buffer) { @@ -49,8 +58,9 @@ public void submit(CommandBuffer buffer, Runnable onComplete) { pendingCount.incrementAndGet(); - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) { contextWrapper.detachCurrent("detached for render submit"); + } RenderTask newTask = TASK_BIN.poll(); if (newTask == null) { @@ -63,10 +73,18 @@ public void submit(CommandBuffer buffer, Runnable onComplete) { queue.add(newTask); } + @SneakyThrows public void waitForRenderingCompleted() { + if(pendingCount.get() == 0 && completedTasks.isEmpty() && contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) { + return; + } + synchronized (completionLock) { try { while (pendingCount.get() > 0) { + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) + contextWrapper.detachCurrent("waiting for render completion"); + timer.begin(Timer.RENDER_THREAD_COMPLETION); completionLock.wait(); timer.end(Timer.RENDER_THREAD_COMPLETION); @@ -76,7 +94,10 @@ public void waitForRenderingCompleted() { return; } - contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); + if (contextWrapper.getOwner() != AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) { + contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); + } synchronized (completedTasks) { for (RenderTask task : completedTasks) { @@ -88,6 +109,10 @@ public void waitForRenderingCompleted() { } } + if(task.buffer.pooled) { + pool.release(task.buffer); + } + task.buffer = null; task.callback = null; @@ -98,6 +123,7 @@ public void waitForRenderingCompleted() { } } + @SneakyThrows @Override public void run() { log.debug("RenderThread started!"); @@ -119,10 +145,13 @@ public void run() { continue; } - contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); + if (contextWrapper.getOwner() != AWTContextWrapper.Owner.RENDER_THREAD) { + contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); + } try { - task.buffer.submit(); + task.buffer.execute(); } catch (Throwable t) { log.error("Error during CommandBuffer execution", t); } finally { @@ -160,6 +189,7 @@ public void shutdown() { Thread.currentThread().interrupt(); } } + thread = null; } private static class RenderTask { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java new file mode 100644 index 0000000000..112c9da471 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java @@ -0,0 +1,31 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public class CallbackCommand extends BaseCommand { + public interface ICallback { + void run(); + } + + public ICallback callback; + + @Override + protected void doWrite() { + writeObject(callback); + } + + @Override + protected void doRead() { + callback = readObject(); + } + + @Override + public void execute() { + callback.run(); + } + + @Override + public void print(StringBuilder sb) { + sb.append("CallbackCommand"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java index 34e84abdfe..6172130500 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java @@ -19,7 +19,7 @@ protected void doRead() { @Override protected void execute() { - cmd.submit(); + cmd.execute(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index 0395581fe7..18c353d7ec 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -35,8 +35,8 @@ protected void doWrite() { write32(width); write32(height); writeObject(data); + writeFlag(copySema != null); if(copySema != null) { - writeFlag(true); writeObject(copySema); } data = null; diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index d44fd97e54..e129f5d9e2 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -9,6 +9,7 @@ import net.runelite.client.callback.ClientThread; import org.lwjgl.opengl.*; import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.RenderThread; import static org.lwjgl.opengl.GL33C.*; @@ -21,6 +22,9 @@ public class FrameTimer { @Inject private ClientThread clientThread; + @Inject + private RenderThread renderThread; + @Inject private HdPlugin plugin; @@ -46,7 +50,7 @@ public FrameTimer() { private void initialize() { clientThread.invoke(() -> { - plugin.renderThread.waitForRenderingCompleted(); + renderThread.waitForRenderingCompleted(); int[] queryNames = new int[NUM_GPU_TIMERS * 2]; glGenQueries(queryNames); @@ -133,8 +137,10 @@ public void begin(Timer timer) { return; if (timer.isGpuTimer) { - if (activeTimers[timer.ordinal()]) - throw new UnsupportedOperationException("Cumulative GPU timing isn't supported"); + if (activeTimers[timer.ordinal()]) { + log.warn("Cumulative GPU timing isn't supported"); + return; + } glQueryCounter(gpuQueries[timer.ordinal() * 2], GL_TIMESTAMP); } else if (!activeTimers[timer.ordinal()]) { cumulativeError += errorCompensation + 1 >> 1; diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index 66d964ed33..da10ad4c99 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -31,6 +31,9 @@ import rs117.hd.config.DynamicLights; import rs117.hd.model.ModelHasher; import rs117.hd.model.ModelOffsets; +import rs117.hd.opengl.commandbuffer.AWTContextWrapper; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBufferPool; import rs117.hd.opengl.compute.ComputeMode; import rs117.hd.opengl.compute.OpenCLManager; import rs117.hd.opengl.shader.ModelPassthroughComputeProgram; @@ -93,6 +96,12 @@ public class LegacyRenderer implements Renderer { @Inject private ClientThread clientThread; + + @Inject + private AWTContextWrapper awtContextWrapper; + + @Inject + private CommandBufferPool commandBufferPool; @Inject private EventBus eventBus; @@ -243,7 +252,7 @@ public void initialize() { int maxComputeThreadCount; if (computeMode == ComputeMode.OPENCL) { - clManager.startUp(this, plugin.awtContext); + clManager.startUp(this, awtContextWrapper.getContext()); maxComputeThreadCount = clManager.getMaxWorkGroupSize(); } else { maxComputeThreadCount = glGetInteger(GL43C.GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS); @@ -1273,7 +1282,7 @@ public void draw(int overlayColor) { } // Blit from the resolved FBO to the default FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); glBlitFramebuffer( 0, 0, @@ -1291,18 +1300,19 @@ public void draw(int overlayColor) { glClear(GL_COLOR_BUFFER_BIT); } - plugin.backbufferCmd.reset(); - plugin.drawUi(overlayColor); - plugin.backbufferCmd.submit(); + CommandBuffer cmd = commandBufferPool.acquire(); + plugin.drawUi(overlayColor, cmd); + cmd.execute(); + commandBufferPool.release(cmd); try { frameTimer.begin(Timer.SWAP_BUFFERS); - plugin.awtContext.swapBuffers(); + awtContextWrapper.getContext().swapBuffers(); frameTimer.end(Timer.SWAP_BUFFERS); drawManager.processDrawComplete(plugin::screenshot); } catch (RuntimeException ex) { // this is always fatal - if (!plugin.canvas.isValid()) { + if (!awtContextWrapper.getCanvas().isValid()) { // this might be AWT shutting down on VM shutdown, ignore it return; } @@ -1310,7 +1320,7 @@ public void draw(int overlayColor) { log.error("Unable to swap buffers:", ex); } - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); frameTimer.end(Timer.DRAW_FRAME); frameTimer.end(Timer.RENDER_FRAME); @@ -1325,8 +1335,6 @@ public void reloadScene() { if (client.getGameState().getState() < GameState.LOGGED_IN.getState()) return; - plugin.renderThread.waitForRenderingCompleted(); - Scene scene = client.getTopLevelWorldView().getScene(); loadScene(scene); if (plugin.skipScene == scene) @@ -1339,8 +1347,6 @@ public void loadScene(Scene scene) { if (!plugin.isActive()) return; - plugin.renderThread.waitForRenderingCompleted(); - int expandedChunks = plugin.getExpandedMapLoadingChunks(); if (HDUtils.sceneIntersects(scene, expandedChunks, areaManager.getArea("PLAYER_OWNED_HOUSE"))) { // Reload once the POH is done loading diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 6f74b2081c..49fff574b9 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -32,7 +32,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; @@ -48,7 +47,10 @@ import rs117.hd.HdPluginConfig; import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; +import rs117.hd.opengl.commandbuffer.AWTContextWrapper; import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBufferPool; +import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -103,7 +105,16 @@ public class ZoneRenderer implements Renderer { @Inject private ClientThread clientThread; + + @Inject + private RenderThread renderThread; + @Inject + private AWTContextWrapper awtContextWrapper; + + @Inject + private CommandBufferPool commandBufferPool; + @Inject private DrawManager drawManager; @@ -152,10 +163,7 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; - private final CommandBuffer scenePassCmd = new CommandBuffer().SetExecutionTimer(Timer.SCENE_COMMAND_BUFFER_EXECUTE); private final CommandBuffer sceneDrawCmd = new CommandBuffer(); - - private final CommandBuffer directionalPassCmd = new CommandBuffer().SetExecutionTimer(Timer.SHADOW_COMMAND_BUFFER_EXECUTE); private final CommandBuffer directionalDrawCmd = new CommandBuffer(); private VAO.VAOList vaoO; @@ -271,7 +279,7 @@ public void destroy() { @Override public void waitUntilIdle() { - plugin.renderThread.waitForRenderingCompleted(); + renderThread.waitForRenderingCompleted(); glFinish(); } @@ -345,8 +353,6 @@ public void preSceneDraw( this.maxLevel = maxLevel; this.hideRoofIds = hideRoofIds; - plugin.renderThread.waitForRenderingCompleted(); - if (scene.getWorldViewId() == WorldView.TOPLEVEL) { preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); } else { @@ -363,6 +369,8 @@ private void preSceneDrawTopLevel( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { + renderThread.waitForRenderingCompleted(); + scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); @@ -745,7 +753,6 @@ private void preSceneDrawTopLevel( eboAlphaStaging.clear(); // Reset Command Buffers - plugin.backbufferCmd.reset(); sceneDrawCmd.reset(); directionalDrawCmd.reset(); @@ -758,9 +765,6 @@ private void preSceneDrawTopLevel( @Override public void postSceneDraw(Scene scene) { log.trace("postSceneDraw({})", scene); - - plugin.renderThread.waitForRenderingCompleted(); - if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { @@ -784,43 +788,40 @@ private void postDrawTopLevel() { glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); } - directionalPassCmd.reset(); - scenePassCmd.reset(); + CommandBuffer cmd = commandBufferPool.acquire(); if (plugin.configShadowsEnabled && plugin.fboShadowMap != 0 && environmentManager.currentDirectionalStrength > 0 ) { // Render to the shadow depth map - directionalPassCmd.BeginTimer(Timer.RENDER_FRAME); - directionalPassCmd.BeginTimer(Timer.RENDER_SHADOWS); - directionalPassCmd.Viewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); - directionalPassCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); - directionalPassCmd.ClearDepth(1.0f); + cmd.BeginTimer(Timer.RENDER_FRAME); + cmd.BeginTimer(Timer.RENDER_SHADOWS); + cmd.Viewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); + cmd.ClearDepth(1.0f); - directionalPassCmd.Enable(GL_DEPTH_TEST); - directionalPassCmd.Disable(GL_CULL_FACE); - directionalPassCmd.SetDepthFunc(GL_LEQUAL); + cmd.Enable(GL_DEPTH_TEST); + cmd.Disable(GL_CULL_FACE); + cmd.SetDepthFunc(GL_LEQUAL); - directionalPassCmd.SetShaderProgram(plugin.shadowProgram); + cmd.SetShaderProgram(plugin.shadowProgram); - directionalPassCmd.ExecuteCommandBuffer(directionalDrawCmd); + cmd.ExecuteCommandBuffer(directionalDrawCmd); - directionalPassCmd.Disable(GL_DEPTH_TEST); + cmd.Disable(GL_DEPTH_TEST); - directionalPassCmd.EndTimer(Timer.RENDER_SHADOWS); - - plugin.renderThread.submit(directionalPassCmd); + cmd.EndTimer(Timer.RENDER_SHADOWS); } else { - scenePassCmd.BeginTimer(Timer.RENDER_FRAME); + cmd.BeginTimer(Timer.RENDER_FRAME); } - scenePassCmd.BeginTimer(Timer.RENDER_SCENE); - scenePassCmd.SetShaderProgram(sceneProgram); + cmd.BeginTimer(Timer.RENDER_SCENE); + cmd.SetShaderProgram(sceneProgram); - scenePassCmd.BindFrameBuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); - scenePassCmd.Toggle(GL_MULTISAMPLE, plugin.msaaSamples > 1); - scenePassCmd.Viewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + cmd.BindFrameBuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); + cmd.Toggle(GL_MULTISAMPLE, plugin.msaaSamples > 1); + cmd.Viewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); @@ -831,26 +832,26 @@ private void postDrawTopLevel() { 1f ); - scenePassCmd.BeginTimer(Timer.CLEAR_SCENE); - scenePassCmd.ClearColorAndDepth(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1f, 0.0f); - scenePassCmd.EndTimer(Timer.CLEAR_SCENE); + cmd.BeginTimer(Timer.CLEAR_SCENE); + cmd.ClearColorAndDepth(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1f, 0.0f); + cmd.EndTimer(Timer.CLEAR_SCENE); - scenePassCmd.Enable(GL_CULL_FACE); - scenePassCmd.Enable(GL_DEPTH_TEST); - scenePassCmd.SetDepthFunc(GL_GREATER); + cmd.Enable(GL_CULL_FACE); + cmd.Enable(GL_DEPTH_TEST); + cmd.SetDepthFunc(GL_GREATER); - scenePassCmd.Enable(GL_BLEND); - scenePassCmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - scenePassCmd.ExecuteCommandBuffer(sceneDrawCmd); + cmd.ExecuteCommandBuffer(sceneDrawCmd); - scenePassCmd.Disable(GL_CULL_FACE); - scenePassCmd.Disable(GL_DEPTH_TEST); - scenePassCmd.Disable(GL_BLEND); + cmd.Disable(GL_CULL_FACE); + cmd.Disable(GL_DEPTH_TEST); + cmd.Disable(GL_BLEND); - scenePassCmd.EndTimer(Timer.RENDER_SCENE); + cmd.EndTimer(Timer.RENDER_SCENE); - plugin.renderThread.submit(scenePassCmd); + renderThread.submit(cmd); frameTimer.end(Timer.DRAW_SCENE); @@ -891,8 +892,6 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int if (!z.initialized || z.sizeO == 0) return; - plugin.renderThread.waitForRenderingCompleted(); - int offset = ctx.sceneContext.sceneOffset >> 3; if (z.inSceneFrustum) { sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); @@ -928,8 +927,6 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i if (!z.initialized) return; - plugin.renderThread.waitForRenderingCompleted(); - boolean hasAlpha = z.sizeA != 0 || !z.alphaModels.isEmpty(); boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; @@ -991,8 +988,6 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (ctx == null) return; - plugin.renderThread.waitForRenderingCompleted(); - switch (pass) { case DrawCallbacks.PASS_OPAQUE: vaoO.addRange(scene); @@ -1064,8 +1059,6 @@ public void drawDynamic( if (modelOverride.hide) return; - plugin.renderThread.waitForRenderingCompleted(); - int preOrientation = HDUtils.getModelPreOrientation(HDUtils.getObjectConfig(tileObject)); byte[] transparencies = m.getFaceTransparencies(); @@ -1119,8 +1112,6 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (modelOverride.hide) return; - plugin.renderThread.waitForRenderingCompleted(); - int preOrientation = HDUtils.getModelPreOrientation(gameObject.getConfig()); int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; @@ -1233,7 +1224,7 @@ private void rebuild(WorldView wv) { if (!zone.invalidate) continue; - plugin.renderThread.waitForRenderingCompleted(); + renderThread.waitForRenderingCompleted(); assert zone.initialized; zone.free(); @@ -1289,37 +1280,32 @@ public void draw(int overlayColor) { return; } + CommandBuffer cmd = commandBufferPool.acquire(); if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { - plugin.backbufferCmd.BlitFramebuffer( - plugin.fboScene, plugin.fboSceneResolve, plugin.awtContext.getFramebuffer(false), + cmd.BlitFramebuffer( + plugin.fboScene, plugin.fboSceneResolve, awtContextWrapper.getBackBuffer(), plugin.sceneResolution[0], plugin.sceneResolution[1], plugin.sceneViewport[0], plugin.sceneViewport[1], plugin.sceneViewport[2], plugin.sceneViewport[3], config.sceneScalingMode().glFilter); } else { - plugin.backbufferCmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - plugin.backbufferCmd.ClearColor(0, 0, 0, 1); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); + cmd.ClearColor(0, 0, 0, 1); } - plugin.drawUi(overlayColor); + plugin.drawUi(overlayColor, cmd); - plugin.backbufferCmd.BeginTimer(Timer.SWAP_BUFFERS); - plugin.backbufferCmd.SwapBuffers(plugin.awtContext); - plugin.backbufferCmd.EndTimer(Timer.SWAP_BUFFERS); - plugin.backbufferCmd.EndTimer(Timer.RENDER_FRAME); + cmd.BeginTimer(Timer.SWAP_BUFFERS); + cmd.SwapBuffers(awtContextWrapper.getContext()); + cmd.EndTimer(Timer.SWAP_BUFFERS); + cmd.EndTimer(Timer.RENDER_FRAME); frameTimer.end(Timer.DRAW_FRAME); - plugin.renderThread.submit(plugin.backbufferCmd, this::onBackBufferSwapCompleted); - - if(client.getGameState().getState() < GameState.LOGGED_IN.getState() || root.sceneContext == null) { - plugin.renderThread.waitForRenderingCompleted(); - } + renderThread.submit(cmd, this::onBackBufferSwapCompleted); } public void onBackBufferSwapCompleted() { frameTimer.endFrameAndReset(); - - plugin.backbufferCmd.reset(); } @Subscribe @@ -1344,8 +1330,6 @@ public void reloadScene() { if (client.getGameState().getState() < GameState.LOGGED_IN.getState() || root.sceneContext == null) return; - plugin.renderThread.waitForRenderingCompleted(); - proceduralGenerator.generateSceneData(root.sceneContext); for (int i = 0; i < NUM_ZONES; i++) for (int j = 0; j < NUM_ZONES; j++) @@ -1510,11 +1494,8 @@ public void loadScene(WorldView worldView, Scene scene) { ); // allocate buffers for zones which require upload - CountDownLatch latch = new CountDownLatch(1); - clientThread.invoke(() -> - { - plugin.renderThread.waitForRenderingCompleted(); - + CommandBuffer cmd = commandBufferPool.acquire(); + cmd.Callback(() -> { for (int x = 0; x < EXTENDED_SCENE_SIZE >> 3; ++x) { for (int z = 0; z < EXTENDED_SCENE_SIZE >> 3; ++z) { Zone zone = newZones[x][z]; @@ -1541,14 +1522,9 @@ public void loadScene(WorldView worldView, Scene scene) { zone.initialize(o, a, eboAlpha); } } - - latch.countDown(); }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + renderThread.submit(cmd); + renderThread.waitForRenderingCompleted(); // upload zones sw = Stopwatch.createStarted(); @@ -1652,11 +1628,9 @@ private void loadSubScene(WorldView worldView, Scene scene) { sceneUploader.zoneSize(sceneContext, ctx.zones[x][z], x, z); // allocate buffers for zones which require upload - CountDownLatch latch = new CountDownLatch(1); - clientThread.invoke(() -> + CommandBuffer cmd = commandBufferPool.acquire(); + cmd.Callback(() -> { - plugin.renderThread.waitForRenderingCompleted(); - for (int x = 0; x < ctx.sizeX; ++x) { for (int z = 0; z < ctx.sizeZ; ++z) { Zone zone = ctx.zones[x][z]; @@ -1679,14 +1653,9 @@ private void loadSubScene(WorldView worldView, Scene scene) { zone.initialize(o, a, eboAlpha); } } - - latch.countDown(); }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } + renderThread.submit(cmd); + renderThread.waitForRenderingCompleted(); for (int x = 0; x < ctx.sizeX; ++x) for (int z = 0; z < ctx.sizeZ; ++z) @@ -1710,6 +1679,9 @@ public void despawnWorldView(WorldView worldView) { @Override public void swapScene(Scene scene) { log.trace("swapScene({})", scene); + + renderThread.waitForRenderingCompleted(); + if (scene.getWorldViewId() > -1) { swapSub(scene); return; @@ -1728,6 +1700,7 @@ public void swapScene(Scene scene) { return; // Return early if scene loading failed } + lightManager.loadSceneLights(nextSceneContext, root.sceneContext); fishingSpotReplacer.despawnRuneLiteObjects(); npcDisplacementCache.clear(); From 4eefb737ea03cee481a9b56c169b26cc3973131d Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:09:42 +0100 Subject: [PATCH 32/50] Fixed edge case where MapLoader thread could call Completion callbacks not on the client thread --- .../hd/opengl/commandbuffer/RenderThread.java | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 5634e3b0d0..8af2de503a 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -79,10 +79,11 @@ public void waitForRenderingCompleted() { return; } + boolean isClientThread = Thread.currentThread() == clientThread; synchronized (completionLock) { try { while (pendingCount.get() > 0) { - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread) contextWrapper.detachCurrent("waiting for render completion"); timer.begin(Timer.RENDER_THREAD_COMPLETION); @@ -94,31 +95,33 @@ public void waitForRenderingCompleted() { return; } - if (contextWrapper.getOwner() != AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) { - contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); - contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); - } + if(isClientThread) { + if (contextWrapper.getOwner() != AWTContextWrapper.Owner.CLIENT) { + contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); + } - synchronized (completedTasks) { - for (RenderTask task : completedTasks) { - if (task.callback != null) { - try { - task.callback.run(); - } catch (Throwable t) { - log.warn("Exception in render completion callback", t); + synchronized (completedTasks) { + for (RenderTask task : completedTasks) { + if (task.callback != null) { + try { + task.callback.run(); + } catch (Throwable t) { + log.warn("Exception in render completion callback", t); + } } - } - if(task.buffer.pooled) { - pool.release(task.buffer); - } + if (task.buffer.pooled) { + pool.release(task.buffer); + } - task.buffer = null; - task.callback = null; + task.buffer = null; + task.callback = null; - TASK_BIN.add(task); + TASK_BIN.add(task); + } + completedTasks.clear(); } - completedTasks.clear(); } } } From 4c495f4eaf968ddcd6c29ddb3252aee00d8f94a7 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 08:22:46 +0100 Subject: [PATCH 33/50] Cleanup --- src/main/java/rs117/hd/HdPlugin.java | 2 +- .../java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java | 2 +- .../hd/opengl/commandbuffer/commands/DrawArraysCommand.java | 2 +- .../opengl/commandbuffer/commands/MultiDrawArraysCommand.java | 4 ++-- .../opengl/commandbuffer/commands/UploadPixelDataCommand.java | 2 ++ 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 2f605183b9..f1f1db678a 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -454,7 +454,7 @@ protected void startUp() { lastFrameTimeMillis = 0; lastFrameClientTime = 0; - if(!awtContextWrapper.initalize()) + if(!awtContextWrapper.initialize()) return false; // lwjgl defaults to lwjgl- + user.name, but this breaks if the username would cause an invalid path diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java index 8d45485eaf..548ddce8f3 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java @@ -35,7 +35,7 @@ public enum Owner { CLIENT, RENDER_THREAD, NONE } @Getter private Canvas canvas; - public boolean initalize() { + public boolean initialize() { AWTContext.loadNatives(); canvas = client.getCanvas(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java index d9626530f4..911a32598c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -34,7 +34,7 @@ public void print(StringBuilder sb) { sb.append(mode); sb.append(", "); sb.append(vertexCount); - sb.append(", GL_UNSIGNED_INT, "); + sb.append(", "); sb.append(offset); sb.append(");"); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index 31329aeae6..e8443ed381 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -40,8 +40,8 @@ public void doRead() { if(offsetsBuffer != null) MemoryUtil.memFree(offsetsBuffer); if(countsBuffer != null) MemoryUtil.memFree(countsBuffer); - offsetsBuffer = MemoryUtil.memAllocInt(length * 2); - countsBuffer = MemoryUtil.memAllocInt(length * 2); + offsetsBuffer = MemoryUtil.memAllocInt(length); + countsBuffer = MemoryUtil.memAllocInt(length); } else { offsetsBuffer.clear(); countsBuffer.clear(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index 18c353d7ec..91f3de5d45 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -68,6 +68,8 @@ protected void execute() { glActiveTexture(texUnit); glBindTexture(GL_TEXTURE_2D, tex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + } else { + if(copySema != null) copySema.release(); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } From e0c920cbd25ccd0f42b21c13a2c4e1c5c12c349b Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:23:43 +0100 Subject: [PATCH 34/50] Fixed Init & Shutdown issues with RenderThread --- src/main/java/rs117/hd/HdPlugin.java | 10 +++++++++- .../rs117/hd/opengl/commandbuffer/RenderThread.java | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index f1f1db678a..fdca9735cf 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -327,7 +327,7 @@ public class HdPlugin extends Plugin { private int texUi; private int pboUi; private int[] stagingUIPixels; - private AtomicBoolean copyInFlight = new AtomicBoolean(false); + private final AtomicBoolean copyInFlight = new AtomicBoolean(false); @Nullable public int[] sceneViewport; @@ -674,6 +674,9 @@ protected void shutDown() { renderThread.shutdown(); awtContextWrapper.shutdown(); + GL_CAPS = null; + GL_RENDER_THREAD_CAPS = null; + if (debugCallback != null) debugCallback.free(); debugCallback = null; @@ -1785,8 +1788,13 @@ public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); frameTimer.begin(Timer.COPY_UI); + long wait = 1000; while (copyInFlight.get()) { Thread.sleep(1); + wait -= 1; + if(wait <= 0) { + break; + } } frameTimer.end(Timer.COPY_UI); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 8af2de503a..108962439b 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -25,7 +25,7 @@ public final class RenderThread implements Runnable { private final AtomicInteger pendingCount = new AtomicInteger(0); private final Object completionLock = new Object(); - private final AtomicBoolean running = new AtomicBoolean(true); + private final AtomicBoolean running = new AtomicBoolean(false); private Thread thread; @Inject @@ -42,6 +42,8 @@ public final class RenderThread implements Runnable { public void initialize() { clientThread = Thread.currentThread(); + running.set(true); + thread = new Thread(this, "HD-RenderThread"); thread.setPriority(Thread.MAX_PRIORITY); thread.setDaemon(true); From cdd45b195e89ee199b2d04e43f4a3da209bb568f Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 18:18:07 +0100 Subject: [PATCH 35/50] Tweaks --- src/main/java/rs117/hd/HdPlugin.java | 57 ++++++++------- .../hd/opengl/commandbuffer/BaseCommand.java | 5 +- .../opengl/commandbuffer/CommandBuffer.java | 38 ++++++---- .../hd/opengl/commandbuffer/RenderThread.java | 55 +++++++------- .../commands/BindElementsArrayCommand.java | 5 +- .../commands/BindFrameBufferCommand.java | 5 +- .../commands/BindVertexArrayCommand.java | 5 +- .../commands/BlendFuncCommand.java | 5 +- .../commands/BlitFrameBufferCommand.java | 5 +- .../commands/CallbackCommand.java | 5 +- .../commandbuffer/commands/ClearCommand.java | 5 +- .../commands/ColorMaskCommand.java | 5 +- .../commands/DepthFuncCommand.java | 5 +- .../commands/DepthMaskCommand.java | 5 +- .../commands/DrawArraysCommand.java | 5 +- .../commands/DrawElementsCommand.java | 5 +- .../commands/ExecuteCommandBufferCommand.java | 7 +- .../commands/FrameTimerCommand.java | 5 +- .../commands/MultiDrawArraysCommand.java | 26 +++---- .../commands/SetShaderUniformCommand.java | 5 +- .../SetUniformBufferPropertyCommand.java | 5 +- .../commands/ShaderProgramCommand.java | 5 +- .../commandbuffer/commands/SignalCommand.java | 5 +- .../commands/SwapBuffersCommand.java | 5 +- .../commandbuffer/commands/ToggleCommand.java | 5 +- .../commands/UploadPixelDataCommand.java | 71 ++++++++++++++----- .../commands/ViewportCommand.java | 5 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 71 ++++++++++--------- 28 files changed, 259 insertions(+), 171 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index fdca9735cf..7bef3fb1f9 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -45,6 +45,7 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; import javax.annotation.Nullable; import javax.inject.Inject; import javax.swing.SwingUtilities; @@ -56,6 +57,7 @@ import net.runelite.api.hooks.*; import net.runelite.client.RuneLite; import net.runelite.client.callback.ClientThread; +import net.runelite.client.callback.Hooks; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; @@ -292,6 +294,9 @@ public class HdPlugin extends Plugin { @Inject private CommandBufferPool commandBufferPool; + @Inject + private Hooks hooks; + @Inject public HDVariables vars; @@ -326,8 +331,9 @@ public class HdPlugin extends Plugin { private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling private int texUi; private int pboUi; - private int[] stagingUIPixels; - private final AtomicBoolean copyInFlight = new AtomicBoolean(false); + private boolean uploadInFlight = false; + private final AtomicBoolean stageTrigger = new AtomicBoolean(false); + private final AtomicBoolean stageComplete = new AtomicBoolean(false); @Nullable public int[] sceneViewport; @@ -599,6 +605,8 @@ protected void startUp() { gammaCalibrationOverlay.initialize(); npcDisplacementCache.initialize(); + hooks.registerRenderableDrawListener(this::onDraw); + isActive = true; hasLoggedIn = client.getGameState().getState() > GameState.LOGGING_IN.getState(); redrawPreviousFrame = false; @@ -621,6 +629,21 @@ protected void startUp() { }); } + @SneakyThrows + private boolean onDraw(Renderable renderable, boolean drawingUI) { + if (uploadInFlight) { + frameTimer.begin(Timer.COPY_UI); + + stageTrigger.set(true); + while (!stageComplete.get()) { + LockSupport.parkNanos(100000); + } + frameTimer.end(Timer.COPY_UI); + uploadInFlight = false; + } + return true; + } + @Override protected void shutDown() { isActive = false; @@ -1030,8 +1053,6 @@ private void destroyUiTexture() { public void updateTiledLightingFbo() { assert configTiledLighting; - renderer.waitUntilIdle(); - int[] newResolution = max(ivec(1), round(divide(vec(sceneResolution), TILED_LIGHTING_TILE_SIZE))); int newLayerCount = configDynamicLights.getTiledLightingLayers(); if (Arrays.equals(newResolution, tiledLightingResolution) && tiledLightingLayerCount == newLayerCount) @@ -1346,18 +1367,14 @@ public void prepareInterfaceTexture() { final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); - if(stagingUIPixels == null || stagingUIPixels.length < width * height) { - stagingUIPixels = new int[width * height]; - } - - copyInFlight.set(true); + uploadInFlight = true; + stageTrigger.set(false); + stageComplete.set(false); CommandBuffer cmd = commandBufferPool.acquire(); - cmd.Callback(() -> { - System.arraycopy(pixels, 0, stagingUIPixels, 0, width * height); - copyInFlight.set(false); - }); - cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, stagingUIPixels); + cmd.BeginTimer(Timer.UPLOAD_UI); + cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, stageTrigger, stageComplete); + cmd.EndTimer(Timer.UPLOAD_UI); if(renderer instanceof ZoneRenderer) { renderThread.submit(cmd); } else { @@ -1787,17 +1804,6 @@ public int getExpandedMapLoadingChunks() { public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); - frameTimer.begin(Timer.COPY_UI); - long wait = 1000; - while (copyInFlight.get()) { - Thread.sleep(1); - wait -= 1; - if(wait <= 0) { - break; - } - } - frameTimer.end(Timer.COPY_UI); - if (client.getScene() == null) return; @@ -1809,7 +1815,6 @@ public void onBeforeRender(BeforeRender beforeRender) { public void onClientTick(ClientTick clientTick) { elapsedClientTime += 1 / 50f; - if (!enableFreezeFrame && skipScene != client.getScene()) redrawPreviousFrame = false; } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java index cf3935eee2..73bce74c9d 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.uniforms.UniformBuffer; @Slf4j @@ -30,7 +31,7 @@ protected BaseCommand(boolean isDrawCall, boolean isGLCommand) { protected BaseCommand() { this(false, false); } - protected abstract void execute(); + protected abstract void execute(MemoryStack stack); protected abstract void print(StringBuilder sb); protected final void write() { @@ -39,7 +40,7 @@ protected final void write() { } protected abstract void doWrite(); - protected abstract void doRead(); + protected abstract void doRead(MemoryStack stack); protected final void markUniformBufferDirty(UniformBuffer buffer) { owner.markUniformBufferDirty(buffer); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 58d2ff4acb..b936a613a4 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -2,8 +2,10 @@ import java.util.Arrays; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; import net.runelite.rlawt.AWTContext; +import org.lwjgl.system.MemoryStack; import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; import rs117.hd.opengl.commandbuffer.commands.BindFrameBufferCommand; @@ -180,18 +182,20 @@ public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height UPLOAD_PIXEL_DATA_COMMAND.width = width; UPLOAD_PIXEL_DATA_COMMAND.height = height; UPLOAD_PIXEL_DATA_COMMAND.data = data; - UPLOAD_PIXEL_DATA_COMMAND.copySema = null; + UPLOAD_PIXEL_DATA_COMMAND.stageData = null; + UPLOAD_PIXEL_DATA_COMMAND.stageComplete = null; UPLOAD_PIXEL_DATA_COMMAND.write(); } - public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data, Semaphore copySema) { + public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data, AtomicBoolean stageData, AtomicBoolean stageComplete) { UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; UPLOAD_PIXEL_DATA_COMMAND.tex = tex; UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; UPLOAD_PIXEL_DATA_COMMAND.width = width; UPLOAD_PIXEL_DATA_COMMAND.height = height; UPLOAD_PIXEL_DATA_COMMAND.data = data; - UPLOAD_PIXEL_DATA_COMMAND.copySema = copySema; + UPLOAD_PIXEL_DATA_COMMAND.stageData = stageData; + UPLOAD_PIXEL_DATA_COMMAND.stageComplete = stageComplete; UPLOAD_PIXEL_DATA_COMMAND.write(); } @@ -349,18 +353,26 @@ public void printCommandBuffer(StringBuilder sb) { readHead = 0; readBitHead = 0; - while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { - int type = (int) readBits(8); - assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; + try (MemoryStack stack = MemoryStack.stackPush()){ + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + int type = (int) readBits(8); + assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; - BaseCommand command = REGISTERED_COMMANDS[type]; - command.doRead(); - command.print(sb); - sb.append('\n'); + BaseCommand command = REGISTERED_COMMANDS[type]; + command.doRead(stack); + command.print(sb); + sb.append('\n'); + } } } public void execute() { + try(MemoryStack stack = MemoryStack.stackPush()) { + execute(stack); + } + } + + public void execute(MemoryStack stack) { readHead = 0; readBitHead = 0; @@ -388,11 +400,13 @@ public void execute() { pendingUBOUploadsCount = 0; } + stack.push(); readTimestamp = System.nanoTime(); - command.doRead(); + command.doRead(stack); readElapsed += System.nanoTime() - readTimestamp; - command.execute(); + command.execute(stack); + stack.pop(); if(command.isGLCommand() && HdPlugin.checkGLErrors(command.getName())) { //StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 108962439b..037833b8d7 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -11,12 +11,15 @@ import javax.inject.Singleton; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.lwjgl.system.MemoryStack; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @Slf4j @Singleton public final class RenderThread implements Runnable { + public static final int RENDER_THREAD_STACK_SIZE = 10 * 1024 * 1024; // 10 MB + private static final RenderTask POISON_PILL = new RenderTask(); private static final ArrayDeque TASK_BIN = new ArrayDeque<>(); @@ -133,34 +136,38 @@ public void waitForRenderingCompleted() { public void run() { log.debug("RenderThread started!"); - while (running.get()) { - RenderTask task; - try { - task = queue.take(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } + try(MemoryStack stack = MemoryStack.create(RENDER_THREAD_STACK_SIZE)) { + while (running.get()) { + RenderTask task; + try { + task = queue.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } - if (task == POISON_PILL) - break; + if (task == POISON_PILL) + break; - if (task.buffer == null) { - taskCompleted(task); - continue; - } + if (task.buffer == null) { + taskCompleted(task); + continue; + } - if (contextWrapper.getOwner() != AWTContextWrapper.Owner.RENDER_THREAD) { - contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); - contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); - } + if (contextWrapper.getOwner() != AWTContextWrapper.Owner.RENDER_THREAD) { + contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); + } - try { - task.buffer.execute(); - } catch (Throwable t) { - log.error("Error during CommandBuffer execution", t); - } finally { - taskCompleted(task); + try { + stack.push(); + task.buffer.execute(stack); + stack.pop(); + } catch (Throwable t) { + log.error("Error during CommandBuffer execution", t); + } finally { + taskCompleted(task); + } } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java index abf4c1efa8..3b618087b7 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; @@ -16,12 +17,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { ebo = read32(); } @Override - public void execute() { + public void execute(MemoryStack stack) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java index 79010399f4..ed4ca27fc0 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL30C.glBindFramebuffer; @@ -17,13 +18,13 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { target = read32(); fbo = read32(); } @Override - protected void execute() { glBindFramebuffer(target, fbo); } + protected void execute(MemoryStack stack) { glBindFramebuffer(target, fbo); } @Override protected void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java index be9659fae0..277b99b3bc 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL30.glBindVertexArray; @@ -15,12 +16,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { vao = read32(); } @Override - public void execute() { + public void execute(MemoryStack stack) { glBindVertexArray(vao); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java index 77bf541902..4cd50ffac9 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; @@ -21,7 +22,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { sfactorRGB = read32(); dfactorRGB = read32(); sfactorAlpha = read32(); @@ -29,7 +30,7 @@ protected void doRead() { } @Override - protected void execute() { + protected void execute(MemoryStack stack) { glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java index ea2d0d4068..75d9a410fd 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; @@ -27,7 +28,7 @@ public class BlitFrameBufferCommand extends BaseCommand { public BlitFrameBufferCommand() { super(false, true); } @Override - protected void execute() { + protected void execute(MemoryStack stack) { glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); if (resolveFbo != 0) { @@ -66,7 +67,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { srcFbo = read32(); resolveFbo = read32(); dstFbo = read32(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java index 112c9da471..41d078fe51 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; public class CallbackCommand extends BaseCommand { @@ -15,12 +16,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { callback = readObject(); } @Override - public void execute() { + public void execute(MemoryStack stack) { callback.run(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java index faba8331df..125a7f96bd 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; @@ -38,7 +39,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { clearColor = readFlag(); if(clearColor) { @@ -55,7 +56,7 @@ protected void doRead() { } @Override - protected void execute() { + protected void execute(MemoryStack stack) { if(clearColor) { glClearColor(red, green, blue, alpha); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java index 7091b5851e..4341da30e6 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11.glColorMask; @@ -21,7 +22,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { red = readFlag(); green = readFlag(); blue = readFlag(); @@ -29,7 +30,7 @@ protected void doRead() { } @Override - public void execute() { glColorMask(red, green, blue, alpha); } + public void execute(MemoryStack stack) { glColorMask(red, green, blue, alpha); } @Override public void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java index 5f9ef44802..8eed3df780 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11C.glDepthFunc; @@ -14,10 +15,10 @@ public class DepthFuncCommand extends BaseCommand { protected void doWrite() { write32(mode); } @Override - protected void doRead() { mode = read32(); } + protected void doRead(MemoryStack stack) { mode = read32(); } @Override - protected void execute() { glDepthFunc(mode); } + protected void execute(MemoryStack stack) { glDepthFunc(mode); } @Override protected void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java index ea95c5958b..576429a121 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11.glDepthMask; @@ -17,12 +18,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { flag = readFlag(); } @Override - public void execute() { + public void execute(MemoryStack stack) { if(SKIP_DEPTH_MASKING) return; glDepthMask(flag); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java index 911a32598c..8ac0d4179c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11C.glDrawArrays; @@ -19,14 +20,14 @@ public void doWrite() { } @Override - public void doRead() { + public void doRead(MemoryStack stack) { mode = read8(); vertexCount = read32(); offset = read32(); } @Override - public void execute() { glDrawArrays(mode, offset, vertexCount); } + public void execute(MemoryStack stack) { glDrawArrays(mode, offset, vertexCount); } @Override public void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java index 6c008b6c8c..d95e30c656 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; @@ -20,14 +21,14 @@ public void doWrite() { } @Override - public void doRead() { + public void doRead(MemoryStack stack) { mode = read8(); vertexCount = read32(); bytesOffset = read64(); } @Override - public void execute() { + public void execute(MemoryStack stack) { glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, bytesOffset); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java index 6172130500..45b285f422 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.commandbuffer.CommandBuffer; @@ -13,13 +14,13 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { cmd = readObject(); } @Override - protected void execute() { - cmd.execute(); + protected void execute(MemoryStack stack) { + cmd.execute(stack); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java index f89cd7f748..4bcad6beaf 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @@ -15,13 +16,13 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { timer = Timer.TIMERS[read32()]; begin = readFlag(); } @Override - protected void execute() { + protected void execute(MemoryStack stack) { if(begin) { FrameTimer.getInstance().begin(timer); } else { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java index e8443ed381..ccdfb399d5 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -1,7 +1,7 @@ package rs117.hd.opengl.commandbuffer.commands; import java.nio.IntBuffer; -import org.lwjgl.system.MemoryUtil; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL14.glMultiDrawArrays; @@ -11,8 +11,8 @@ public final class MultiDrawArraysCommand extends BaseCommand { public int[] offsets; public int[] counts; - public IntBuffer offsetsBuffer; - public IntBuffer countsBuffer; + private IntBuffer offsetsBuffer; + private IntBuffer countsBuffer; public MultiDrawArraysCommand() { super(true); } @@ -32,20 +32,12 @@ public void doWrite() { } @Override - public void doRead() { + public void doRead(MemoryStack stack) { mode = read8(); int length = read32(); - if(offsetsBuffer == null || offsetsBuffer.capacity() < length) { - if(offsetsBuffer != null) MemoryUtil.memFree(offsetsBuffer); - if(countsBuffer != null) MemoryUtil.memFree(countsBuffer); - - offsetsBuffer = MemoryUtil.memAllocInt(length); - countsBuffer = MemoryUtil.memAllocInt(length); - } else { - offsetsBuffer.clear(); - countsBuffer.clear(); - } + offsetsBuffer = stack.callocInt(length); + countsBuffer = stack.callocInt(length); for(int i = 0; i < length; i++) { offsetsBuffer.put(read32()); @@ -57,7 +49,11 @@ public void doRead() { } @Override - public void execute() { glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); } + public void execute(MemoryStack stack) { + glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); + offsetsBuffer = null; // TODO: Kinda need a clear function? + countsBuffer = null; + } @Override public void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java index 2e86cb1e1d..8c0d0578c9 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.shader.ShaderProgram; @@ -34,7 +35,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { property = readObject(); isFloat = readFlag(); stagingSize = read32(); @@ -56,7 +57,7 @@ protected void doRead() { } @Override - protected void execute() { + protected void execute(MemoryStack stack) { if(isFloat) { if (property instanceof ShaderProgram.Uniform1f) { ((ShaderProgram.Uniform1f) property).set(stagingFloatValues[0]); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java index 70f215533a..cf46dc94fd 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.uniforms.UniformBuffer; @@ -36,7 +37,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { property = readObject(); isFloat = readFlag(); stagingSize = read32(); @@ -58,7 +59,7 @@ protected void doRead() { } @Override - protected void execute() { + protected void execute(MemoryStack stack) { if(isFloat) { switch (stagingSize) { case 1: diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java index aa1b4fc5f8..8519207460 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.shader.ShaderProgram; @@ -14,12 +15,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { program = readObject(); } @Override - protected void execute() { + protected void execute(MemoryStack stack) { program.use(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java index e4eec35f2a..c080980230 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java @@ -1,6 +1,7 @@ package rs117.hd.opengl.commandbuffer.commands; import java.util.concurrent.Semaphore; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; public class SignalCommand extends BaseCommand { @@ -13,12 +14,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { semaphore = readObject(); } @Override - protected void execute() { + protected void execute(MemoryStack stack) { semaphore.release(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java index 5fdf1182d2..fc302bc67b 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java @@ -1,6 +1,7 @@ package rs117.hd.opengl.commandbuffer.commands; import net.runelite.rlawt.AWTContext; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; public class SwapBuffersCommand extends BaseCommand { @@ -13,12 +14,12 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { awtContext = readObject(); } @Override - protected void execute() { + protected void execute(MemoryStack stack) { awtContext.swapBuffers(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java index 9fd80e8814..0c158138ce 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11.glDisable; @@ -18,13 +19,13 @@ public void doWrite() { } @Override - public void doRead() { + public void doRead(MemoryStack stack) { capability = read32(); state = readFlag(); } @Override - public void execute() { + public void execute(MemoryStack stack) { if (state) { glEnable(capability); } else { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index 91f3de5d45..9d39e20048 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -1,7 +1,10 @@ package rs117.hd.opengl.commandbuffer.commands; import java.nio.ByteBuffer; -import java.util.concurrent.Semaphore; +import java.nio.IntBuffer; +import java.util.concurrent.atomic.AtomicBoolean; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11C.GL_TEXTURE_2D; @@ -17,14 +20,16 @@ import static org.lwjgl.opengl.GL21C.GL_PIXEL_UNPACK_BUFFER; public class UploadPixelDataCommand extends BaseCommand { - public int texUnit; public int tex; public int pbo; public int width; public int height; public int[] data; - public Semaphore copySema; + public AtomicBoolean stageData; + public AtomicBoolean stageComplete; + + private IntBuffer stagingData = null; private ByteBuffer mappedBuffer = null; @Override @@ -35,15 +40,18 @@ protected void doWrite() { write32(width); write32(height); writeObject(data); - writeFlag(copySema != null); - if(copySema != null) { - writeObject(copySema); + if(stageData != null && stageComplete != null) { + writeFlag(true); + writeObject(stageData); + writeObject(stageComplete); + } else { + writeFlag(false); } data = null; } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { texUnit = read32(); tex = read32(); pbo = read32(); @@ -51,31 +59,60 @@ protected void doRead() { height = read32(); data = readObject(); if(readFlag()) { - copySema = readObject(); + stageData = readObject(); + stageComplete = readObject(); + } else { + stageData = null; + stageComplete = null; } } @Override - protected void execute() { + protected void execute(MemoryStack stack) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); - mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY, mappedBuffer); + + mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY, (long)(width * height) * Integer.BYTES, mappedBuffer); if (mappedBuffer != null) { - mappedBuffer.asIntBuffer().put(data, 0, width * height); - if(copySema != null) copySema.release(); + IntBuffer mappedIntBuffer = mappedBuffer.asIntBuffer(); + + boolean stagingSupport = stageData != null && stageComplete != null; + if(stagingSupport && (stagingData == null || stagingData.capacity() < data.length)) { + stagingData = MemoryUtil.memAllocInt(data.length); + } + + int chunks = 8; + int remaining = width * height; + int chunkSize = remaining / chunks; + int offset = 0; + for(int i = 0; i < chunks; i++) { + if (stagingSupport && stageData.get()) { + stagingData.put(data, offset, remaining); + stageComplete.set(true); + + stagingData.flip(); + mappedIntBuffer.put(stagingData); + stagingData.clear(); + break; + } else { + mappedIntBuffer.put(data, offset, chunkSize); + offset += chunkSize; + remaining -= chunkSize; + } + } + + if (stagingSupport) { + stageComplete.set(true); + } glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glActiveTexture(texUnit); glBindTexture(GL_TEXTURE_2D, tex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); - } else { - if(copySema != null) copySema.release(); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } @Override - protected void print(StringBuilder sb) { - - } + protected void print(StringBuilder sb) {} } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java index 2ed02d1cbd..9dac7f05e5 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.commandbuffer.commands; +import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; import static org.lwjgl.opengl.GL11C.glViewport; @@ -21,7 +22,7 @@ protected void doWrite() { } @Override - protected void doRead() { + protected void doRead(MemoryStack stack) { x = read32(); y = read32(); width = read32(); @@ -29,7 +30,7 @@ protected void doRead() { } @Override - protected void execute() { glViewport(x, y, width, height); } + protected void execute(MemoryStack stack) { glViewport(x, y, width, height); } @Override protected void print(StringBuilder sb) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 49fff574b9..554c0d59d0 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -162,9 +162,10 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; + private boolean isDrawingScene; - private final CommandBuffer sceneDrawCmd = new CommandBuffer(); - private final CommandBuffer directionalDrawCmd = new CommandBuffer(); + private final CommandBuffer zoneDrawCallBuffer = new CommandBuffer(); + private final CommandBuffer zoneShadowDrawCallBuffer = new CommandBuffer(); private VAO.VAOList vaoO; private VAO.VAOList vaoA; @@ -360,8 +361,8 @@ public void preSceneDraw( vaoO.addRange(topLevel); vaoPO.addRange(topLevel); vaoPOShadow.addRange(topLevel); - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); } } @@ -370,6 +371,7 @@ private void preSceneDrawTopLevel( float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { renderThread.waitForRenderingCompleted(); + isDrawingScene = true; scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); @@ -753,11 +755,11 @@ private void preSceneDrawTopLevel( eboAlphaStaging.clear(); // Reset Command Buffers - sceneDrawCmd.reset(); - directionalDrawCmd.reset(); + zoneDrawCallBuffer.reset(); + zoneShadowDrawCallBuffer.reset(); - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); checkGLErrors(); } @@ -768,8 +770,8 @@ public void postSceneDraw(Scene scene) { if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); } } @@ -807,7 +809,7 @@ private void postDrawTopLevel() { cmd.SetShaderProgram(plugin.shadowProgram); - cmd.ExecuteCommandBuffer(directionalDrawCmd); + cmd.ExecuteCommandBuffer(zoneShadowDrawCallBuffer); cmd.Disable(GL_DEPTH_TEST); @@ -843,7 +845,7 @@ private void postDrawTopLevel() { cmd.Enable(GL_BLEND); cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - cmd.ExecuteCommandBuffer(sceneDrawCmd); + cmd.ExecuteCommandBuffer(zoneDrawCallBuffer); cmd.Disable(GL_CULL_FACE); cmd.Disable(GL_DEPTH_TEST); @@ -894,15 +896,15 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int int offset = ctx.sceneContext.sceneOffset >> 3; if (z.inSceneFrustum) { - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); - z.renderOpaque(uboCommandBuffer, sceneDrawCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + z.renderOpaque(uboCommandBuffer, zoneDrawCallBuffer, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); } if (z.inShadowFrustum) { - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderOpaque( uboCommandBuffer, - directionalDrawCmd, + zoneShadowDrawCallBuffer, zx - offset, zz - offset, minLevel, @@ -931,11 +933,11 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; if (renderWater || hasAlpha) - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); int offset = ctx.sceneContext.sceneOffset >> 3; if (renderWater) - z.renderOpaqueLevel(uboCommandBuffer, sceneDrawCmd, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); + z.renderOpaqueLevel(uboCommandBuffer, zoneDrawCallBuffer, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); if (!hasAlpha) return; @@ -948,7 +950,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i if (z.inSceneFrustum) { z.renderAlpha( uboCommandBuffer, - sceneDrawCmd, + zoneDrawCallBuffer, zx - offset, zz - offset, minLevel, @@ -962,10 +964,10 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i } if (z.inShadowFrustum) { - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderAlpha( uboCommandBuffer, - directionalDrawCmd, + zoneShadowDrawCallBuffer, zx - offset, zz - offset, minLevel, @@ -995,31 +997,31 @@ public void drawPass(Projection projection, Scene scene, int pass) { vaoPOShadow.addRange(scene); if (scene.getWorldViewId() == -1) { - sceneDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); - directionalDrawCmd.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); // Draw opaque vaoO.unmap(); - vaoO.drawAll(this, sceneDrawCmd); - vaoO.drawAll(this, directionalDrawCmd); + vaoO.drawAll(this, zoneDrawCallBuffer); + vaoO.drawAll(this, zoneShadowDrawCallBuffer); vaoO.resetAll(); vaoPO.unmap(); // Draw player shadows vaoPOShadow.unmap(); - vaoPOShadow.drawAll(this, directionalDrawCmd); + vaoPOShadow.drawAll(this, zoneShadowDrawCallBuffer); vaoPOShadow.resetAll(); // Draw players opaque, without depth writes - sceneDrawCmd.DepthMask(false); - vaoPO.drawAll(this, sceneDrawCmd); - sceneDrawCmd.DepthMask(true); + zoneDrawCallBuffer.DepthMask(false); + vaoPO.drawAll(this, zoneDrawCallBuffer); + zoneDrawCallBuffer.DepthMask(true); // Draw players opaque, writing only depth - sceneDrawCmd.ColorMask(false, false, false, false); - vaoPO.drawAll(this, sceneDrawCmd); - sceneDrawCmd.ColorMask(true, true, true, true); + zoneDrawCallBuffer.ColorMask(false, false, false, false); + vaoPO.drawAll(this, zoneDrawCallBuffer); + zoneDrawCallBuffer.ColorMask(true, true, true, true); vaoPO.resetAll(); } @@ -1270,6 +1272,10 @@ public void draw(int overlayColor) { return; } + if(!isDrawingScene) { + renderThread.waitForRenderingCompleted(); + } + try { plugin.prepareInterfaceTexture(); } catch (Exception ex) { @@ -1306,6 +1312,7 @@ public void draw(int overlayColor) { public void onBackBufferSwapCompleted() { frameTimer.endFrameAndReset(); + isDrawingScene = false; } @Subscribe From 512546b3f2e4f949f31808fe3c7782477e8b8948 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 20:55:07 +0100 Subject: [PATCH 36/50] Fixed Deadlock that occurs when Client releases, then reobtains the AWTContext ownership, causing the RenderThread to fail to make current --- .../commandbuffer/AWTContextWrapper.java | 50 +++++++++---------- .../hd/opengl/commandbuffer/RenderThread.java | 10 +--- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java index 548ddce8f3..ad29161474 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java @@ -70,28 +70,37 @@ public void shutdown() { public int getBackBuffer() { return context.getFramebuffer(false); } - public synchronized Owner getOwner() { - return currentOwner; - } + public Owner getOwner() { return currentOwner; } - public synchronized void makeCurrent(Owner newOwner) { + public void makeCurrent(Owner newOwner) { if (currentOwner == newOwner) return; - try { - context.makeCurrent(); - if(newOwner == Owner.RENDER_THREAD && HdPlugin.GL_RENDER_THREAD_CAPS == null){ - HdPlugin.GL_RENDER_THREAD_CAPS = GL.createCapabilities(); + synchronized (ownershipLock) { + while (currentOwner != Owner.NONE) { + try { + ownershipLock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + + try { + context.makeCurrent(); + if(newOwner == Owner.RENDER_THREAD && HdPlugin.GL_RENDER_THREAD_CAPS == null){ + HdPlugin.GL_RENDER_THREAD_CAPS = GL.createCapabilities(); + } + updateOwner(newOwner, "makeCurrent"); + } catch (Exception e) { + printOwnershipLog(); + log.error("Failed to make OpenGL context current for {}", newOwner, e); + throw new RuntimeException(e); } - updateOwner(newOwner, "makeCurrent"); - } catch (Exception e) { - printOwnershipLog(); - log.error("Failed to make OpenGL context current for {}", newOwner, e); - throw new RuntimeException(e); } } - public synchronized void detachCurrent(String reason) { + public void detachCurrent(String reason) { try { context.detachCurrent(); updateOwner(Owner.NONE, reason); @@ -102,19 +111,6 @@ public synchronized void detachCurrent(String reason) { } } - public void awaitOwnership(Owner desiredOwner) { - synchronized (ownershipLock) { - while (currentOwner != desiredOwner) { - try { - ownershipLock.wait(1); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } - } - } - } - private void updateOwner(Owner newOwner, String action) { if (currentOwner != newOwner && VALIDATE) { if (ownershipLog.size() >= MAX_OWNERSHIP_LOG) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 037833b8d7..3052c200aa 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -101,10 +101,7 @@ public void waitForRenderingCompleted() { } if(isClientThread) { - if (contextWrapper.getOwner() != AWTContextWrapper.Owner.CLIENT) { - contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); - contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); - } + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); synchronized (completedTasks) { for (RenderTask task : completedTasks) { @@ -154,10 +151,7 @@ public void run() { continue; } - if (contextWrapper.getOwner() != AWTContextWrapper.Owner.RENDER_THREAD) { - contextWrapper.awaitOwnership(AWTContextWrapper.Owner.NONE); - contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); - } + contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); try { stack.push(); From fcb77f0f7aa1f1f0cbbc8584694f49df5149af90 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:15:06 +0100 Subject: [PATCH 37/50] Added Config option to disable RenderThread --- src/main/java/rs117/hd/HdPlugin.java | 2 ++ src/main/java/rs117/hd/HdPluginConfig.java | 13 +++++++++++- .../hd/opengl/commandbuffer/RenderThread.java | 21 +++++++++++++++++++ .../rs117/hd/renderer/zone/ZoneRenderer.java | 4 ++-- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 7bef3fb1f9..d600537803 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -382,6 +382,7 @@ public class HdPlugin extends Plugin { public boolean configCharacterDisplacement; public boolean configTiledLighting; public boolean configTiledLightingImageLoadStore; + public boolean configRenderThread; public DynamicLights configDynamicLights; public ShadowMode configShadowMode; public SeasonalTheme configSeasonalTheme; @@ -1496,6 +1497,7 @@ private void updateCachedConfigs() { configDynamicLights = config.dynamicLights(); configTiledLighting = config.tiledLighting(); configTiledLightingImageLoadStore = config.tiledLightingImageLoadStore(); + configRenderThread = config.renderThread(); configExpandShadowDraw = config.expandShadowDraw(); configUseFasterModelHashing = config.fasterModelHashing(); configUndoVanillaShading = config.shadingMode() != ShadingMode.VANILLA; diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index 09f0bd70ca..b04b321735 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -245,7 +245,7 @@ default SyncMode syncMode() ) @Range( min = 0, - max = 999 + max = 2000 ) default int fpsTarget() { @@ -1109,6 +1109,17 @@ default boolean tiledLightingImageLoadStore() { return true; } + String KEY_RENDER_THREAD = "experimentalRenderThread"; + @ConfigItem( + keyName = KEY_RENDER_THREAD, + name = "Render Thread", + description = "Its a Render Thread...", + section = experimentalSettings + ) + default boolean renderThread() { + return true; + } + /*====== Internal settings ======*/ diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 3052c200aa..ff728f0eff 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -12,6 +12,7 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.lwjgl.system.MemoryStack; +import rs117.hd.HdPlugin; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @@ -31,6 +32,9 @@ public final class RenderThread implements Runnable { private final AtomicBoolean running = new AtomicBoolean(false); private Thread thread; + @Inject + private HdPlugin plugin; + @Inject private FrameTimer timer; @@ -57,10 +61,27 @@ public void submit(CommandBuffer buffer) { submit(buffer, null); } + public void submit(CommandBuffer buffer, boolean forceThread) { + submit(buffer, null, forceThread); + } + + public void submit(CommandBuffer buffer, Runnable onComplete) { + submit(buffer, onComplete, false); + } + + public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThread) { if (buffer == null) throw new IllegalArgumentException("CommandBuffer cannot be null"); + if(!plugin.configRenderThread && !forceThread) { + buffer.execute(); + if (onComplete != null) { + onComplete.run(); + } + return; + } + pendingCount.incrementAndGet(); if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 554c0d59d0..e53f7ed0d6 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -1530,7 +1530,7 @@ public void loadScene(WorldView worldView, Scene scene) { } } }); - renderThread.submit(cmd); + renderThread.submit(cmd, true); renderThread.waitForRenderingCompleted(); // upload zones @@ -1661,7 +1661,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { } } }); - renderThread.submit(cmd); + renderThread.submit(cmd, true); renderThread.waitForRenderingCompleted(); for (int x = 0; x < ctx.sizeX; ++x) From 5975a175c5ffa99a08b50a04fb0361ac166cba4d Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:40:14 +0100 Subject: [PATCH 38/50] preSceneDrawTopLevel now uses the RenderThread, meaning it doesn't need to wait for rendering to complete --- src/main/java/rs117/hd/HdPlugin.java | 3 +- .../opengl/commandbuffer/CommandBuffer.java | 50 ++++++++++++++---- .../commands/DrawBufferCommand.java | 45 ++++++++++++++++ .../commands/FrameBufferLayerCommand.java | 51 +++++++++++++++++++ .../commands/UploadUniformBufferCommand.java | 33 ++++++++++++ .../hd/opengl/uniforms/UBOWorldViews.java | 2 - .../rs117/hd/renderer/zone/ZoneRenderer.java | 49 +++++++++++------- 7 files changed, 202 insertions(+), 31 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index d600537803..af1f440ed9 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -1059,6 +1059,7 @@ public void updateTiledLightingFbo() { if (Arrays.equals(newResolution, tiledLightingResolution) && tiledLightingLayerCount == newLayerCount) return; + renderThread.waitForRenderingCompleted(); destroyTiledLightingFbo(); tiledLightingResolution = newResolution; @@ -1142,7 +1143,7 @@ public void updateSceneFbo() { if (Arrays.equals(sceneViewport, viewport)) return; - renderer.waitUntilIdle(); + renderThread.waitForRenderingCompleted(); destroySceneFbo(); sceneViewport = viewport; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index b936a613a4..bc5adbf283 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -18,8 +18,10 @@ import rs117.hd.opengl.commandbuffer.commands.DepthFuncCommand; import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; import rs117.hd.opengl.commandbuffer.commands.DrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawBufferCommand; import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; import rs117.hd.opengl.commandbuffer.commands.ExecuteCommandBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.FrameBufferLayerCommand; import rs117.hd.opengl.commandbuffer.commands.FrameTimerCommand; import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; import rs117.hd.opengl.commandbuffer.commands.SetShaderUniformCommand; @@ -29,6 +31,7 @@ import rs117.hd.opengl.commandbuffer.commands.SwapBuffersCommand; import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; import rs117.hd.opengl.commandbuffer.commands.UploadPixelDataCommand; +import rs117.hd.opengl.commandbuffer.commands.UploadUniformBufferCommand; import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.uniforms.UniformBuffer; @@ -75,7 +78,10 @@ public final class CommandBuffer { private final SwapBuffersCommand SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new); private final FrameTimerCommand FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new); private final CallbackCommand CALLBACK_COMMAND = REGISTER_COMMAND(CallbackCommand::new); - private final SignalCommand SIGNAL_COMMAND = REGISTER_COMMAND(SignalCommand::new); + private final SignalCommand SIGNAL_COMMAND = REGISTER_COMMAND(SignalCommand::new);; + private final UploadUniformBufferCommand UPLOAD_UNIFORM_BUFFER_COMMAND = REGISTER_COMMAND(UploadUniformBufferCommand::new); + private final DrawBufferCommand DRAW_BUFFER_COMMAND = REGISTER_COMMAND(DrawBufferCommand::new); + private final FrameBufferLayerCommand FRAME_BUFFER_LAYER_COMMAND = REGISTER_COMMAND(FrameBufferLayerCommand::new); interface ICreateCommand { T construct(); } @@ -148,6 +154,19 @@ public void SetUniformProperty(ShaderProgram.UniformProperty property, float... SET_SHADER_PROPERTY_COMMAND.write(); } + public void FrameBufferLayer(int attachment, int texId, int level, int layer) { + FRAME_BUFFER_LAYER_COMMAND.attachment = attachment; + FRAME_BUFFER_LAYER_COMMAND.texId = texId; + FRAME_BUFFER_LAYER_COMMAND.level = level; + FRAME_BUFFER_LAYER_COMMAND.layer = layer; + FRAME_BUFFER_LAYER_COMMAND.write(); + } + + public void DrawBuffers(int... drawBuffers) { + DRAW_BUFFER_COMMAND.drawBuffers = drawBuffers; + DRAW_BUFFER_COMMAND.write(); + } + public void BindVertexArray(int vao) { BIND_VERTEX_ARRAY_COMMAND.vao = vao; BIND_VERTEX_ARRAY_COMMAND.write(); @@ -318,6 +337,11 @@ public void Signal(Semaphore semaphore) { SIGNAL_COMMAND.write(); } + public void UploadUniformBuffer(UniformBuffer buffer) { + UPLOAD_UNIFORM_BUFFER_COMMAND.buffer = buffer; + UPLOAD_UNIFORM_BUFFER_COMMAND.write(); + } + public void BeginTimer(Timer timer) { FRAME_TIMER_COMMAND.timer = timer; FRAME_TIMER_COMMAND.begin = true; @@ -390,15 +414,8 @@ public void execute(MemoryStack stack) { BaseCommand command = REGISTERED_COMMANDS[type]; - if (command.isDrawCall()) { - for(int i = 0; i < pendingUBOUploadsCount; i++) { - if(pendingUBOUploads[i].isDirty()) { - pendingUBOUploads[i].upload(); - pendingUBOUploads[i] = null; - } - } - pendingUBOUploadsCount = 0; - } + if (command.isDrawCall()) + uploadPendingUniformBuffers(); stack.push(); readTimestamp = System.nanoTime(); @@ -415,12 +432,25 @@ public void execute(MemoryStack stack) { //break; } } + + uploadPendingUniformBuffers(); + if(executionTimer != null) { FrameTimer.getInstance().add(executionTimer, System.nanoTime() - executeTimestamp); } FrameTimer.getInstance().add(Timer.COMMAND_BUFFER_READ, readElapsed); } + private void uploadPendingUniformBuffers() { + for(int i = 0; i < pendingUBOUploadsCount; i++) { + if(pendingUBOUploads[i].isDirty()) { + pendingUBOUploads[i].upload(); + pendingUBOUploads[i] = null; + } + } + pendingUBOUploadsCount = 0; + } + protected void markUniformBufferDirty(UniformBuffer buffer) { for(int i = 0; i < pendingUBOUploadsCount; i++) { if(pendingUBOUploads[i] == buffer) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java new file mode 100644 index 0000000000..435e343457 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java @@ -0,0 +1,45 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL20.glDrawBuffers; + +public class DrawBufferCommand extends BaseCommand { + + public int[] drawBuffers; + + private IntBuffer stagingDrawBuffers; + + public DrawBufferCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(drawBuffers.length); + for (int drawBuffer : drawBuffers) { + write32(drawBuffer); + } + drawBuffers = null; + } + + @Override + protected void doRead(MemoryStack stack) { + int count = read32(); + stagingDrawBuffers = stack.mallocInt(count); + for(int i = 0; i < count; i++) { + stagingDrawBuffers.put(read32()); + } + stagingDrawBuffers.flip(); + } + + @Override + protected void execute(MemoryStack stack) { + glDrawBuffers(stagingDrawBuffers); + } + + @Override + protected void print(StringBuilder sb) { + + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java new file mode 100644 index 0000000000..6e86ec43ef --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java @@ -0,0 +1,51 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30C.glFramebufferTextureLayer; + +public class FrameBufferLayerCommand extends BaseCommand { + + public int attachment; + public int texId; + public int level; + public int layer; + + public FrameBufferLayerCommand() {super(false, true);} + + @Override + protected void doWrite() { + write32(attachment); + write32(texId); + write32(level); + write32(layer); + } + + @Override + protected void doRead(MemoryStack stack) { + attachment = read32(); + texId = read32(); + level = read32(); + layer = read32(); + } + + @Override + protected void execute(MemoryStack stack) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, texId, level, layer); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("glFramebufferTextureLayer(GL_FRAMEBUFFER, "); + sb.append(attachment); + sb.append(", "); + sb.append(texId); + sb.append(", "); + sb.append(level); + sb.append(", "); + sb.append(layer); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java new file mode 100644 index 0000000000..7ca40e51dd --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java @@ -0,0 +1,33 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.uniforms.UniformBuffer; + +public final class UploadUniformBufferCommand extends BaseCommand { + + public UniformBuffer buffer; + + public UploadUniformBufferCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeObject(buffer); + } + + @Override + protected void doRead(MemoryStack stack) { + buffer = readObject(); + } + + @Override + protected void execute(MemoryStack stack) { + markUniformBufferDirty(buffer); + } + + @Override + protected void print(StringBuilder sb) { + sb.append(buffer.getClass().getSimpleName()); + sb.append(".upload()"); + } +} diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java b/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java index 3df77d3e71..0c7f857ad5 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java @@ -69,8 +69,6 @@ public void update(Client client) { ); } } - - upload(); } public int getIndex(@Nullable Scene scene) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index e53f7ed0d6..dad0e5a624 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -370,7 +370,6 @@ private void preSceneDrawTopLevel( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { - renderThread.waitForRenderingCompleted(); isDrawingScene = true; scene.setDrawDistance(plugin.getDrawDistance()); @@ -379,6 +378,8 @@ private void preSceneDrawTopLevel( if (root.sceneContext == null || plugin.sceneViewport == null) return; + CommandBuffer cmd = commandBufferPool.acquire(); + frameTimer.begin(Timer.DRAW_FRAME); frameTimer.begin(Timer.DRAW_SCENE); @@ -569,7 +570,7 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.projectionMatrix.set(plugin.viewProjMatrix); plugin.uboGlobal.invProjectionMatrix.set(plugin.invViewProjMatrix); plugin.uboGlobal.pointLightsCount.set(root.sceneContext.numVisibleLights); - plugin.uboGlobal.upload(); + cmd.UploadUniformBuffer(plugin.uboGlobal); } } @@ -603,39 +604,39 @@ private void preSceneDrawTopLevel( plugin.uboLightsCulling.setLight(i, lightPosition, lightColor); } } - - plugin.uboLights.upload(); - plugin.uboLightsCulling.upload(); frameTimer.end(Timer.UPDATE_LIGHTS); + cmd.UploadUniformBuffer(plugin.uboLights); + cmd.UploadUniformBuffer(plugin.uboLightsCulling); + // Perform tiled lighting culling before the compute memory barrier, so it's performed asynchronously if (plugin.configTiledLighting) { plugin.updateTiledLightingFbo(); assert plugin.fboTiledLighting != 0; frameTimer.begin(Timer.DRAW_TILED_LIGHTING); - frameTimer.begin(Timer.RENDER_TILED_LIGHTING); + cmd.BeginTimer(Timer.RENDER_TILED_LIGHTING); - glViewport(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); - glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboTiledLighting); + cmd.Viewport(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboTiledLighting); - glBindVertexArray(plugin.vaoTri); + cmd.BindVertexArray(plugin.vaoTri); if (plugin.tiledLightingImageStoreProgram.isValid()) { - plugin.tiledLightingImageStoreProgram.use(); - glDrawBuffer(GL_NONE); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.SetShaderProgram(plugin.tiledLightingImageStoreProgram); + cmd.DrawBuffers(GL_NONE); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); } else { - glDrawBuffer(GL_COLOR_ATTACHMENT0); + cmd.DrawBuffers(GL_COLOR_ATTACHMENT0); int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); for (int layer = 0; layer < layerCount; layer++) { - plugin.tiledLightingShaderPrograms.get(layer).use(); - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.SetShaderProgram(plugin.tiledLightingShaderPrograms.get(layer)); + cmd.FrameBufferLayer(GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); } } - frameTimer.end(Timer.RENDER_TILED_LIGHTING); + cmd.EndTimer(Timer.RENDER_TILED_LIGHTING); frameTimer.end(Timer.DRAW_TILED_LIGHTING); } } @@ -748,9 +749,13 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.colorFilterFade.set(clamp(timeSinceChange / COLOR_FILTER_FADE_DURATION, 0, 1)); } - plugin.uboGlobal.upload(); uboWorldViews.update(client); + cmd.UploadUniformBuffer(plugin.uboGlobal); + cmd.UploadUniformBuffer(uboWorldViews); + + renderThread.submit(cmd); + // Reset buffers for the next frame eboAlphaStaging.clear(); @@ -781,6 +786,8 @@ private void postDrawTopLevel() { sceneFboValid = true; + renderThread.waitForRenderingCompleted(); + vaoA.unmap(); // Scene draw state to apply before all recorded commands @@ -990,6 +997,8 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (ctx == null) return; + renderThread.waitForRenderingCompleted(); + switch (pass) { case DrawCallbacks.PASS_OPAQUE: vaoO.addRange(scene); @@ -1055,6 +1064,8 @@ public void drawDynamic( if (ctx == null) return; + renderThread.waitForRenderingCompleted(); + int uuid = ModelHash.generateUuid(client, tileObject.getHash(), r); int[] worldPos = ctx.sceneContext.localToWorld(tileObject.getLocalLocation(), tileObject.getPlane()); ModelOverride modelOverride = modelOverrideManager.getOverride(uuid, worldPos); @@ -1107,6 +1118,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (ctx == null) return; + renderThread.waitForRenderingCompleted(); + Renderable renderable = gameObject.getRenderable(); int uuid = ModelHash.generateUuid(client, gameObject.getHash(), renderable); int[] worldPos = root.sceneContext.localToWorld(gameObject.getLocalLocation(), gameObject.getPlane()); From 383b2f83401509ea9f421a65d8475d41db5e53d9 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:45:01 +0100 Subject: [PATCH 39/50] Zone Draws dont need to wait on the RenderThread, currently only Dynamic/Temp Drawing does --- .../java/rs117/hd/renderer/zone/ZoneRenderer.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index dad0e5a624..7fddaca5bf 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -1064,8 +1064,6 @@ public void drawDynamic( if (ctx == null) return; - renderThread.waitForRenderingCompleted(); - int uuid = ModelHash.generateUuid(client, tileObject.getHash(), r); int[] worldPos = ctx.sceneContext.localToWorld(tileObject.getLocalLocation(), tileObject.getPlane()); ModelOverride modelOverride = modelOverrideManager.getOverride(uuid, worldPos); @@ -1089,6 +1087,8 @@ public void drawDynamic( } } + renderThread.waitForRenderingCompleted(); + int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; if (!hasAlpha) { VAO o = vaoO.get(size); @@ -1118,8 +1118,6 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (ctx == null) return; - renderThread.waitForRenderingCompleted(); - Renderable renderable = gameObject.getRenderable(); int uuid = ModelHash.generateUuid(client, gameObject.getHash(), renderable); int[] worldPos = root.sceneContext.localToWorld(gameObject.getLocalLocation(), gameObject.getPlane()); @@ -1127,6 +1125,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (modelOverride.hide) return; + renderThread.waitForRenderingCompleted(); + int preOrientation = HDUtils.getModelPreOrientation(gameObject.getConfig()); int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; @@ -1324,8 +1324,9 @@ public void draw(int overlayColor) { } public void onBackBufferSwapCompleted() { - frameTimer.endFrameAndReset(); isDrawingScene = false; + + frameTimer.endFrameAndReset(); } @Subscribe From 2d201fb72e6c3b8ec81d02f1343019b400cb407f Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 11:49:30 +0100 Subject: [PATCH 40/50] The Big Synchronisation update... RenderThread now maintains ownership of the AWTContext at all times --- src/main/java/rs117/hd/HdPlugin.java | 34 +---- .../opengl/commandbuffer/CommandBuffer.java | 9 +- .../commandbuffer/CommandBufferPool.java | 6 + .../hd/opengl/commandbuffer/FrameSync.java | 33 +++++ .../hd/opengl/commandbuffer/RenderThread.java | 90 +++++++----- .../commands/UploadPixelDataCommand.java | 27 ++-- .../java/rs117/hd/overlays/FrameTimer.java | 6 +- src/main/java/rs117/hd/renderer/zone/VAO.java | 22 +-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 138 ++++++++---------- 9 files changed, 190 insertions(+), 175 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index af1f440ed9..8b62f6f5ca 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -44,8 +44,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.LockSupport; import javax.annotation.Nullable; import javax.inject.Inject; import javax.swing.SwingUtilities; @@ -85,6 +83,7 @@ import rs117.hd.opengl.commandbuffer.AWTContextWrapper; import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.opengl.commandbuffer.CommandBufferPool; +import rs117.hd.opengl.commandbuffer.FrameSync; import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -331,9 +330,7 @@ public class HdPlugin extends Plugin { private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling private int texUi; private int pboUi; - private boolean uploadInFlight = false; - private final AtomicBoolean stageTrigger = new AtomicBoolean(false); - private final AtomicBoolean stageComplete = new AtomicBoolean(false); + private final FrameSync uiUploadSync = new FrameSync(); @Nullable public int[] sceneViewport; @@ -606,8 +603,6 @@ protected void startUp() { gammaCalibrationOverlay.initialize(); npcDisplacementCache.initialize(); - hooks.registerRenderableDrawListener(this::onDraw); - isActive = true; hasLoggedIn = client.getGameState().getState() > GameState.LOGGING_IN.getState(); redrawPreviousFrame = false; @@ -630,21 +625,6 @@ protected void startUp() { }); } - @SneakyThrows - private boolean onDraw(Renderable renderable, boolean drawingUI) { - if (uploadInFlight) { - frameTimer.begin(Timer.COPY_UI); - - stageTrigger.set(true); - while (!stageComplete.get()) { - LockSupport.parkNanos(100000); - } - frameTimer.end(Timer.COPY_UI); - uploadInFlight = false; - } - return true; - } - @Override protected void shutDown() { isActive = false; @@ -1369,13 +1349,11 @@ public void prepareInterfaceTexture() { final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); - uploadInFlight = true; - stageTrigger.set(false); - stageComplete.set(false); + uiUploadSync.markInFlight(); CommandBuffer cmd = commandBufferPool.acquire(); cmd.BeginTimer(Timer.UPLOAD_UI); - cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, stageTrigger, stageComplete); + cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, uiUploadSync); cmd.EndTimer(Timer.UPLOAD_UI); if(renderer instanceof ZoneRenderer) { renderThread.submit(cmd); @@ -1807,6 +1785,10 @@ public int getExpandedMapLoadingChunks() { public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); + frameTimer.begin(Timer.COPY_UI); + uiUploadSync.await(); + frameTimer.end(Timer.COPY_UI); + if (client.getScene() == null) return; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index bc5adbf283..0fd39aed9e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -2,7 +2,6 @@ import java.util.Arrays; import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; import net.runelite.rlawt.AWTContext; import org.lwjgl.system.MemoryStack; @@ -201,20 +200,18 @@ public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height UPLOAD_PIXEL_DATA_COMMAND.width = width; UPLOAD_PIXEL_DATA_COMMAND.height = height; UPLOAD_PIXEL_DATA_COMMAND.data = data; - UPLOAD_PIXEL_DATA_COMMAND.stageData = null; - UPLOAD_PIXEL_DATA_COMMAND.stageComplete = null; + UPLOAD_PIXEL_DATA_COMMAND.frameSync = null; UPLOAD_PIXEL_DATA_COMMAND.write(); } - public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data, AtomicBoolean stageData, AtomicBoolean stageComplete) { + public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data, FrameSync frameSync) { UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; UPLOAD_PIXEL_DATA_COMMAND.tex = tex; UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; UPLOAD_PIXEL_DATA_COMMAND.width = width; UPLOAD_PIXEL_DATA_COMMAND.height = height; UPLOAD_PIXEL_DATA_COMMAND.data = data; - UPLOAD_PIXEL_DATA_COMMAND.stageData = stageData; - UPLOAD_PIXEL_DATA_COMMAND.stageComplete = stageComplete; + UPLOAD_PIXEL_DATA_COMMAND.frameSync = frameSync; UPLOAD_PIXEL_DATA_COMMAND.write(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java index 770d6355a1..6e9a8f49eb 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java @@ -8,8 +8,14 @@ @Singleton public class CommandBufferPool { + public static CommandBufferPool INSTANCE; + private final ArrayDeque pool = new ArrayDeque<>(); + public CommandBufferPool() { + INSTANCE = this; + } + public CommandBuffer acquire() { if (pool.isEmpty()) { CommandBuffer buffer = new CommandBuffer(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java new file mode 100644 index 0000000000..9f05f03e9c --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java @@ -0,0 +1,33 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import lombok.Getter; +import lombok.SneakyThrows; + +public class FrameSync { + @Getter + private final Semaphore sema = new Semaphore(0); + + private final AtomicBoolean inFlight = new AtomicBoolean(false); + private final AtomicBoolean awaiting = new AtomicBoolean(false); + + public boolean isAwaiting() { return awaiting.get();} + + @SneakyThrows + public boolean await() { + if(inFlight.get() && !awaiting.get()) { + sema.acquire(); + awaiting.set(true); + inFlight.set(false); + return true; + } + return false; + } + + public void markInFlight() { + awaiting.set(false); + inFlight.set(true); + sema.drainPermits(); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index ff728f0eff..e40c129bd0 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Inject; @@ -13,6 +14,7 @@ import lombok.extern.slf4j.Slf4j; import org.lwjgl.system.MemoryStack; import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.commands.CallbackCommand; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @@ -30,6 +32,7 @@ public final class RenderThread implements Runnable { private final AtomicInteger pendingCount = new AtomicInteger(0); private final Object completionLock = new Object(); private final AtomicBoolean running = new AtomicBoolean(false); + private final Semaphore invokeOnRenderThreadSemaphore = new Semaphore(0); private Thread thread; @Inject @@ -57,18 +60,13 @@ public void initialize() { thread.start(); } - public void submit(CommandBuffer buffer) { - submit(buffer, null); - } + public void submit(CommandBuffer buffer) { submit(buffer, null); } public void submit(CommandBuffer buffer, boolean forceThread) { submit(buffer, null, forceThread); } - - public void submit(CommandBuffer buffer, Runnable onComplete) { - submit(buffer, onComplete, false); - } + public void submit(CommandBuffer buffer, Runnable onComplete) { submit(buffer, onComplete, false); } public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThread) { if (buffer == null) @@ -76,9 +74,8 @@ public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThrea if(!plugin.configRenderThread && !forceThread) { buffer.execute(); - if (onComplete != null) { - onComplete.run(); - } + if (onComplete != null) onComplete.run(); + if (buffer.pooled) pool.release(buffer); return; } @@ -99,17 +96,44 @@ public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThrea queue.add(newTask); } + private boolean isClientThread() {return Thread.currentThread() == clientThread; } + + public void processCompletedTasks() { + if(isClientThread()) { + synchronized (completedTasks) { + for (RenderTask task : completedTasks) { + if (task.callback != null) { + try { + task.callback.run(); + } catch (Throwable t) { + log.warn("Exception in render completion callback", t); + } + } + + if (task.buffer.pooled) { + pool.release(task.buffer); + } + + task.buffer = null; + task.callback = null; + + TASK_BIN.add(task); + } + completedTasks.clear(); + } + } + } + @SneakyThrows public void waitForRenderingCompleted() { if(pendingCount.get() == 0 && completedTasks.isEmpty() && contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) { return; } - boolean isClientThread = Thread.currentThread() == clientThread; synchronized (completionLock) { try { while (pendingCount.get() > 0) { - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread) + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) contextWrapper.detachCurrent("waiting for render completion"); timer.begin(Timer.RENDER_THREAD_COMPLETION); @@ -121,31 +145,10 @@ public void waitForRenderingCompleted() { return; } - if(isClientThread) { - contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); - - synchronized (completedTasks) { - for (RenderTask task : completedTasks) { - if (task.callback != null) { - try { - task.callback.run(); - } catch (Throwable t) { - log.warn("Exception in render completion callback", t); - } - } - - if (task.buffer.pooled) { - pool.release(task.buffer); - } - - task.buffer = null; - task.callback = null; + invokeOnRenderThread(() -> contextWrapper.detachCurrent("detached after render completion")); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); - TASK_BIN.add(task); - } - completedTasks.clear(); - } - } + processCompletedTasks(); } } @@ -189,6 +192,17 @@ public void run() { log.debug("RenderThread stopped!"); } + public void invokeOnRenderThread(CallbackCommand.ICallback callback) { + invokeOnRenderThreadSemaphore.drainPermits(); + CommandBuffer cmd = pool.acquire(); + cmd.Callback(callback); + cmd.Signal(invokeOnRenderThreadSemaphore); + submit(cmd); + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) + contextWrapper.detachCurrent("waiting for callback to be invoked on render thread"); + invokeOnRenderThreadSemaphore.acquireUninterruptibly(); + } + private void taskCompleted(RenderTask task) { synchronized (completedTasks) { completedTasks.add(task); @@ -196,10 +210,6 @@ private void taskCompleted(RenderTask task) { int remaining = pendingCount.decrementAndGet(); if (remaining <= 0) { - // Release ownership now that there is no more work pending - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.RENDER_THREAD) - contextWrapper.detachCurrent("released by render thread"); - pendingCount.set(0); synchronized (completionLock) { completionLock.notifyAll(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index 9d39e20048..ca594a4e8d 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -2,10 +2,10 @@ import java.nio.ByteBuffer; import java.nio.IntBuffer; -import java.util.concurrent.atomic.AtomicBoolean; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.commandbuffer.FrameSync; import static org.lwjgl.opengl.GL11C.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL11C.glBindTexture; @@ -26,8 +26,7 @@ public class UploadPixelDataCommand extends BaseCommand { public int width; public int height; public int[] data; - public AtomicBoolean stageData; - public AtomicBoolean stageComplete; + public FrameSync frameSync; private IntBuffer stagingData = null; private ByteBuffer mappedBuffer = null; @@ -40,10 +39,9 @@ protected void doWrite() { write32(width); write32(height); writeObject(data); - if(stageData != null && stageComplete != null) { + if(frameSync != null) { writeFlag(true); - writeObject(stageData); - writeObject(stageComplete); + writeObject(frameSync); } else { writeFlag(false); } @@ -59,11 +57,9 @@ protected void doRead(MemoryStack stack) { height = read32(); data = readObject(); if(readFlag()) { - stageData = readObject(); - stageComplete = readObject(); + frameSync = readObject(); } else { - stageData = null; - stageComplete = null; + frameSync = null; } } @@ -76,8 +72,7 @@ protected void execute(MemoryStack stack) { if (mappedBuffer != null) { IntBuffer mappedIntBuffer = mappedBuffer.asIntBuffer(); - boolean stagingSupport = stageData != null && stageComplete != null; - if(stagingSupport && (stagingData == null || stagingData.capacity() < data.length)) { + if(frameSync != null && (stagingData == null || stagingData.capacity() < data.length)) { stagingData = MemoryUtil.memAllocInt(data.length); } @@ -86,9 +81,9 @@ protected void execute(MemoryStack stack) { int chunkSize = remaining / chunks; int offset = 0; for(int i = 0; i < chunks; i++) { - if (stagingSupport && stageData.get()) { + if (frameSync != null && frameSync.isAwaiting()) { stagingData.put(data, offset, remaining); - stageComplete.set(true); + frameSync.getSema().release(); stagingData.flip(); mappedIntBuffer.put(stagingData); @@ -101,8 +96,8 @@ protected void execute(MemoryStack stack) { } } - if (stagingSupport) { - stageComplete.set(true); + if (frameSync != null) { + frameSync.getSema().release(); } glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index e129f5d9e2..37b6aa10c8 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -49,9 +49,7 @@ public FrameTimer() { } private void initialize() { - clientThread.invoke(() -> { - renderThread.waitForRenderingCompleted(); - + renderThread.invokeOnRenderThread(() -> { int[] queryNames = new int[NUM_GPU_TIMERS * 2]; glGenQueries(queryNames); int queryIndex = 0; @@ -81,7 +79,7 @@ private void initialize() { } private void destroy() { - clientThread.invoke(() -> { + renderThread.invokeOnRenderThread(() -> { if (!isActive) return; diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 089800989e..9cfbd29e59 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -4,9 +4,11 @@ import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.RenderThread; import static org.lwjgl.opengl.GL33C.*; @@ -127,14 +129,14 @@ static class VAOList { private final List vaos = new ArrayList<>(); private final int eboAlpha; - VAO get(int size) { + @SneakyThrows + VAO get(RenderThread renderThread, int size) { assert size <= VAO_SIZE; while (curIdx < vaos.size()) { VAO vao = vaos.get(curIdx); - if (!vao.vbo.mapped) { - vao.vbo.map(); - } + if (!vao.vbo.mapped) + renderThread.invokeOnRenderThread(vao.vbo::map); int rem = vao.vbo.vb.remaining() * Integer.BYTES; if (size <= rem) { @@ -144,11 +146,13 @@ VAO get(int size) { curIdx++; } - VAO vao = new VAO(VAO_SIZE); - vao.initialize(eboAlpha); - vao.vbo.map(); - vaos.add(vao); - log.debug("Allocated VAO {} request {}", vao.vao, size); + final VAO vao = new VAO(VAO_SIZE); + renderThread.invokeOnRenderThread(() -> { + vao.initialize(eboAlpha); + vao.vbo.map(); + vaos.add(vao); + log.debug("Allocated VAO {} request {}", vao.vao, size); + }); return vao; } diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 7fddaca5bf..acc508e95b 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -50,6 +50,7 @@ import rs117.hd.opengl.commandbuffer.AWTContextWrapper; import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.opengl.commandbuffer.CommandBufferPool; +import rs117.hd.opengl.commandbuffer.FrameSync; import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; @@ -162,11 +163,12 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; - private boolean isDrawingScene; private final CommandBuffer zoneDrawCallBuffer = new CommandBuffer(); private final CommandBuffer zoneShadowDrawCallBuffer = new CommandBuffer(); + private final FrameSync renderingSync = new FrameSync(); + private VAO.VAOList vaoO; private VAO.VAOList vaoA; private VAO.VAOList vaoPO; @@ -354,6 +356,10 @@ public void preSceneDraw( this.maxLevel = maxLevel; this.hideRoofIds = hideRoofIds; + if(renderingSync.await()) { + renderThread.processCompletedTasks(); + } + if (scene.getWorldViewId() == WorldView.TOPLEVEL) { preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); } else { @@ -370,8 +376,6 @@ private void preSceneDrawTopLevel( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw ) { - isDrawingScene = true; - scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); @@ -786,18 +790,19 @@ private void postDrawTopLevel() { sceneFboValid = true; - renderThread.waitForRenderingCompleted(); + renderThread.invokeOnRenderThread(vaoA::unmap); - vaoA.unmap(); + CommandBuffer cmd = commandBufferPool.acquire(); // Scene draw state to apply before all recorded commands if (eboAlphaStaging.position() > 0) { - eboAlphaStaging.flip(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboAlpha); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); + cmd.Callback(() -> { + eboAlphaStaging.flip(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboAlpha); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); + }); } - CommandBuffer cmd = commandBufferPool.acquire(); if (plugin.configShadowsEnabled && plugin.fboShadowMap != 0 && @@ -997,8 +1002,6 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (ctx == null) return; - renderThread.waitForRenderingCompleted(); - switch (pass) { case DrawCallbacks.PASS_OPAQUE: vaoO.addRange(scene); @@ -1010,15 +1013,15 @@ public void drawPass(Projection projection, Scene scene, int pass) { zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); // Draw opaque - vaoO.unmap(); + renderThread.invokeOnRenderThread(vaoO::unmap); vaoO.drawAll(this, zoneDrawCallBuffer); vaoO.drawAll(this, zoneShadowDrawCallBuffer); vaoO.resetAll(); - vaoPO.unmap(); + renderThread.invokeOnRenderThread(vaoPO::unmap); // Draw player shadows - vaoPOShadow.unmap(); + renderThread.invokeOnRenderThread(vaoPOShadow::unmap); vaoPOShadow.drawAll(this, zoneShadowDrawCallBuffer); vaoPOShadow.resetAll(); @@ -1087,15 +1090,13 @@ public void drawDynamic( } } - renderThread.waitForRenderingCompleted(); - int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; if (!hasAlpha) { - VAO o = vaoO.get(size); + VAO o = vaoO.get(renderThread, size); sceneUploader.uploadTempModel(m, modelOverride, preOrientation, orient, x, y, z, o.vbo.vb); } else { m.calculateBoundsCylinder(); - VAO o = vaoO.get(size), a = vaoA.get(size); + VAO o = vaoO.get(renderThread, size), a = vaoA.get(renderThread, size); int start = a.vbo.vb.position(); facePrioritySorter.uploadSortedModel(worldProjection, m, modelOverride, preOrientation, orient, x, y, z, o.vbo.vb, a.vbo.vb); int end = a.vbo.vb.position(); @@ -1125,8 +1126,6 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (modelOverride.hide) return; - renderThread.waitForRenderingCompleted(); - int preOrientation = HDUtils.getModelPreOrientation(gameObject.getConfig()); int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; @@ -1140,8 +1139,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj // opaque player faces have their own vao and are drawn in a separate pass from normal opaque faces // because they are not depth tested. transparent player faces don't need their own vao because normal // transparent faces are already not depth tested - VAO o = renderable instanceof Player ? vaoPO.get(size) : vaoO.get(size); - VAO a = vaoA.get(size); + VAO o = renderable instanceof Player ? vaoPO.get(renderThread, size) : vaoO.get(renderThread, size); + VAO a = vaoA.get(renderThread, size); int start = a.vbo.vb.position(); m.calculateBoundsCylinder(); @@ -1179,7 +1178,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (zone.inShadowFrustum) { // Since priority sorting of models includes back-face culling, // we need to upload the entire model again for shadows - VAO o = vaoPOShadow.get(size); + VAO o = vaoPOShadow.get(renderThread, size); sceneUploader.uploadTempModel( m, modelOverride, @@ -1192,7 +1191,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj ); } } else { - VAO o = vaoO.get(size); + VAO o = vaoO.get(renderThread, size); sceneUploader.uploadTempModel( m, modelOverride, @@ -1233,47 +1232,47 @@ private void rebuild(WorldView wv) { if (ctx == null) return; - for (int x = 0; x < ctx.sizeX; ++x) { - for (int z = 0; z < ctx.sizeZ; ++z) { - Zone zone = ctx.zones[x][z]; - if (!zone.invalidate) - continue; - - renderThread.waitForRenderingCompleted(); + renderThread.invokeOnRenderThread(() -> { + for (int x = 0; x < ctx.sizeX; ++x) { + for (int z = 0; z < ctx.sizeZ; ++z) { + Zone zone = ctx.zones[x][z]; + if (!zone.invalidate) + continue; - assert zone.initialized; - zone.free(); - zone = ctx.zones[x][z] = new Zone(); + assert zone.initialized; + zone.free(); + zone = ctx.zones[x][z] = new Zone(); - SceneUploader sceneUploader = injector.getInstance(SceneUploader.class); - sceneUploader.zoneSize(ctx.sceneContext, zone, x, z); + SceneUploader sceneUploader = injector.getInstance(SceneUploader.class); + sceneUploader.zoneSize(ctx.sceneContext, zone, x, z); - VBO o = null, a = null; - int sz = zone.sizeO * Zone.VERT_SIZE * 3; - if (sz > 0) { - o = new VBO(sz); - o.initialize(GL_STATIC_DRAW); - o.map(); - } + VBO o = null, a = null; + int sz = zone.sizeO * Zone.VERT_SIZE * 3; + if (sz > 0) { + o = new VBO(sz); + o.initialize(GL_STATIC_DRAW); + o.map(); + } - sz = zone.sizeA * Zone.VERT_SIZE * 3; - if (sz > 0) { - a = new VBO(sz); - a.initialize(GL_STATIC_DRAW); - a.map(); - } + sz = zone.sizeA * Zone.VERT_SIZE * 3; + if (sz > 0) { + a = new VBO(sz); + a.initialize(GL_STATIC_DRAW); + a.map(); + } - zone.initialize(o, a, eboAlpha); + zone.initialize(o, a, eboAlpha); - sceneUploader.uploadZone(ctx.sceneContext, zone, x, z); + sceneUploader.uploadZone(ctx.sceneContext, zone, x, z); - zone.unmap(); - zone.initialized = true; - zone.dirty = true; + zone.unmap(); + zone.initialized = true; + zone.dirty = true; - log.trace("Rebuilt zone wv={} x={} z={}", wv.getId(), x, z); + log.trace("Rebuilt zone wv={} x={} z={}", wv.getId(), x, z); + } } - } + }); } @Override @@ -1285,8 +1284,8 @@ public void draw(int overlayColor) { return; } - if(!isDrawingScene) { - renderThread.waitForRenderingCompleted(); + if(renderingSync.await()) { + renderThread.processCompletedTasks(); } try { @@ -1318,15 +1317,13 @@ public void draw(int overlayColor) { cmd.SwapBuffers(awtContextWrapper.getContext()); cmd.EndTimer(Timer.SWAP_BUFFERS); cmd.EndTimer(Timer.RENDER_FRAME); + cmd.Callback(frameTimer::endFrameAndReset); + cmd.Signal(renderingSync.getSema()); - frameTimer.end(Timer.DRAW_FRAME); - renderThread.submit(cmd, this::onBackBufferSwapCompleted); - } - - public void onBackBufferSwapCompleted() { - isDrawingScene = false; + renderingSync.markInFlight(); - frameTimer.endFrameAndReset(); + frameTimer.end(Timer.DRAW_FRAME); + renderThread.submit(cmd); } @Subscribe @@ -1515,8 +1512,7 @@ public void loadScene(WorldView worldView, Scene scene) { ); // allocate buffers for zones which require upload - CommandBuffer cmd = commandBufferPool.acquire(); - cmd.Callback(() -> { + renderThread.invokeOnRenderThread(() -> { for (int x = 0; x < EXTENDED_SCENE_SIZE >> 3; ++x) { for (int z = 0; z < EXTENDED_SCENE_SIZE >> 3; ++z) { Zone zone = newZones[x][z]; @@ -1544,8 +1540,6 @@ public void loadScene(WorldView worldView, Scene scene) { } } }); - renderThread.submit(cmd, true); - renderThread.waitForRenderingCompleted(); // upload zones sw = Stopwatch.createStarted(); @@ -1649,9 +1643,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { sceneUploader.zoneSize(sceneContext, ctx.zones[x][z], x, z); // allocate buffers for zones which require upload - CommandBuffer cmd = commandBufferPool.acquire(); - cmd.Callback(() -> - { + renderThread.invokeOnRenderThread(() -> { for (int x = 0; x < ctx.sizeX; ++x) { for (int z = 0; z < ctx.sizeZ; ++z) { Zone zone = ctx.zones[x][z]; @@ -1675,8 +1667,6 @@ private void loadSubScene(WorldView worldView, Scene scene) { } } }); - renderThread.submit(cmd, true); - renderThread.waitForRenderingCompleted(); for (int x = 0; x < ctx.sizeX; ++x) for (int z = 0; z < ctx.sizeZ; ++z) From 638de1540c233e795b3dcf74fb7c9df16ed50d58 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 12:07:29 +0100 Subject: [PATCH 41/50] Optimizations --- .../opengl/commandbuffer/CommandBuffer.java | 15 ++- .../hd/opengl/commandbuffer/RenderThread.java | 12 ++- .../rs117/hd/renderer/zone/ZoneRenderer.java | 92 +++++++++++-------- 3 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index 0fd39aed9e..e3d72a03c9 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -116,6 +116,7 @@ private T REGISTER_COMMAND(ICreateCommand createComma private int readHead = 0; private int readBitHead = 0; + protected boolean highPriority; protected boolean pooled; private Timer executionTimer = null; @@ -548,6 +549,8 @@ protected void writeBits(long value, int numBits) { } public void reset() { + highPriority = false; + writeHead = 0; writeBitHead = 0; @@ -555,10 +558,14 @@ public void reset() { readBitHead = 0; // Objects need to be cleared to avoid holding onto a reference and preventing garbage collection - Arrays.fill(objects, 0, objectCount, null); - objectCount = 0; + if(objectCount > 0 ) { + Arrays.fill(objects, 0, objectCount, null); + objectCount = 0; + } - Arrays.fill(pendingUBOUploads, 0, pendingUBOUploadsCount, null); - pendingUBOUploadsCount = 0; + if(pendingUBOUploadsCount > 0) { + Arrays.fill(pendingUBOUploads, 0, pendingUBOUploadsCount, null); + pendingUBOUploadsCount = 0; + } } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index e40c129bd0..aa568321ef 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -3,8 +3,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -26,7 +25,7 @@ public final class RenderThread implements Runnable { private static final RenderTask POISON_PILL = new RenderTask(); private static final ArrayDeque TASK_BIN = new ArrayDeque<>(); - private final BlockingQueue queue = new LinkedBlockingQueue<>(); + private final LinkedBlockingDeque queue = new LinkedBlockingDeque<>(); private final List completedTasks = new ArrayList<>(); private final AtomicInteger pendingCount = new AtomicInteger(0); @@ -93,7 +92,11 @@ public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThrea newTask.buffer = buffer; newTask.callback = onComplete; - queue.add(newTask); + if(buffer.highPriority) { + queue.addFirst(newTask); + } else { + queue.addLast(newTask); + } } private boolean isClientThread() {return Thread.currentThread() == clientThread; } @@ -195,6 +198,7 @@ public void run() { public void invokeOnRenderThread(CallbackCommand.ICallback callback) { invokeOnRenderThreadSemaphore.drainPermits(); CommandBuffer cmd = pool.acquire(); + cmd.highPriority = true; cmd.Callback(callback); cmd.Signal(invokeOnRenderThreadSemaphore); submit(cmd); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index acc508e95b..717df82ad1 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -169,10 +169,29 @@ public class ZoneRenderer implements Renderer { private final FrameSync renderingSync = new FrameSync(); - private VAO.VAOList vaoO; - private VAO.VAOList vaoA; - private VAO.VAOList vaoPO; - private VAO.VAOList vaoPOShadow; + // TODO: Can we rename these to something more human readable? + class SceneVAOs { + public VAO.VAOList vaoO; + public VAO.VAOList vaoA; + public VAO.VAOList vaoPO; + public VAO.VAOList vaoPOShadow; + + public void init(int eboAlpha) { + vaoO = new VAO.VAOList(eboAlpha); + vaoA = new VAO.VAOList(eboAlpha); + vaoPO = new VAO.VAOList(eboAlpha); + vaoPOShadow = new VAO.VAOList(eboAlpha); + } + + public void free() { + vaoO.free(); + vaoA.free(); + vaoPO.free(); + vaoPOShadow.free(); + vaoO = vaoA = vaoPO = vaoPOShadow = null; + } + } + private SceneVAOs vaos = new SceneVAOs(), vaos_prev = new SceneVAOs(); public static int eboAlpha; public static GpuIntBuffer eboAlphaStaging; @@ -309,18 +328,13 @@ private void initializeBuffers() { eboAlpha = glGenBuffers(); eboAlphaStaging = new GpuIntBuffer(); - vaoO = new VAO.VAOList(eboAlpha); - vaoA = new VAO.VAOList(eboAlpha); - vaoPO = new VAO.VAOList(eboAlpha); - vaoPOShadow = new VAO.VAOList(eboAlpha); + vaos.init(eboAlpha); + vaos_prev.init(eboAlpha); } private void destroyBuffers() { - vaoO.free(); - vaoA.free(); - vaoPO.free(); - vaoPOShadow.free(); - vaoO = vaoA = vaoPO = vaoPOShadow = null; + vaos.free(); + vaos_prev.free(); if (eboAlpha != 0) glDeleteBuffers(eboAlpha); @@ -358,15 +372,19 @@ public void preSceneDraw( if(renderingSync.await()) { renderThread.processCompletedTasks(); + + SceneVAOs temp = vaos; + vaos = vaos_prev; + vaos_prev = temp; } if (scene.getWorldViewId() == WorldView.TOPLEVEL) { preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); } else { Scene topLevel = client.getScene(); - vaoO.addRange(topLevel); - vaoPO.addRange(topLevel); - vaoPOShadow.addRange(topLevel); + vaos.vaoO.addRange(topLevel); + vaos.vaoPO.addRange(topLevel); + vaos.vaoPOShadow.addRange(topLevel); zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); } @@ -790,7 +808,7 @@ private void postDrawTopLevel() { sceneFboValid = true; - renderThread.invokeOnRenderThread(vaoA::unmap); + renderThread.invokeOnRenderThread(vaos.vaoA::unmap); CommandBuffer cmd = commandBufferPool.acquire(); @@ -1004,38 +1022,38 @@ public void drawPass(Projection projection, Scene scene, int pass) { switch (pass) { case DrawCallbacks.PASS_OPAQUE: - vaoO.addRange(scene); - vaoPO.addRange(scene); - vaoPOShadow.addRange(scene); + vaos.vaoO.addRange(scene); + vaos.vaoPO.addRange(scene); + vaos.vaoPOShadow.addRange(scene); if (scene.getWorldViewId() == -1) { zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); // Draw opaque - renderThread.invokeOnRenderThread(vaoO::unmap); - vaoO.drawAll(this, zoneDrawCallBuffer); - vaoO.drawAll(this, zoneShadowDrawCallBuffer); - vaoO.resetAll(); + renderThread.invokeOnRenderThread(vaos.vaoO::unmap); + vaos.vaoO.drawAll(this, zoneDrawCallBuffer); + vaos.vaoO.drawAll(this, zoneShadowDrawCallBuffer); + vaos.vaoO.resetAll(); - renderThread.invokeOnRenderThread(vaoPO::unmap); + renderThread.invokeOnRenderThread(vaos.vaoPO::unmap); // Draw player shadows - renderThread.invokeOnRenderThread(vaoPOShadow::unmap); - vaoPOShadow.drawAll(this, zoneShadowDrawCallBuffer); - vaoPOShadow.resetAll(); + renderThread.invokeOnRenderThread(vaos.vaoPOShadow::unmap); + vaos.vaoPOShadow.drawAll(this, zoneShadowDrawCallBuffer); + vaos.vaoPOShadow.resetAll(); // Draw players opaque, without depth writes zoneDrawCallBuffer.DepthMask(false); - vaoPO.drawAll(this, zoneDrawCallBuffer); + vaos.vaoPO.drawAll(this, zoneDrawCallBuffer); zoneDrawCallBuffer.DepthMask(true); // Draw players opaque, writing only depth zoneDrawCallBuffer.ColorMask(false, false, false, false); - vaoPO.drawAll(this, zoneDrawCallBuffer); + vaos.vaoPO.drawAll(this, zoneDrawCallBuffer); zoneDrawCallBuffer.ColorMask(true, true, true, true); - vaoPO.resetAll(); + vaos.vaoPO.resetAll(); } break; case DrawCallbacks.PASS_ALPHA: @@ -1092,11 +1110,11 @@ public void drawDynamic( int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; if (!hasAlpha) { - VAO o = vaoO.get(renderThread, size); + VAO o = vaos.vaoO.get(renderThread, size); sceneUploader.uploadTempModel(m, modelOverride, preOrientation, orient, x, y, z, o.vbo.vb); } else { m.calculateBoundsCylinder(); - VAO o = vaoO.get(renderThread, size), a = vaoA.get(renderThread, size); + VAO o = vaos.vaoO.get(renderThread, size), a = vaos.vaoA.get(renderThread, size); int start = a.vbo.vb.position(); facePrioritySorter.uploadSortedModel(worldProjection, m, modelOverride, preOrientation, orient, x, y, z, o.vbo.vb, a.vbo.vb); int end = a.vbo.vb.position(); @@ -1139,8 +1157,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj // opaque player faces have their own vao and are drawn in a separate pass from normal opaque faces // because they are not depth tested. transparent player faces don't need their own vao because normal // transparent faces are already not depth tested - VAO o = renderable instanceof Player ? vaoPO.get(renderThread, size) : vaoO.get(renderThread, size); - VAO a = vaoA.get(renderThread, size); + VAO o = renderable instanceof Player ? vaos.vaoPO.get(renderThread, size) : vaos.vaoO.get(renderThread, size); + VAO a = vaos.vaoA.get(renderThread, size); int start = a.vbo.vb.position(); m.calculateBoundsCylinder(); @@ -1178,7 +1196,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (zone.inShadowFrustum) { // Since priority sorting of models includes back-face culling, // we need to upload the entire model again for shadows - VAO o = vaoPOShadow.get(renderThread, size); + VAO o = vaos.vaoPOShadow.get(renderThread, size); sceneUploader.uploadTempModel( m, modelOverride, @@ -1191,7 +1209,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj ); } } else { - VAO o = vaoO.get(renderThread, size); + VAO o = vaos.vaoO.get(renderThread, size); sceneUploader.uploadTempModel( m, modelOverride, From 2e54905b11ef6f213383a6f1587c9e6672f64d96 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 12:31:51 +0100 Subject: [PATCH 42/50] Tweaks and improvements --- .../hd/opengl/commandbuffer/RenderThread.java | 28 +++++++----- .../java/rs117/hd/overlays/FrameTimer.java | 2 +- src/main/java/rs117/hd/overlays/Timer.java | 5 +-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 44 ++++++++++++------- 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index aa568321ef..d7f5b59049 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -11,11 +11,11 @@ import javax.inject.Singleton; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import net.runelite.client.callback.ClientThread; import org.lwjgl.system.MemoryStack; import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.CallbackCommand; import rs117.hd.overlays.FrameTimer; -import rs117.hd.overlays.Timer; @Slf4j @Singleton @@ -33,6 +33,7 @@ public final class RenderThread implements Runnable { private final AtomicBoolean running = new AtomicBoolean(false); private final Semaphore invokeOnRenderThreadSemaphore = new Semaphore(0); private Thread thread; + private Thread clientJavaThread; @Inject private HdPlugin plugin; @@ -40,7 +41,8 @@ public final class RenderThread implements Runnable { @Inject private FrameTimer timer; - private Thread clientThread; + @Inject + private ClientThread clientThread; @Inject private AWTContextWrapper contextWrapper; @@ -49,7 +51,7 @@ public final class RenderThread implements Runnable { private CommandBufferPool pool; public void initialize() { - clientThread = Thread.currentThread(); + clientJavaThread = Thread.currentThread(); running.set(true); @@ -72,15 +74,23 @@ public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThrea throw new IllegalArgumentException("CommandBuffer cannot be null"); if(!plugin.configRenderThread && !forceThread) { - buffer.execute(); - if (onComplete != null) onComplete.run(); - if (buffer.pooled) pool.release(buffer); + if(isClientThread()) { + buffer.execute(); + if (onComplete != null) onComplete.run(); + if (buffer.pooled) pool.release(buffer); + } else { + clientThread.invoke(() -> { + buffer.execute(); + if (onComplete != null) onComplete.run(); + if (buffer.pooled) pool.release(buffer); + }); + } return; } pendingCount.incrementAndGet(); - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && Thread.currentThread() == clientThread) { + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) { contextWrapper.detachCurrent("detached for render submit"); } @@ -99,7 +109,7 @@ public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThrea } } - private boolean isClientThread() {return Thread.currentThread() == clientThread; } + private boolean isClientThread() {return Thread.currentThread() == clientJavaThread; } public void processCompletedTasks() { if(isClientThread()) { @@ -139,9 +149,7 @@ public void waitForRenderingCompleted() { if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) contextWrapper.detachCurrent("waiting for render completion"); - timer.begin(Timer.RENDER_THREAD_COMPLETION); completionLock.wait(); - timer.end(Timer.RENDER_THREAD_COMPLETION); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index 37b6aa10c8..e8324b79c2 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -204,7 +204,7 @@ public void endFrameAndReset() { } else { if (activeTimers[i]) { // End the CPU timer automatically, but warn about it - log.warn("Timer {} was never ended", timer); + //log.warn("Timer {} was never ended", timer); timings[i] += frameEndNanos; } } diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 35e80fb6ce..79302a7f5a 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,11 +17,8 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), - RENDER_THREAD_COMPLETION, + FRAME_SYNC, COMMAND_BUFFER_READ, - SHADOW_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Shadow"), - SCENE_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Scene"), - BACKBUFFER_COMMAND_BUFFER_EXECUTE(false, "Command Buffer Main"), UPDATE_ENVIRONMENT, UPDATE_LIGHTS, IMPOSTOR_TRACKING, diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 717df82ad1..2079ec326b 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -370,14 +370,6 @@ public void preSceneDraw( this.maxLevel = maxLevel; this.hideRoofIds = hideRoofIds; - if(renderingSync.await()) { - renderThread.processCompletedTasks(); - - SceneVAOs temp = vaos; - vaos = vaos_prev; - vaos_prev = temp; - } - if (scene.getWorldViewId() == WorldView.TOPLEVEL) { preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); } else { @@ -1250,6 +1242,22 @@ private void rebuild(WorldView wv) { if (ctx == null) return; + + // Check if any zones need to be invalidated before invoking on the RenderThread + boolean needsRebuild = false; + for (int x = 0; x < ctx.sizeX; ++x) { + for (int z = 0; z < ctx.sizeZ; ++z) { + Zone zone = ctx.zones[x][z]; + if (zone.invalidate) { + needsRebuild = true; + break; + } + } + } + + if(!needsRebuild) + return; + renderThread.invokeOnRenderThread(() -> { for (int x = 0; x < ctx.sizeX; ++x) { for (int z = 0; z < ctx.sizeZ; ++z) { @@ -1296,14 +1304,18 @@ private void rebuild(WorldView wv) { @Override public void draw(int overlayColor) { log.trace("draw(overlaySrgba={})", overlayColor); - final GameState gameState = client.getGameState(); - if (gameState == GameState.STARTING) { - frameTimer.end(Timer.DRAW_FRAME); - return; - } + frameTimer.end(Timer.DRAW_FRAME); + + if(!renderingSync.isAwaiting()) { + long time = System.nanoTime(); + if (renderingSync.await()) { + renderThread.processCompletedTasks(); - if(renderingSync.await()) { - renderThread.processCompletedTasks(); + SceneVAOs temp = vaos; + vaos = vaos_prev; + vaos_prev = temp; + } + frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); } try { @@ -1339,8 +1351,6 @@ public void draw(int overlayColor) { cmd.Signal(renderingSync.getSema()); renderingSync.markInFlight(); - - frameTimer.end(Timer.DRAW_FRAME); renderThread.submit(cmd); } From fad6de1b5c92befebab8b6f65a250b8f200e3628 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 12:58:37 +0100 Subject: [PATCH 43/50] Removed Redundant CheckGLErrors --- src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 2079ec326b..e6d48b376d 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -878,8 +878,6 @@ private void postDrawTopLevel() { renderThread.submit(cmd); frameTimer.end(Timer.DRAW_SCENE); - - checkGLErrors(); } @Override @@ -935,8 +933,6 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int plugin.configRoofShadows ? Collections.emptySet() : hideRoofIds ); } - - checkGLErrors(); } @Override @@ -1001,8 +997,6 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i plugin.configRoofShadows ? Collections.emptySet() : hideRoofIds ); } - - checkGLErrors(); } @Override @@ -1054,7 +1048,6 @@ public void drawPass(Projection projection, Scene scene, int pass) { ctx.zones[x][z].removeTemp(); break; } - checkGLErrors(); } @Override From b525327e4182d8a868084aeee910127950e87c35 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 13:21:43 +0100 Subject: [PATCH 44/50] FrameSync should spinlock to avoid making the RenderThread unpark the waiter --- src/main/java/rs117/hd/HdPlugin.java | 21 +++++++++++-------- .../hd/opengl/commandbuffer/FrameSync.java | 4 +++- .../hd/opengl/commandbuffer/RenderThread.java | 10 ++------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 8b62f6f5ca..6a1696e846 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -100,7 +100,6 @@ import rs117.hd.overlays.TiledLightingOverlay; import rs117.hd.overlays.Timer; import rs117.hd.renderer.Renderer; -import rs117.hd.renderer.zone.ZoneRenderer; import rs117.hd.scene.AreaManager; import rs117.hd.scene.EnvironmentManager; import rs117.hd.scene.FishingSpotReplacer; @@ -1355,12 +1354,7 @@ public void prepareInterfaceTexture() { cmd.BeginTimer(Timer.UPLOAD_UI); cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, uiUploadSync); cmd.EndTimer(Timer.UPLOAD_UI); - if(renderer instanceof ZoneRenderer) { - renderThread.submit(cmd); - } else { - cmd.execute(); - commandBufferPool.release(cmd); - } + renderThread.submit(cmd); } public void drawUi(int overlayColor, CommandBuffer cmd) { @@ -1486,6 +1480,15 @@ private void updateCachedConfigs() { configSeasonalTheme = config.seasonalTheme(); configSeasonalHemisphere = config.seasonalHemisphere(); + var newRenderThread = config.renderThread(); + if(newRenderThread != configRenderThread) { + if(configRenderThread) { + renderThread.waitForRenderingCompleted(); + } + configRenderThread = newRenderThread; + restartPlugin(); + } + var newColorFilter = config.colorFilter(); if (newColorFilter != configColorFilter) { configColorFilterPrevious = configColorFilter; @@ -1785,9 +1788,9 @@ public int getExpandedMapLoadingChunks() { public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); - frameTimer.begin(Timer.COPY_UI); + long start = System.nanoTime(); uiUploadSync.await(); - frameTimer.end(Timer.COPY_UI); + frameTimer.add(Timer.COPY_UI, System.nanoTime() - start); if (client.getScene() == null) return; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java index 9f05f03e9c..e89d168e5f 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java @@ -17,8 +17,10 @@ public class FrameSync { @SneakyThrows public boolean await() { if(inFlight.get() && !awaiting.get()) { - sema.acquire(); awaiting.set(true); + while (!sema.tryAcquire()) { + Thread.sleep(1); + } inFlight.set(false); return true; } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index d7f5b59049..98d284e463 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -63,17 +63,11 @@ public void initialize() { public void submit(CommandBuffer buffer) { submit(buffer, null); } - public void submit(CommandBuffer buffer, boolean forceThread) { - submit(buffer, null, forceThread); - } - - public void submit(CommandBuffer buffer, Runnable onComplete) { submit(buffer, onComplete, false); } - - public void submit(CommandBuffer buffer, Runnable onComplete, boolean forceThread) { + public void submit(CommandBuffer buffer, Runnable onComplete) { if (buffer == null) throw new IllegalArgumentException("CommandBuffer cannot be null"); - if(!plugin.configRenderThread && !forceThread) { + if(!plugin.configRenderThread) { if(isClientThread()) { buffer.execute(); if (onComplete != null) onComplete.run(); From d3a1f8b665337d5efb6a0c7dc8437d811db9a2d6 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:02:06 +0100 Subject: [PATCH 45/50] Tweaks & Fixes --- src/main/java/rs117/hd/HdPlugin.java | 9 ----- .../opengl/commandbuffer/CommandBuffer.java | 5 +-- .../hd/opengl/commandbuffer/FrameSync.java | 37 ++++++++++--------- .../hd/opengl/commandbuffer/RenderThread.java | 11 ++---- .../commandbuffer/commands/SignalCommand.java | 10 ++--- .../commands/UploadPixelDataCommand.java | 4 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 2 +- 7 files changed, 33 insertions(+), 45 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 6a1696e846..15dc88f0dc 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -1480,15 +1480,6 @@ private void updateCachedConfigs() { configSeasonalTheme = config.seasonalTheme(); configSeasonalHemisphere = config.seasonalHemisphere(); - var newRenderThread = config.renderThread(); - if(newRenderThread != configRenderThread) { - if(configRenderThread) { - renderThread.waitForRenderingCompleted(); - } - configRenderThread = newRenderThread; - restartPlugin(); - } - var newColorFilter = config.colorFilter(); if (newColorFilter != configColorFilter) { configColorFilterPrevious = configColorFilter; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java index e3d72a03c9..5d1b4d4fd6 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -1,7 +1,6 @@ package rs117.hd.opengl.commandbuffer; import java.util.Arrays; -import java.util.concurrent.Semaphore; import lombok.extern.slf4j.Slf4j; import net.runelite.rlawt.AWTContext; import org.lwjgl.system.MemoryStack; @@ -330,8 +329,8 @@ public void Callback(CallbackCommand.ICallback callback) { CALLBACK_COMMAND.write(); } - public void Signal(Semaphore semaphore) { - SIGNAL_COMMAND.semaphore = semaphore; + public void Signal(FrameSync frameSync) { + SIGNAL_COMMAND.frameSync = frameSync; SIGNAL_COMMAND.write(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java index e89d168e5f..5a7e921bdb 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java @@ -1,35 +1,36 @@ package rs117.hd.opengl.commandbuffer; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; import lombok.Getter; -import lombok.SneakyThrows; public class FrameSync { @Getter - private final Semaphore sema = new Semaphore(0); + private volatile boolean awaiting = false; + private volatile boolean ready = false; + private volatile boolean inFlight = false; - private final AtomicBoolean inFlight = new AtomicBoolean(false); - private final AtomicBoolean awaiting = new AtomicBoolean(false); - - public boolean isAwaiting() { return awaiting.get();} - - @SneakyThrows public boolean await() { - if(inFlight.get() && !awaiting.get()) { - awaiting.set(true); - while (!sema.tryAcquire()) { - Thread.sleep(1); + if (inFlight && !awaiting) { + awaiting = true; + + while (!ready) { + LockSupport.parkNanos(1); } - inFlight.set(false); + + inFlight = false; return true; } return false; } public void markInFlight() { - awaiting.set(false); - inFlight.set(true); - sema.drainPermits(); + awaiting = false; + ready = false; + inFlight = true; + } + + public void signalReady() { + ready = true; } } + diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 98d284e463..5c903144e3 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Inject; @@ -31,7 +30,7 @@ public final class RenderThread implements Runnable { private final AtomicInteger pendingCount = new AtomicInteger(0); private final Object completionLock = new Object(); private final AtomicBoolean running = new AtomicBoolean(false); - private final Semaphore invokeOnRenderThreadSemaphore = new Semaphore(0); + private final FrameSync invokeOnRenderThreadSync = new FrameSync(); private Thread thread; private Thread clientJavaThread; @@ -198,15 +197,13 @@ public void run() { } public void invokeOnRenderThread(CallbackCommand.ICallback callback) { - invokeOnRenderThreadSemaphore.drainPermits(); + invokeOnRenderThreadSync.markInFlight(); CommandBuffer cmd = pool.acquire(); cmd.highPriority = true; cmd.Callback(callback); - cmd.Signal(invokeOnRenderThreadSemaphore); + cmd.Signal(invokeOnRenderThreadSync); submit(cmd); - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) - contextWrapper.detachCurrent("waiting for callback to be invoked on render thread"); - invokeOnRenderThreadSemaphore.acquireUninterruptibly(); + invokeOnRenderThreadSync.await(); } private void taskCompleted(RenderTask task) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java index c080980230..bde41c5553 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java @@ -1,26 +1,26 @@ package rs117.hd.opengl.commandbuffer.commands; -import java.util.concurrent.Semaphore; import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.commandbuffer.FrameSync; public class SignalCommand extends BaseCommand { - public Semaphore semaphore; + public FrameSync frameSync; @Override protected void doWrite() { - writeObject(semaphore); + writeObject(frameSync); } @Override protected void doRead(MemoryStack stack) { - semaphore = readObject(); + frameSync = readObject(); } @Override protected void execute(MemoryStack stack) { - semaphore.release(); + frameSync.signalReady(); } @Override diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index ca594a4e8d..6eed28cf45 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -83,7 +83,7 @@ protected void execute(MemoryStack stack) { for(int i = 0; i < chunks; i++) { if (frameSync != null && frameSync.isAwaiting()) { stagingData.put(data, offset, remaining); - frameSync.getSema().release(); + frameSync.signalReady(); stagingData.flip(); mappedIntBuffer.put(stagingData); @@ -97,7 +97,7 @@ protected void execute(MemoryStack stack) { } if (frameSync != null) { - frameSync.getSema().release(); + frameSync.signalReady(); } glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index e6d48b376d..15ed3eadd9 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -1341,7 +1341,7 @@ public void draw(int overlayColor) { cmd.EndTimer(Timer.SWAP_BUFFERS); cmd.EndTimer(Timer.RENDER_FRAME); cmd.Callback(frameTimer::endFrameAndReset); - cmd.Signal(renderingSync.getSema()); + cmd.Signal(renderingSync); renderingSync.markInFlight(); renderThread.submit(cmd); From ba06f2e0b70c78780b2000fa7ff494cf07b870ae Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:07:28 +0100 Subject: [PATCH 46/50] Double buffer the pixel unpack buffer --- src/main/java/rs117/hd/HdPlugin.java | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 15dc88f0dc..f7030e6704 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -328,7 +328,8 @@ public class HdPlugin extends Plugin { private int[] uiResolution; private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling private int texUi; - private int pboUi; + private int[] pboUi = new int[2]; + private int pboUiIdx = 0; private final FrameSync uiUploadSync = new FrameSync(); @Nullable @@ -1007,7 +1008,8 @@ private void destroyUbos() { } private void initializeUiTexture() { - pboUi = glGenBuffers(); + pboUi[0] = glGenBuffers(); + pboUi[1] = glGenBuffers(); texUi = glGenTextures(); glActiveTexture(TEXTURE_UNIT_UI); @@ -1021,9 +1023,12 @@ private void initializeUiTexture() { private void destroyUiTexture() { uiResolution = null; - if (pboUi != 0) - glDeleteBuffers(pboUi); - pboUi = 0; + for(int i = 0; i < pboUi.length; i++) { + if (pboUi[i] != 0) { + glDeleteBuffers(pboUi[i]); + pboUi[i] = 0; + } + } if (texUi != 0) glDeleteTextures(texUi); @@ -1325,8 +1330,10 @@ public void prepareInterfaceTexture() { renderer.waitUntilIdle(); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); - glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); + for(int i = 0; i < pboUi.length; i++) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi[i]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); + } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glActiveTexture(TEXTURE_UNIT_UI); @@ -1352,8 +1359,9 @@ public void prepareInterfaceTexture() { CommandBuffer cmd = commandBufferPool.acquire(); cmd.BeginTimer(Timer.UPLOAD_UI); - cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi, width, height, pixels, uiUploadSync); + cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi[pboUiIdx], width, height, pixels, uiUploadSync); cmd.EndTimer(Timer.UPLOAD_UI); + pboUiIdx = (pboUiIdx + 1) % pboUi.length; renderThread.submit(cmd); } From 72d2750ae33d9c65b99d12a3e864b04e80cdca87 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 25 Oct 2025 20:44:12 +0100 Subject: [PATCH 47/50] Tweaks --- src/main/java/rs117/hd/HdPlugin.java | 24 ++++----- .../hd/opengl/commandbuffer/RenderThread.java | 2 + .../commands/UploadPixelDataCommand.java | 11 ++-- src/main/java/rs117/hd/renderer/zone/VAO.java | 14 ++++-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 50 +++++++++++-------- 5 files changed, 60 insertions(+), 41 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index f7030e6704..893cbe7bfc 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -1008,8 +1008,8 @@ private void destroyUbos() { } private void initializeUiTexture() { - pboUi[0] = glGenBuffers(); - pboUi[1] = glGenBuffers(); + for(int i = 0; i < pboUi.length; i++) + pboUi[i] = glGenBuffers(); texUi = glGenTextures(); glActiveTexture(TEXTURE_UNIT_UI); @@ -1328,17 +1328,17 @@ public void prepareInterfaceTexture() { if (resize) { uiResolution = resolution; - renderer.waitUntilIdle(); - - for(int i = 0; i < pboUi.length; i++) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi[i]); - glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); - } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + renderThread.invokeOnRenderThread(() -> { + for(int i = 0; i < pboUi.length; i++) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi[i]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, uiResolution[0], uiResolution[1], 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glActiveTexture(TEXTURE_UNIT_UI); + glBindTexture(GL_TEXTURE_2D, texUi); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, uiResolution[0], uiResolution[1], 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + }); } if (client.isStretchedEnabled()) { diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 5c903144e3..666758dbe8 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -6,6 +6,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.LockSupport; import javax.inject.Inject; import javax.inject.Singleton; import lombok.SneakyThrows; @@ -165,6 +166,7 @@ public void run() { while (running.get()) { RenderTask task; try { + while (queue.isEmpty()) LockSupport.parkNanos(1); task = queue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index 6eed28cf45..da641fcf0f 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -13,11 +13,12 @@ import static org.lwjgl.opengl.GL12C.GL_BGRA; import static org.lwjgl.opengl.GL12C.GL_UNSIGNED_INT_8_8_8_8_REV; import static org.lwjgl.opengl.GL13C.glActiveTexture; -import static org.lwjgl.opengl.GL15C.GL_WRITE_ONLY; import static org.lwjgl.opengl.GL15C.glBindBuffer; -import static org.lwjgl.opengl.GL15C.glMapBuffer; import static org.lwjgl.opengl.GL15C.glUnmapBuffer; import static org.lwjgl.opengl.GL21C.GL_PIXEL_UNPACK_BUFFER; +import static org.lwjgl.opengl.GL30.GL_MAP_UNSYNCHRONIZED_BIT; +import static org.lwjgl.opengl.GL30.GL_MAP_WRITE_BIT; +import static org.lwjgl.opengl.GL30.glMapBufferRange; public class UploadPixelDataCommand extends BaseCommand { public int texUnit; @@ -67,7 +68,9 @@ protected void doRead(MemoryStack stack) { protected void execute(MemoryStack stack) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); - mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY, (long)(width * height) * Integer.BYTES, mappedBuffer); + mappedBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, + 0, ((long) width * height) * Integer.BYTES, + GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, mappedBuffer); if (mappedBuffer != null) { IntBuffer mappedIntBuffer = mappedBuffer.asIntBuffer(); @@ -100,9 +103,9 @@ protected void execute(MemoryStack stack) { frameSync.signalReady(); } - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glActiveTexture(texUnit); glBindTexture(GL_TEXTURE_2D, tex); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 9cfbd29e59..cedce24d70 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -156,16 +156,20 @@ VAO get(RenderThread renderThread, int size) { return vao; } + void map() { + for (VAO vao : vaos) { + if (!vao.vbo.mapped) + vao.vbo.map(); + } + } + void unmap() { - int sz = 0; for (VAO vao : vaos) { - if (vao.vbo.mapped) { - ++sz; + if (vao.vbo.mapped) vao.vbo.unmap(); - } } + drawCount = curIdx + 1; curIdx = 0; - drawCount = sz; } void free() { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 15ed3eadd9..64b373c8ce 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -167,7 +167,7 @@ public class ZoneRenderer implements Renderer { private final CommandBuffer zoneDrawCallBuffer = new CommandBuffer(); private final CommandBuffer zoneShadowDrawCallBuffer = new CommandBuffer(); - private final FrameSync renderingSync = new FrameSync(); + private final FrameSync mainFrameSync = new FrameSync(); // TODO: Can we rename these to something more human readable? class SceneVAOs { @@ -780,6 +780,14 @@ private void preSceneDrawTopLevel( zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + // Map VAO buffers ahead of time to reduce the number of calls to the RenderThread mid-building of CommandBuffer + renderThread.invokeOnRenderThread(() -> { + vaos.vaoO.map(); + vaos.vaoA.map(); + vaos.vaoPO.map(); + vaos.vaoPOShadow.map(); + }); + checkGLErrors(); } @@ -813,7 +821,6 @@ private void postDrawTopLevel() { }); } - if (plugin.configShadowsEnabled && plugin.fboShadowMap != 0 && environmentManager.currentDirectionalStrength > 0 @@ -1016,16 +1023,18 @@ public void drawPass(Projection projection, Scene scene, int pass) { zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); + renderThread.invokeOnRenderThread(() -> { + vaos.vaoO.unmap(); + vaos.vaoPO.unmap(); + vaos.vaoPOShadow.unmap(); + }); + // Draw opaque - renderThread.invokeOnRenderThread(vaos.vaoO::unmap); vaos.vaoO.drawAll(this, zoneDrawCallBuffer); vaos.vaoO.drawAll(this, zoneShadowDrawCallBuffer); vaos.vaoO.resetAll(); - renderThread.invokeOnRenderThread(vaos.vaoPO::unmap); - // Draw player shadows - renderThread.invokeOnRenderThread(vaos.vaoPOShadow::unmap); vaos.vaoPOShadow.drawAll(this, zoneShadowDrawCallBuffer); vaos.vaoPOShadow.resetAll(); @@ -1299,18 +1308,6 @@ public void draw(int overlayColor) { log.trace("draw(overlaySrgba={})", overlayColor); frameTimer.end(Timer.DRAW_FRAME); - if(!renderingSync.isAwaiting()) { - long time = System.nanoTime(); - if (renderingSync.await()) { - renderThread.processCompletedTasks(); - - SceneVAOs temp = vaos; - vaos = vaos_prev; - vaos_prev = temp; - } - frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); - } - try { plugin.prepareInterfaceTexture(); } catch (Exception ex) { @@ -1321,6 +1318,19 @@ public void draw(int overlayColor) { return; } + // Before submitting current frame, ensure previous frame has finished executing + if(!mainFrameSync.isAwaiting()) { + long time = System.nanoTime(); + if (mainFrameSync.await()) { + renderThread.processCompletedTasks(); + + SceneVAOs temp = vaos; + vaos = vaos_prev; + vaos_prev = temp; + } + frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); + } + CommandBuffer cmd = commandBufferPool.acquire(); if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { cmd.BlitFramebuffer( @@ -1341,9 +1351,9 @@ public void draw(int overlayColor) { cmd.EndTimer(Timer.SWAP_BUFFERS); cmd.EndTimer(Timer.RENDER_FRAME); cmd.Callback(frameTimer::endFrameAndReset); - cmd.Signal(renderingSync); + cmd.Signal(mainFrameSync); - renderingSync.markInFlight(); + mainFrameSync.markInFlight(); renderThread.submit(cmd); } From 9d34f445fe16401ec0e27e3ec2ef463bc223ad77 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 26 Oct 2025 21:08:11 +0000 Subject: [PATCH 48/50] Fixed FrameTimer --- .../hd/opengl/commandbuffer/RenderThread.java | 12 +++++-- src/main/java/rs117/hd/overlays/Timer.java | 2 ++ .../rs117/hd/renderer/zone/ZoneRenderer.java | 33 ++++++++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index 666758dbe8..d3eab432c4 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -6,6 +6,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.LockSupport; import javax.inject.Inject; import javax.inject.Singleton; @@ -16,6 +17,7 @@ import rs117.hd.HdPlugin; import rs117.hd.opengl.commandbuffer.commands.CallbackCommand; import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; @Slf4j @Singleton @@ -39,7 +41,7 @@ public final class RenderThread implements Runnable { private HdPlugin plugin; @Inject - private FrameTimer timer; + private FrameTimer frameTimer; @Inject private ClientThread clientThread; @@ -101,6 +103,7 @@ public void submit(CommandBuffer buffer, Runnable onComplete) { } else { queue.addLast(newTask); } + LockSupport.unpark(thread); } private boolean isClientThread() {return Thread.currentThread() == clientJavaThread; } @@ -166,7 +169,8 @@ public void run() { while (running.get()) { RenderTask task; try { - while (queue.isEmpty()) LockSupport.parkNanos(1); + while (queue.isEmpty()) + LockSupport.park(10); task = queue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -184,9 +188,11 @@ public void run() { contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); try { + long timeStamp = System.nanoTime(); stack.push(); task.buffer.execute(stack); stack.pop(); + frameTimer.add(Timer.COMMAND_BUFFER_EXECUTE, System.nanoTime() - timeStamp); } catch (Throwable t) { log.error("Error during CommandBuffer execution", t); } finally { @@ -205,7 +211,9 @@ public void invokeOnRenderThread(CallbackCommand.ICallback callback) { cmd.Callback(callback); cmd.Signal(invokeOnRenderThreadSync); submit(cmd); + long timestamp = System.nanoTime(); invokeOnRenderThreadSync.await(); + frameTimer.add(Timer.INVOKE_ON_RENDER_THREAD, System.nanoTime() - timestamp); } private void taskCompleted(RenderTask task) { diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 79302a7f5a..081ad55311 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,6 +17,8 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), + COMMAND_BUFFER_EXECUTE, + INVOKE_ON_RENDER_THREAD, FRAME_SYNC, COMMAND_BUFFER_READ, UPDATE_ENVIRONMENT, diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 1bea86cf5d..4bf994e197 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -389,6 +389,21 @@ private void preSceneDrawTopLevel( scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); + // Before submitting current frame, ensure previous frame has finished executing + if(!mainFrameSync.isAwaiting()) { + long time = System.nanoTime(); + if (mainFrameSync.await()) { + renderThread.processCompletedTasks(); + frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); + + renderThread.invokeOnRenderThread(frameTimer::endFrameAndReset); + + SceneVAOs temp = vaos; + vaos = vaos_prev; + vaos_prev = temp; + } + } + if (root.sceneContext == null || plugin.sceneViewport == null) return; @@ -786,9 +801,9 @@ private void preSceneDrawTopLevel( vaos.vaoA.map(); vaos.vaoPO.map(); vaos.vaoPOShadow.map(); - }); - checkGLErrors(); + checkGLErrors(); + }); } @Override @@ -1318,19 +1333,6 @@ public void draw(int overlayColor) { return; } - // Before submitting current frame, ensure previous frame has finished executing - if(!mainFrameSync.isAwaiting()) { - long time = System.nanoTime(); - if (mainFrameSync.await()) { - renderThread.processCompletedTasks(); - - SceneVAOs temp = vaos; - vaos = vaos_prev; - vaos_prev = temp; - } - frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); - } - CommandBuffer cmd = commandBufferPool.acquire(); if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { cmd.BlitFramebuffer( @@ -1350,7 +1352,6 @@ public void draw(int overlayColor) { cmd.SwapBuffers(awtContextWrapper.getContext()); cmd.EndTimer(Timer.SWAP_BUFFERS); cmd.EndTimer(Timer.RENDER_FRAME); - cmd.Callback(frameTimer::endFrameAndReset); cmd.Signal(mainFrameSync); mainFrameSync.markInFlight(); From 083c6ded6261f80389003473883a7771a9c2617a Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 26 Oct 2025 23:05:09 +0000 Subject: [PATCH 49/50] Optimizations --- .../hd/opengl/commandbuffer/FrameSync.java | 26 ++++++++--- .../hd/opengl/commandbuffer/RenderThread.java | 46 ++++++++----------- .../commands/BindFrameBufferCommand.java | 2 +- .../commands/BlendFuncCommand.java | 2 +- .../commands/BlitFrameBufferCommand.java | 2 +- .../commands/CallbackCommand.java | 2 +- .../commandbuffer/commands/ClearCommand.java | 2 +- .../commands/DepthFuncCommand.java | 2 +- .../commands/DrawBufferCommand.java | 2 +- .../commands/ExecuteCommandBufferCommand.java | 2 +- .../commands/FrameBufferLayerCommand.java | 2 +- .../commands/FrameTimerCommand.java | 2 +- .../commands/SetShaderUniformCommand.java | 2 +- .../SetUniformBufferPropertyCommand.java | 2 +- .../commands/ShaderProgramCommand.java | 2 +- .../commandbuffer/commands/SignalCommand.java | 2 +- .../commands/SwapBuffersCommand.java | 2 +- .../commands/UploadPixelDataCommand.java | 2 +- .../commands/ViewportCommand.java | 2 +- src/main/java/rs117/hd/renderer/zone/VAO.java | 9 ++-- .../rs117/hd/renderer/zone/ZoneRenderer.java | 46 ++++++++++--------- 21 files changed, 82 insertions(+), 79 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java index 5a7e921bdb..52fff91b0f 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java @@ -8,21 +8,30 @@ public class FrameSync { private volatile boolean awaiting = false; private volatile boolean ready = false; private volatile boolean inFlight = false; + private volatile Thread waitingThread; public boolean await() { - if (inFlight && !awaiting) { - awaiting = true; + if(inFlight) { + if (!awaiting) { + awaiting = true; + waitingThread = Thread.currentThread(); + } while (!ready) { - LockSupport.parkNanos(1); + LockSupport.park(this); + if (Thread.interrupted()) { + Thread.currentThread().interrupt(); + break; + } } - - inFlight = false; - return true; } - return false; + + inFlight = false; + waitingThread = null; + return true; } + public void markInFlight() { awaiting = false; ready = false; @@ -31,6 +40,9 @@ public void markInFlight() { public void signalReady() { ready = true; + Thread waiter = waitingThread; + if (waiter != null) + LockSupport.unpark(waiter); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java index d3eab432c4..b7308df2d7 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -6,8 +6,6 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.LockSupport; import javax.inject.Inject; import javax.inject.Singleton; import lombok.SneakyThrows; @@ -31,9 +29,9 @@ public final class RenderThread implements Runnable { private final List completedTasks = new ArrayList<>(); private final AtomicInteger pendingCount = new AtomicInteger(0); - private final Object completionLock = new Object(); private final AtomicBoolean running = new AtomicBoolean(false); private final FrameSync invokeOnRenderThreadSync = new FrameSync(); + private final FrameSync renderCompleteSync = new FrameSync(); private Thread thread; private Thread clientJavaThread; @@ -98,12 +96,13 @@ public void submit(CommandBuffer buffer, Runnable onComplete) { newTask.buffer = buffer; newTask.callback = onComplete; + renderCompleteSync.markInFlight(); + if(buffer.highPriority) { queue.addFirst(newTask); } else { queue.addLast(newTask); } - LockSupport.unpark(thread); } private boolean isClientThread() {return Thread.currentThread() == clientJavaThread; } @@ -136,30 +135,25 @@ public void processCompletedTasks() { @SneakyThrows public void waitForRenderingCompleted() { - if(pendingCount.get() == 0 && completedTasks.isEmpty() && contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) { + if (pendingCount.get() == 0 && completedTasks.isEmpty() + && contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) { return; } - synchronized (completionLock) { - try { - while (pendingCount.get() > 0) { - if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) - contextWrapper.detachCurrent("waiting for render completion"); + while (pendingCount.get() > 0) { + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) + contextWrapper.detachCurrent("waiting for render completion"); - completionLock.wait(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } + renderCompleteSync.await(); + } - invokeOnRenderThread(() -> contextWrapper.detachCurrent("detached after render completion")); - contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); + invokeOnRenderThread(() -> contextWrapper.detachCurrent("detached after render completion")); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); - processCompletedTasks(); - } + processCompletedTasks(); } + @SneakyThrows @Override public void run() { @@ -169,8 +163,6 @@ public void run() { while (running.get()) { RenderTask task; try { - while (queue.isEmpty()) - LockSupport.park(10); task = queue.take(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -205,15 +197,15 @@ public void run() { } public void invokeOnRenderThread(CallbackCommand.ICallback callback) { - invokeOnRenderThreadSync.markInFlight(); + long timeStamp = System.nanoTime(); CommandBuffer cmd = pool.acquire(); cmd.highPriority = true; cmd.Callback(callback); cmd.Signal(invokeOnRenderThreadSync); + invokeOnRenderThreadSync.markInFlight(); submit(cmd); - long timestamp = System.nanoTime(); invokeOnRenderThreadSync.await(); - frameTimer.add(Timer.INVOKE_ON_RENDER_THREAD, System.nanoTime() - timestamp); + frameTimer.add(Timer.INVOKE_ON_RENDER_THREAD, System.nanoTime() - timeStamp); } private void taskCompleted(RenderTask task) { @@ -224,9 +216,7 @@ private void taskCompleted(RenderTask task) { int remaining = pendingCount.decrementAndGet(); if (remaining <= 0) { pendingCount.set(0); - synchronized (completionLock) { - completionLock.notifyAll(); - } + renderCompleteSync.signalReady(); } } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java index ed4ca27fc0..266c75f9fd 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java @@ -5,7 +5,7 @@ import static org.lwjgl.opengl.GL30C.glBindFramebuffer; -public class BindFrameBufferCommand extends BaseCommand { +public final class BindFrameBufferCommand extends BaseCommand { public int target; public int fbo; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java index 4cd50ffac9..fb2403290f 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java @@ -5,7 +5,7 @@ import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; -public class BlendFuncCommand extends BaseCommand { +public final class BlendFuncCommand extends BaseCommand { public int sfactorRGB; public int dfactorRGB; public int sfactorAlpha; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java index 75d9a410fd..76706e8093 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java @@ -10,7 +10,7 @@ import static org.lwjgl.opengl.GL30.glBlitFramebuffer; -public class BlitFrameBufferCommand extends BaseCommand { +public final class BlitFrameBufferCommand extends BaseCommand { public int srcFbo; public int resolveFbo; // optional intermediate FBO for MSAA resolve public int dstFbo; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java index 41d078fe51..f0edd2dc77 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java @@ -3,7 +3,7 @@ import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; -public class CallbackCommand extends BaseCommand { +public final class CallbackCommand extends BaseCommand { public interface ICallback { void run(); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java index 125a7f96bd..0b08ee33e9 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java @@ -9,7 +9,7 @@ import static org.lwjgl.opengl.GL11C.GL_DEPTH_BUFFER_BIT; import static org.lwjgl.opengl.GL11C.glClearDepth; -public class ClearCommand extends BaseCommand { +public final class ClearCommand extends BaseCommand { public boolean clearColor; public boolean clearDepth; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java index 8eed3df780..3512c80c1e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java @@ -5,7 +5,7 @@ import static org.lwjgl.opengl.GL11C.glDepthFunc; -public class DepthFuncCommand extends BaseCommand { +public final class DepthFuncCommand extends BaseCommand { public int mode; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java index 435e343457..72ead31d38 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java @@ -6,7 +6,7 @@ import static org.lwjgl.opengl.GL20.glDrawBuffers; -public class DrawBufferCommand extends BaseCommand { +public final class DrawBufferCommand extends BaseCommand { public int[] drawBuffers; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java index 45b285f422..db95e943bf 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java @@ -4,7 +4,7 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.commandbuffer.CommandBuffer; -public class ExecuteCommandBufferCommand extends BaseCommand { +public final class ExecuteCommandBufferCommand extends BaseCommand { public CommandBuffer cmd; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java index 6e86ec43ef..e1df478f8f 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java @@ -6,7 +6,7 @@ import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER; import static org.lwjgl.opengl.GL30C.glFramebufferTextureLayer; -public class FrameBufferLayerCommand extends BaseCommand { +public final class FrameBufferLayerCommand extends BaseCommand { public int attachment; public int texId; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java index 4bcad6beaf..e0a0caf6c2 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java @@ -5,7 +5,7 @@ import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; -public class FrameTimerCommand extends BaseCommand { +public final class FrameTimerCommand extends BaseCommand { public Timer timer; public boolean begin; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java index 8c0d0578c9..2d97720c8f 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java @@ -4,7 +4,7 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.shader.ShaderProgram; -public class SetShaderUniformCommand extends BaseCommand { +public final class SetShaderUniformCommand extends BaseCommand { public ShaderProgram.UniformProperty property; public int[] intValues; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java index cf46dc94fd..31420f0c0d 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java @@ -4,7 +4,7 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.uniforms.UniformBuffer; -public class SetUniformBufferPropertyCommand extends BaseCommand { +public final class SetUniformBufferPropertyCommand extends BaseCommand { public UniformBuffer.Property property; public int[] intValues; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java index 8519207460..05bf4be90c 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java @@ -4,7 +4,7 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.shader.ShaderProgram; -public class ShaderProgramCommand extends BaseCommand { +public final class ShaderProgramCommand extends BaseCommand { public ShaderProgram program; public ShaderProgramCommand() { super(false, true); } diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java index bde41c5553..a444a2344e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java @@ -4,7 +4,7 @@ import rs117.hd.opengl.commandbuffer.BaseCommand; import rs117.hd.opengl.commandbuffer.FrameSync; -public class SignalCommand extends BaseCommand { +public final class SignalCommand extends BaseCommand { public FrameSync frameSync; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java index fc302bc67b..7b87c828ea 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java @@ -4,7 +4,7 @@ import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.commandbuffer.BaseCommand; -public class SwapBuffersCommand extends BaseCommand { +public final class SwapBuffersCommand extends BaseCommand { public AWTContext awtContext; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java index da641fcf0f..a3de74b46e 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -20,7 +20,7 @@ import static org.lwjgl.opengl.GL30.GL_MAP_WRITE_BIT; import static org.lwjgl.opengl.GL30.glMapBufferRange; -public class UploadPixelDataCommand extends BaseCommand { +public final class UploadPixelDataCommand extends BaseCommand { public int texUnit; public int tex; public int pbo; diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java index 9dac7f05e5..365514a333 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java @@ -5,7 +5,7 @@ import static org.lwjgl.opengl.GL11C.glViewport; -public class ViewportCommand extends BaseCommand { +public final class ViewportCommand extends BaseCommand { public int x; public int y; public int width; diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index cedce24d70..601f28c714 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -78,8 +78,6 @@ void destroy() { int off = 0; void addRange(Scene scene) { - assert vbo.mapped; - if (off > 0 && lengths[off - 1] == vbo.vb.position()) { return; } @@ -96,8 +94,6 @@ void addRange(Scene scene) { } void draw(ZoneRenderer renderer, CommandBuffer cmd) { - assert !vbo.mapped; - int start = 0; for (int i = 0; i < off; ++i) { int end = lengths[i]; @@ -163,12 +159,15 @@ void map() { } } + void setDrawCount() { + drawCount = curIdx + 1; + } + void unmap() { for (VAO vao : vaos) { if (vao.vbo.mapped) vao.vbo.unmap(); } - drawCount = curIdx + 1; curIdx = 0; } diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 4bf994e197..2f17b545db 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -163,6 +163,7 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; + private int frameNo; private final CommandBuffer zoneDrawCallBuffer = new CommandBuffer(); private final CommandBuffer zoneShadowDrawCallBuffer = new CommandBuffer(); @@ -170,7 +171,7 @@ public class ZoneRenderer implements Renderer { private final FrameSync mainFrameSync = new FrameSync(); // TODO: Can we rename these to something more human readable? - class SceneVAOs { + class DrawContext { public VAO.VAOList vaoO; public VAO.VAOList vaoA; public VAO.VAOList vaoPO; @@ -191,7 +192,7 @@ public void free() { vaoO = vaoA = vaoPO = vaoPOShadow = null; } } - private SceneVAOs vaos = new SceneVAOs(), vaos_prev = new SceneVAOs(); + private DrawContext vaos = new DrawContext(), vaos_prev = new DrawContext(); public static int eboAlpha; public static GpuIntBuffer eboAlphaStaging; @@ -396,11 +397,21 @@ private void preSceneDrawTopLevel( renderThread.processCompletedTasks(); frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); - renderThread.invokeOnRenderThread(frameTimer::endFrameAndReset); - - SceneVAOs temp = vaos; + DrawContext temp = vaos; vaos = vaos_prev; vaos_prev = temp; + + // Map VAO buffers ahead of time to reduce the number of calls to the RenderThread mid-building of CommandBuffer + renderThread.invokeOnRenderThread(() -> { + frameTimer.endFrameAndReset(); + + vaos.vaoO.map(); + vaos.vaoA.map(); + vaos.vaoPO.map(); + vaos.vaoPOShadow.map(); + + checkGLErrors(); + }); } } @@ -794,16 +805,6 @@ private void preSceneDrawTopLevel( zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); - - // Map VAO buffers ahead of time to reduce the number of calls to the RenderThread mid-building of CommandBuffer - renderThread.invokeOnRenderThread(() -> { - vaos.vaoO.map(); - vaos.vaoA.map(); - vaos.vaoPO.map(); - vaos.vaoPOShadow.map(); - - checkGLErrors(); - }); } @Override @@ -823,13 +824,16 @@ private void postDrawTopLevel() { sceneFboValid = true; - renderThread.invokeOnRenderThread(vaos.vaoA::unmap); - CommandBuffer cmd = commandBufferPool.acquire(); // Scene draw state to apply before all recorded commands if (eboAlphaStaging.position() > 0) { cmd.Callback(() -> { + vaos.vaoO.unmap(); + vaos.vaoPO.unmap(); + vaos.vaoPOShadow.unmap(); + vaos.vaoA.unmap(); + eboAlphaStaging.flip(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboAlpha); glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); @@ -1038,11 +1042,9 @@ public void drawPass(Projection projection, Scene scene, int pass) { zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); - renderThread.invokeOnRenderThread(() -> { - vaos.vaoO.unmap(); - vaos.vaoPO.unmap(); - vaos.vaoPOShadow.unmap(); - }); + vaos.vaoO.setDrawCount(); + vaos.vaoPO.setDrawCount(); + vaos.vaoPOShadow.setDrawCount(); // Draw opaque vaos.vaoO.drawAll(this, zoneDrawCallBuffer); From 1d3ddf1910520a59bef1b60f93b0062b1e3f683a Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 26 Oct 2025 23:55:34 +0000 Subject: [PATCH 50/50] Optimizations --- .../hd/opengl/commandbuffer/FrameSync.java | 20 ++-------- .../rs117/hd/renderer/zone/ZoneRenderer.java | 38 +++++++------------ 2 files changed, 16 insertions(+), 42 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java index 52fff91b0f..8ca96a2838 100644 --- a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java +++ b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java @@ -1,6 +1,5 @@ package rs117.hd.opengl.commandbuffer; -import java.util.concurrent.locks.LockSupport; import lombok.Getter; public class FrameSync { @@ -8,26 +7,16 @@ public class FrameSync { private volatile boolean awaiting = false; private volatile boolean ready = false; private volatile boolean inFlight = false; - private volatile Thread waitingThread; public boolean await() { - if(inFlight) { - if (!awaiting) { - awaiting = true; - waitingThread = Thread.currentThread(); - } - + if(inFlight && !awaiting) { + awaiting = true; while (!ready) { - LockSupport.park(this); - if (Thread.interrupted()) { - Thread.currentThread().interrupt(); - break; - } + Thread.onSpinWait(); } } inFlight = false; - waitingThread = null; return true; } @@ -40,9 +29,6 @@ public void markInFlight() { public void signalReady() { ready = true; - Thread waiter = waitingThread; - if (waiter != null) - LockSupport.unpark(waiter); } } diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 2f17b545db..a59e34c95a 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -390,31 +390,6 @@ private void preSceneDrawTopLevel( scene.setDrawDistance(plugin.getDrawDistance()); plugin.updateSceneFbo(); - // Before submitting current frame, ensure previous frame has finished executing - if(!mainFrameSync.isAwaiting()) { - long time = System.nanoTime(); - if (mainFrameSync.await()) { - renderThread.processCompletedTasks(); - frameTimer.add(Timer.FRAME_SYNC, System.nanoTime() - time); - - DrawContext temp = vaos; - vaos = vaos_prev; - vaos_prev = temp; - - // Map VAO buffers ahead of time to reduce the number of calls to the RenderThread mid-building of CommandBuffer - renderThread.invokeOnRenderThread(() -> { - frameTimer.endFrameAndReset(); - - vaos.vaoO.map(); - vaos.vaoA.map(); - vaos.vaoPO.map(); - vaos.vaoPOShadow.map(); - - checkGLErrors(); - }); - } - } - if (root.sceneContext == null || plugin.sceneViewport == null) return; @@ -837,6 +812,18 @@ private void postDrawTopLevel() { eboAlphaStaging.flip(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboAlpha); glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); + + // Prepare for next frame + DrawContext temp = vaos; + vaos = vaos_prev; + vaos_prev = temp; + + vaos.vaoO.map(); + vaos.vaoA.map(); + vaos.vaoPO.map(); + vaos.vaoPOShadow.map(); + + checkGLErrors(); }); } @@ -1354,6 +1341,7 @@ public void draw(int overlayColor) { cmd.SwapBuffers(awtContextWrapper.getContext()); cmd.EndTimer(Timer.SWAP_BUFFERS); cmd.EndTimer(Timer.RENDER_FRAME); + cmd.Callback(frameTimer::endFrameAndReset); cmd.Signal(mainFrameSync); mainFrameSync.markInFlight();