Skip to content

Commit 86b07a6

Browse files
committed
legui and jxl support for later.
1 parent a90b715 commit 86b07a6

File tree

17 files changed

+887
-46
lines changed

17 files changed

+887
-46
lines changed

build.gradle.kts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ repositories {
3939
mavenLocal()
4040
maven("https://jitpack.io")
4141
maven("https://maven.generations.gg/releases")
42+
maven("https://raw.githubusercontent.com/SpinyOwl/repo/releases")
4243
}
4344

4445
dependencies {
@@ -57,13 +58,19 @@ dependencies {
5758
"shadowTools"(implementation("org.lwjgl", "lwjgl-glfw"))
5859
"shadowTools"(implementation("org.lwjgl", "lwjgl-opengl"))
5960
"shadowTools"(implementation("org.lwjgl", "lwjgl-stb"))
61+
"shadowTools"(implementation("org.lwjgl", "lwjgl-yoga"))
62+
"shadowTools"(implementation("org.lwjgl", "lwjgl-nanovg"))
6063
"shadow"(implementation("org.lwjgl", "lwjgl-assimp", "3.3.2")) //Only now just to keep assimp native from complaining
6164
"shadow"(implementation("com.github.thecodewarrior", "BinarySMD", "-SNAPSHOT"))
6265
"shadow"(implementation("org.msgpack", "msgpack-core", "0.8.17"))
6366
"shadowTools"(implementation(fileTree(mapOf("dir" to "libs", "include" to "*.jar")))!!)
6467
"shadow"(implementation("com.github.ben-manes.caffeine:caffeine:3.1.8")!!)
6568
"shadow"(implementation("io.github.mudbill:dds-lwjgl:3.0.0")!!)
6669

70+
"shadowTools"(implementation("com.google.guava:guava:33.4.0-jre")!!)
71+
"shadowTools"(implementation("com.squareup.moshi:moshi-kotlin:1.15.2")!!)
72+
73+
"shadowTools"(implementation("org.liquidengine:cbchain:1.0.0")!!)
6774

6875
listOf("windows", "macos", "linux", ).forEach { os ->
6976
listOf("-arm64", "").forEach { cpu ->
@@ -73,6 +80,9 @@ dependencies {
7380
"shadowTools"(runtimeOnly("org.lwjgl", "lwjgl-stb", classifier = "natives-$os$cpu"))
7481
"shadow"(runtimeOnly("org.lwjgl", "lwjgl-assimp", classifier = "natives-$os$cpu"))
7582
"shadowTools"(runtimeOnly("org.lwjgl", "lwjgl-nfd", classifier = "natives-$os$cpu"))
83+
84+
"shadowTools"(runtimeOnly("org.lwjgl", "lwjgl-nanovg", classifier = "natives-$os$cpu"))
85+
"shadowTools"(runtimeOnly("org.lwjgl", "lwjgl-yoga", classifier = "natives-$os$cpu"))
7686
}
7787
}
7888

@@ -86,10 +96,7 @@ dependencies {
8696

8797
"shadow"(implementation("com.google.flatbuffers:flatbuffers-java:23.5.26")!!)
8898

89-
//TODO: JT need some funky gradle logic that lets us build a version does and doesn't include gson for the viewer and generations respectively
9099
"shadowTools"(implementation("com.google.code.gson:gson:2.10.1")!!)
91-
92-
93100
}
94101

95102
tasks {

libs/cbchain-1.0.2.jar

25.8 KB
Binary file not shown.

libs/jxlatte-2.1.0.jar

228 KB
Binary file not shown.

libs/legui-4.2.0-javadoc.jar

1.5 MB
Binary file not shown.

libs/legui-4.2.0-sources.jar

1.26 MB
Binary file not shown.

libs/legui-4.2.0.jar

1.31 MB
Binary file not shown.

src/library/java/gg/generations/rarecandy/renderer/rendering/FrameBuffer.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package gg.generations.rarecandy.renderer.rendering;
22

3+
import com.spinyowl.legui.image.Image;
34
import gg.generations.rarecandy.renderer.loading.ITexture;
45
import org.lwjgl.BufferUtils;
56
import org.lwjgl.opengl.GL11;
@@ -14,14 +15,16 @@
1415
import java.nio.file.Files;
1516
import java.nio.file.Path;
1617

17-
public class FrameBuffer implements ITexture {
18+
public class FrameBuffer extends Image implements ITexture {
1819
private final int framebufferId;
1920
private final int textureId;
2021
private final int rbo;
2122
private final int width;
2223
private final int height;
2324

2425
public FrameBuffer(int width, int height) {
26+
super();
27+
2528
this.width = width;
2629
this.height = height;
2730
framebufferId = GL30.glGenFramebuffers();
@@ -67,6 +70,14 @@ public int width() {
6770
return width;
6871
}
6972

73+
public int getWidth() {
74+
return width();
75+
}
76+
77+
public int getHeight() {
78+
return height();
79+
}
80+
7081
public int height() {
7182
return height;
7283
}
@@ -207,4 +218,8 @@ public boolean captureScreenshot(Path filePath, boolean isPortrait) throws IOExc
207218
return false;
208219
}
209220
}
221+
222+
public int getTextureId() {
223+
return textureId;
224+
}
210225
}

src/main/java/gg/generations/rarecandy/renderer/loading/Texture.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package gg.generations.rarecandy.renderer.loading;
22

3+
import com.traneptora.jxlatte.JXLDecoder;
4+
import com.traneptora.jxlatte.JXLImage;
5+
import com.traneptora.jxlatte.JXLatte;
6+
import com.traneptora.jxlatte.color.ColorFlags;
7+
import com.traneptora.jxlatte.util.ImageBuffer;
38
import io.github.mudbill.dds.DDSFile;
9+
import org.jetbrains.annotations.NotNull;
410
import org.lwjgl.opengl.GL11;
511
import org.lwjgl.opengl.GL11C;
612
import org.lwjgl.opengl.GL13C;
@@ -64,6 +70,8 @@ public static Texture read(byte[] imageBytes, String name) throws IOException {
6470
var dds = new DDSFile(new ByteArrayInputStream(imageBytes));
6571

6672
return new Texture(new DDSTextureDetails(dds));
73+
} else if(name.endsWith(".jxl")) {
74+
return new Texture(readJXL(imageBytes));
6775
} else return new Texture(read(imageBytes));
6876
}
6977

@@ -101,6 +109,84 @@ public static TextureDetails read(byte[] bytes) {
101109
return new TextureDetailsSTB(image, comp == 3 ? Type.RGB_BYTE : Type.RGBA_BYTE, w, h);
102110
}
103111

112+
private static TextureDetails readJXL(byte[] bytes) throws IOException {
113+
// Decode the JPEG XL image
114+
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
115+
JXLImage jxlImage = new JXLDecoder(inputStream).decode();
116+
117+
if (jxlImage == null) {
118+
throw new IOException("Failed to decode JPEG XL image.");
119+
}
120+
121+
// Validate 8-bit support (common for typical GL texture usage)
122+
int bitDepth = jxlImage.getHeader().getBitDepthHeader().bitsPerSample;
123+
if (bitDepth != 8) {
124+
throw new IOException("Unsupported bit depth: " + bitDepth + ". Only 8-bit is supported.");
125+
}
126+
127+
// Retrieve relevant metadata
128+
int width = jxlImage.getWidth();
129+
int height = jxlImage.getHeight();
130+
boolean hasAlpha = jxlImage.hasAlpha();
131+
132+
// Decide how many channels to pull from the image buffers
133+
// For colorEncoding == CE_GRAY -> 1 channel (plus alpha if present)
134+
// For colorEncoding == CE_RGB -> 3 channels (plus alpha if present)
135+
int colorChannels = jxlImage.getColorChannelCount(); // 1 (gray) or 3 (rgb)
136+
if (hasAlpha) {
137+
colorChannels++; // add one for alpha
138+
}
139+
140+
// Prepare a ByteBuffer for the raw pixel data
141+
ByteBuffer pixelData = MemoryUtil.memAlloc(width * height * colorChannels);
142+
143+
// We'll gather channel indices to read in the correct order (R, G, B, then alpha)
144+
// If it's grayscale, that means channel=0 for Gray, and if alpha exists, alphaIndex
145+
// If it's RGB, channels=0,1,2, then alphaIndex if present
146+
ImageBuffer[] buffers = jxlImage.getBuffer(false);
147+
int[] channelIndices = getInts(jxlImage, hasAlpha);
148+
149+
// Write raw pixel data (8-bit) into the ByteBuffer
150+
for (int y = 0; y < height; y++) {
151+
for (int x = 0; x < width; x++) {
152+
for (int index : channelIndices) {
153+
// Get the integer sample; JXL is guaranteed 8-bit in this context
154+
int sample = buffers[index].getIntBuffer()[y][x];
155+
pixelData.put((byte) sample);
156+
}
157+
}
158+
}
159+
pixelData.flip();
160+
161+
// Determine texture format
162+
Texture.Type type = hasAlpha ? Texture.Type.RGBA_BYTE : Texture.Type.RGB_BYTE;
163+
164+
// Wrap and return
165+
return new TextureDetailsSTB(pixelData, type, width, height);
166+
}
167+
168+
private static int @NotNull [] getInts(JXLImage jxlImage, boolean hasAlpha) {
169+
int alphaIndex = jxlImage.getAlphaIndex(); // -1 if none
170+
171+
// Build the ordered channel list
172+
// e.g., if CE_GRAY and alphaIndex=1 -> channels = [0, 1]
173+
// if CE_RGB and alphaIndex=3 -> channels = [0, 1, 2, 3]
174+
// if CE_RGB and no alpha -> channels = [0, 1, 2]
175+
// etc.
176+
int[] channelIndices;
177+
if (jxlImage.getColorEncoding() == ColorFlags.CE_GRAY) {
178+
channelIndices = hasAlpha ? new int[]{0, alphaIndex} : new int[]{0};
179+
} else {
180+
// CE_RGB
181+
if (hasAlpha) {
182+
channelIndices = new int[]{0, 1, 2, alphaIndex};
183+
} else {
184+
channelIndices = new int[]{0, 1, 2};
185+
}
186+
}
187+
return channelIndices;
188+
}
189+
104190
public enum Type {
105191
RGBA_BYTE(GL30.GL_RGBA8, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE),
106192
RGB_BYTE(GL30.GL_RGB8, GL30.GL_RGB, GL30.GL_UNSIGNED_BYTE);
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package gg.generations.rarecandy.tools.gui;
2+
3+
import com.spinyowl.legui.system.context.CallbackKeeper;
4+
import com.spinyowl.legui.system.context.DefaultCallbackKeeper;
5+
import gg.generations.rarecandy.pokeutils.MaterialReference;
6+
import gg.generations.rarecandy.pokeutils.PixelAsset;
7+
import gg.generations.rarecandy.renderer.loading.ModelLoader;
8+
import gg.generations.rarecandy.renderer.rendering.FrameBuffer;
9+
import org.liquidengine.cbchain.IChainCharCallback;
10+
import org.lwjgl.glfw.GLFWKeyCallbackI;
11+
import org.lwjgl.glfw.GLFWWindowCloseCallbackI;
12+
import org.lwjgl.nanovg.*;
13+
import org.lwjgl.opengl.GL11;
14+
import org.lwjgl.opengl.GL30;
15+
import org.lwjgl.util.nfd.NativeFileDialog;
16+
17+
import java.util.ArrayList;
18+
import java.util.HashMap;
19+
import java.util.List;
20+
import java.util.function.Supplier;
21+
22+
import static gg.generations.rarecandy.tools.gui.MinimalQuad.mapDuplicatesWithStreams;
23+
import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE;
24+
import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
25+
import static org.lwjgl.nanovg.NanoVG.*;
26+
import static org.lwjgl.nanovg.NanoVGGL2.*;
27+
import static org.lwjgl.nanovg.NanoVGGL3.*;
28+
import static org.lwjgl.nanovg.NanoVGGL3.NVG_ANTIALIAS;
29+
import static org.lwjgl.nanovg.NanoVGGL3.NVG_STENCIL_STROKES;
30+
import static org.lwjgl.opengl.GL11.*;
31+
import static org.lwjgl.opengl.GL30.*;
32+
33+
/**
34+
* Example usage of FrameBuffer with GLFWCanvas and NanoVG.
35+
*/
36+
public class FBOCanvasUsingFrameBuffer extends GLFWCanvas {
37+
38+
private FrameBuffer fbo;
39+
private final int fboWidth = 200;
40+
private final int fboHeight = 200;
41+
42+
private long nvgContext;
43+
private int fboImageID;
44+
45+
private List<String> materialNames = new ArrayList<>();
46+
47+
48+
// Track rotation
49+
private long lastTime = System.currentTimeMillis();
50+
51+
public FBOCanvasUsingFrameBuffer(int width, int height, String title) {
52+
super(width, height, title);
53+
initCallbacks();
54+
}
55+
56+
public static void main(String[] args) {
57+
58+
59+
FBOCanvasUsingFrameBuffer canvas = new FBOCanvasUsingFrameBuffer(1024, 1024, "Using FrameBuffer");
60+
canvas.run();
61+
}
62+
63+
@Override
64+
public void initGL() {
65+
// Determine NanoVG version
66+
67+
int flags = NVG_STENCIL_STROKES | NVG_ANTIALIAS;
68+
nvgContext = NanoVGGL3.nvgCreate(flags);
69+
if (nvgContext == 0) {
70+
throw new RuntimeException("Failed to create NanoVG context");
71+
}
72+
73+
// Create our off-screen FrameBuffer
74+
fbo = new FrameBuffer(fboWidth, fboHeight);
75+
76+
// Tell NanoVG about the FrameBuffer’s texture so we can draw it on screen later
77+
fboImageID = NanoVGGL3.nnvglCreateImageFromHandle(nvgContext, fbo.getTextureId(),
78+
fboWidth, fboHeight, 0);
79+
80+
glEnable(GL_BLEND);
81+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
82+
}
83+
84+
@Override
85+
public void paintGL() {
86+
// Compute rotation angle
87+
long thisTime = System.currentTimeMillis();
88+
float angle = (lastTime - thisTime)/10000000f;
89+
lastTime = thisTime;
90+
91+
System.out.println(angle);
92+
93+
// 1) Render the rotating rectangle onto our FrameBuffer
94+
renderToFrameBuffer(angle);
95+
96+
// Clear main window
97+
glClearColor(0.3f, 0.3f, 0.5f, 1.0f);
98+
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
99+
100+
renderBuffer();
101+
102+
103+
}
104+
105+
private void renderBuffer() {
106+
// Paint the FBO image as a fullscreen quad using NanoVG
107+
nvgBeginFrame(nvgContext, width, height, 1f);
108+
try (NVGPaint paint = NVGPaint.calloc()) {
109+
nvgImagePattern(nvgContext, width/4, height/4, width/2, height/4, 0f, fboImageID, 1f, paint);
110+
111+
nvgBeginPath(nvgContext);
112+
nvgRect(nvgContext, width/4, height/4, width/2, height/4);
113+
nvgFillPaint(nvgContext, paint);
114+
nvgFill(nvgContext);
115+
}
116+
nvgEndFrame(nvgContext);
117+
}
118+
119+
private void renderToFrameBuffer(float angle) {
120+
// Bind off-screen FBO for drawing
121+
fbo.bindFramebuffer();
122+
glViewport(0, 0, fboWidth, fboHeight);
123+
124+
// Clear off-screen area
125+
glClearColor(0.3f, 0.5f, 0.7f, 1f);
126+
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
127+
128+
// Use NanoVG to draw rotating rectangle
129+
nvgBeginFrame(nvgContext, fboWidth, fboHeight, 1f);
130+
try (NVGColor green = NVGColor.calloc(); NVGColor black = NVGColor.calloc()) {
131+
green.r(0f).g(1f).b(0f).a(1f);
132+
black.r(0f).g(0f).b(0f).a(1f);
133+
134+
nvgTranslate(nvgContext, fboWidth / 2f, fboHeight / 2f);
135+
nvgRotate(nvgContext, angle);
136+
137+
nvgBeginPath(nvgContext);
138+
nvgRect(nvgContext, -fboWidth / 4f, -fboHeight / 4f, fboWidth / 2f, fboHeight / 2f);
139+
nvgStrokeColor(nvgContext, black);
140+
nvgStroke(nvgContext);
141+
142+
nvgBeginPath(nvgContext);
143+
nvgRect(nvgContext, -fboWidth / 4f, -fboHeight / 4f, fboWidth / 2f, fboHeight / 2f);
144+
nvgFillColor(nvgContext, green);
145+
nvgFill(nvgContext);
146+
}
147+
nvgEndFrame(nvgContext);
148+
149+
// Unbind FBO to return to default
150+
fbo.unbindFramebuffer();
151+
152+
glViewport(0, 0, width, height);
153+
}
154+
155+
@Override
156+
protected void cleanup() {
157+
super.cleanup(); // destroys window and terminates GLFW
158+
if (nvgContext != 0) {
159+
boolean isVersionNew = (glGetInteger(GL30.GL_MAJOR_VERSION) > 3)
160+
|| (glGetInteger(GL30.GL_MAJOR_VERSION) == 3 && glGetInteger(GL30.GL_MINOR_VERSION) >= 2);
161+
if (isVersionNew) {
162+
NanoVGGL3.nnvgDelete(nvgContext);
163+
} else {
164+
NanoVGGL2.nnvgDelete(nvgContext);
165+
}
166+
}
167+
if (fbo != null) {
168+
fbo.close(); // clean up GPU resources
169+
}
170+
}
171+
172+
protected void initCallbacks() {
173+
var keeper = new DefaultCallbackKeeper();
174+
CallbackKeeper.registerCallbacks(window, keeper);
175+
176+
GLFWKeyCallbackI glfwKeyCallbackI = (w, key, scancode, action, mods) -> {
177+
if (key == GLFW_KEY_ESCAPE && action != GLFW_RELEASE) {
178+
running = false;
179+
}
180+
};
181+
182+
keeper.getChainKeyCallback().add(glfwKeyCallbackI);
183+
184+
GLFWWindowCloseCallbackI glfwWindowCloseCallbackI = w -> running = false;
185+
keeper.getChainWindowCloseCallback().add(glfwWindowCloseCallbackI);
186+
}
187+
188+
@Override
189+
protected CallbackKeeper getCallBackKeeper() {
190+
return null;
191+
}
192+
}

0 commit comments

Comments
 (0)