Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io.github.marcsanzdev.chestseparators.client.ui;

import java.nio.ByteBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.system.MemoryUtil;

/**
* Cursor zoom loupe. It samples a small block of the real framebuffer under the cursor and draws it
Expand Down Expand Up @@ -44,20 +41,9 @@ public static void render(GuiGraphicsExtractor context, int cursorX, int cursorY
int fbX = clamp((int) Math.round((cursorX - srcHalf) * scale), 0, fbW - blockFb);
int fbYBottom = clamp(fbH - (int) Math.round((cursorY + srcHalf) * scale), 0, fbH - blockFb);

int[] pixels = new int[blockFb * blockFb];
// Heap-allocated native buffer: a stack buffer (MemoryStack) overflows for large loupe sizes.
ByteBuffer buf = MemoryUtil.memAlloc(blockFb * blockFb * 4);
try {
GL11.glReadPixels(fbX, fbYBottom, blockFb, blockFb, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
for (int i = 0; i < pixels.length; i++) {
int r = buf.get(i * 4) & 0xFF;
int g = buf.get(i * 4 + 1) & 0xFF;
int b = buf.get(i * 4 + 2) & 0xFF;
pixels[i] = 0xFF000000 | (r << 16) | (g << 8) | b;
}
} finally {
MemoryUtil.memFree(buf);
}
// Crash-safe readback (forces a sane pixel-pack state, bounds-checked read). See SafePixelReader.
int[] pixels = SafePixelReader.readArgb(fbX, fbYBottom, blockFb, blockFb);
if (pixels == null) return;

// Place the loupe up-right of the cursor, flipping to stay on screen.
int sw = client.getWindow().getGuiScaledWidth();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package io.github.marcsanzdev.chestseparators.client.ui;

import java.nio.ByteBuffer;
import net.minecraft.client.Minecraft;
import org.lwjgl.opengl.ARBRobustness;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.GL21C;
import org.lwjgl.opengl.GL45C;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.system.MemoryUtil;

/**
* Crash-safe framebuffer readback for the loupe and the eyedropper.
*
* <p>Plain {@code glReadPixels} trusts the current pixel-pack state. Other mods (or vanilla) may leave
* {@code GL_PACK_ROW_LENGTH}, {@code GL_PACK_SKIP_*} or a bound {@code GL_PIXEL_PACK_BUFFER} behind, which
* makes the driver write more bytes than the destination buffer holds (hard JVM crash on AMD,
* EXCEPTION_ACCESS_VIOLATION in atio6axx.dll). This helper:
* <ul>
* <li>clamps the region to the framebuffer,</li>
* <li>forces a known pack state (no PBO, alignment 4, row length 0, no skips) and restores it after,</li>
* <li>uses the bounds-checked {@code glReadnPixels} (GL 4.5 / KHR / ARB robustness) when available,</li>
* <li>adds slack to the destination buffer as a last line of defence.</li>
* </ul>
*/
public final class SafePixelReader {

private SafePixelReader() {}

private static final int SLACK = 64 * 1024;

/**
* Reads a w*h block at framebuffer coords (x, yBottom) as opaque ARGB ints, row 0 = bottom row.
* Returns {@code null} if nothing could be read; the caller should then skip drawing.
*/
public static int[] readArgb(int x, int yBottom, int w, int h) {
Minecraft client = Minecraft.getInstance();
if (client == null || client.getWindow() == null) return null;
int fbW = client.getWindow().getWidth();
int fbH = client.getWindow().getHeight();
if (w <= 0 || h <= 0 || fbW <= 0 || fbH <= 0 || w > fbW || h > fbH) return null;
x = Math.max(0, Math.min(x, fbW - w));
yBottom = Math.max(0, Math.min(yBottom, fbH - h));

GLCapabilities caps;
try {
caps = GL.getCapabilities();
} catch (Throwable t) {
return null; // no GL context on this thread
}

int bytes = w * h * 4;
ByteBuffer buf = MemoryUtil.memCalloc(bytes + SLACK);
buf.limit(bytes); // glReadnPixels uses remaining() as the hard size limit

int prevPbo = GL11C.glGetInteger(GL21C.GL_PIXEL_PACK_BUFFER_BINDING);
int prevAlign = GL11C.glGetInteger(GL11C.GL_PACK_ALIGNMENT);
int prevRowLen = GL11C.glGetInteger(GL11C.GL_PACK_ROW_LENGTH);
int prevSkipRows = GL11C.glGetInteger(GL11C.GL_PACK_SKIP_ROWS);
int prevSkipPixels = GL11C.glGetInteger(GL11C.GL_PACK_SKIP_PIXELS);
int prevSwap = GL11C.glGetInteger(GL11C.GL_PACK_SWAP_BYTES);
int prevLsb = GL11C.glGetInteger(GL11C.GL_PACK_LSB_FIRST);
try {
if (prevPbo != 0) GL15C.glBindBuffer(GL21C.GL_PIXEL_PACK_BUFFER, 0);
GL11C.glPixelStorei(GL11C.GL_PACK_ALIGNMENT, 4);
GL11C.glPixelStorei(GL11C.GL_PACK_ROW_LENGTH, 0);
GL11C.glPixelStorei(GL11C.GL_PACK_SKIP_ROWS, 0);
GL11C.glPixelStorei(GL11C.GL_PACK_SKIP_PIXELS, 0);
GL11C.glPixelStorei(GL11C.GL_PACK_SWAP_BYTES, 0);
GL11C.glPixelStorei(GL11C.GL_PACK_LSB_FIRST, 0);

if (caps.OpenGL45 || caps.GL_KHR_robustness) {
GL45C.glReadnPixels(x, yBottom, w, h, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, buf);
} else if (caps.GL_ARB_robustness) {
ARBRobustness.glReadnPixelsARB(x, yBottom, w, h, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, buf);
} else {
GL11C.glReadPixels(x, yBottom, w, h, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, buf);
}

int[] out = new int[w * h];
for (int i = 0; i < out.length; i++) {
int r = buf.get(i * 4) & 0xFF;
int g = buf.get(i * 4 + 1) & 0xFF;
int b = buf.get(i * 4 + 2) & 0xFF;
out[i] = 0xFF000000 | (r << 16) | (g << 8) | b;
}
return out;
} finally {
GL11C.glPixelStorei(GL11C.GL_PACK_ALIGNMENT, prevAlign);
GL11C.glPixelStorei(GL11C.GL_PACK_ROW_LENGTH, prevRowLen);
GL11C.glPixelStorei(GL11C.GL_PACK_SKIP_ROWS, prevSkipRows);
GL11C.glPixelStorei(GL11C.GL_PACK_SKIP_PIXELS, prevSkipPixels);
GL11C.glPixelStorei(GL11C.GL_PACK_SWAP_BYTES, prevSwap);
GL11C.glPixelStorei(GL11C.GL_PACK_LSB_FIRST, prevLsb);
if (prevPbo != 0) GL15C.glBindBuffer(GL21C.GL_PIXEL_PACK_BUFFER, prevPbo);
MemoryUtil.memFree(buf);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package io.github.marcsanzdev.chestseparators.client.ui.screens;

import java.awt.Color;
import java.nio.ByteBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.system.MemoryStack;

/**
* Stateless drawing helpers for the color picker: the saturation/value gradient box, the hue
Expand Down Expand Up @@ -43,14 +40,8 @@ static int readHoveredPixelColor(int mouseX, int mouseY) {
double scale = client.getWindow().getGuiScale();
int fbX = (int) (mouseX * scale);
int fbY = client.getWindow().getHeight() - (int) (mouseY * scale) - 1;

try (MemoryStack stack = MemoryStack.stackPush()) {
ByteBuffer buffer = stack.malloc(4);
GL11.glReadPixels(fbX, fbY, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buffer);
int r = buffer.get(0) & 0xFF;
int g = buffer.get(1) & 0xFF;
int b = buffer.get(2) & 0xFF;
return (r << 16) | (g << 8) | b;
}
// Crash-safe readback (forces a sane pixel-pack state, bounds-checked read).
int[] px = io.github.marcsanzdev.chestseparators.client.ui.SafePixelReader.readArgb(fbX, fbY, 1, 1);
return px == null ? 0 : (px[0] & 0x00FFFFFF);
}
}