diff --git a/modules/freeworld.client/build.gradle.kts b/modules/freeworld.client/build.gradle.kts index c46f901..eaf62f6 100644 --- a/modules/freeworld.client/build.gradle.kts +++ b/modules/freeworld.client/build.gradle.kts @@ -13,43 +13,45 @@ plugins { } val jdkEnablePreview: String by rootProject - val overrunglVersion: String by rootProject -val overrunglNatives = Pair( - System.getProperty("os.name")!!, - System.getProperty("os.arch")!! -).let { (name, arch) -> +val overrunglOs = System.getProperty("os.name")!!.let { name -> when { - arrayOf("Linux", "FreeBSD", "SunOS", "Unit").any { name.startsWith(it) } -> - if (arrayOf("arm", "aarch64").any { arch.startsWith(it) }) - "natives-linux${if (arch.contains("64") || arch.startsWith("armv8")) "-arm64" else "-arm32"}" - else "natives-linux" - - arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } -> - "natives-macos${if (arch.startsWith("aarch64")) "-arm64" else ""}" - - arrayOf("Windows").any { name.startsWith(it) } -> - if (arch.contains("64")) - "natives-windows${if (arch.startsWith("aarch64")) "-arm64" else ""}" - else throw Error("Unrecognized or unsupported architecture. Please set \"overrunglNatives\" manually") + "FreeBSD" == name -> "freebsd" + arrayOf("Linux", "SunOS", "Unit").any { name.startsWith(it) } -> "linux" + arrayOf("Mac OS X", "Darwin").any { name.startsWith(it) } -> "macos" + arrayOf("Windows").any { name.startsWith(it) } -> "windows" + else -> throw Error("Unrecognized or unsupported platform $name. Please set \"overrunglOs\" manually") + } +} +val overrunglArch = System.getProperty("os.arch")!!.let { arch -> + when (overrunglOs) { + "freebsd" -> "x64" + "linux" -> if (arrayOf("arm", "aarch64").any { arch.startsWith(it) }) { + if (arch.contains("64") || arch.startsWith("armv8")) "arm64" else "arm32" + } else if (arch.startsWith("ppc")) "ppc64le" + else if (arch.startsWith("riscv")) "riscv64" + else "x64" - else -> throw Error("Unrecognized or unsupported platform. Please set \"overrunglNatives\" manually") + "macos" -> if (arch.startsWith("aarch64")) "arm64" else "x64" + "windows" -> if (arch.contains("64") && arch.startsWith("aarch64")) "arm64" else "x64" + else -> throw Error("Unrecognized or unsupported platform $overrunglOs. Please set \"overrunglArch\" manually") } } +configurations.runtimeClasspath.get().attributes { + attribute(OperatingSystemFamily.OPERATING_SYSTEM_ATTRIBUTE, objects.named(overrunglOs)) + attribute(MachineArchitecture.ARCHITECTURE_ATTRIBUTE, objects.named(overrunglArch)) +} + dependencies { api(project(":freeworld")) implementation(platform("io.github.over-run:overrungl-bom:$overrunglVersion")) implementation("io.github.over-run:overrungl") implementation("io.github.over-run:overrungl-joml") implementation("io.github.over-run:overrungl-glfw") - runtimeOnly("io.github.over-run:overrungl-glfw::$overrunglNatives") implementation("io.github.over-run:overrungl-opengl") implementation("io.github.over-run:overrungl-stb") - runtimeOnly("io.github.over-run:overrungl-stb::$overrunglNatives") - //TODO - implementation("io.github.over-run:marshal:0.1.0-alpha.24-jdk22") } application { @@ -57,7 +59,7 @@ application { mainModule = "freeworld.client" mainClass = "freeworld.client.main.Main" applicationDefaultJvmArgs = buildList { - if (jdkEnablePreview.toBoolean())add("--enable-preview") + if (jdkEnablePreview.toBoolean()) add("--enable-preview") add( "--enable-native-access=${ listOf( diff --git a/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java b/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java index 7fc6ffd..fb6853a 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/Freeworld.java @@ -91,7 +91,7 @@ public void start() { glfw.windowHint(GLFW.CONTEXT_VERSION_MINOR, 3); // center window - final GLFWVidMode.Value videoMode = glfw.getVideoMode(glfw.getPrimaryMonitor()); + final GLFWVidMode videoMode = glfw.getVideoMode(glfw.getPrimaryMonitor()); if (videoMode != null) { glfw.windowHint(GLFW.POSITION_X, (videoMode.width() - INIT_WINDOW_WIDTH) / 2); glfw.windowHint(GLFW.POSITION_Y, (videoMode.height() - INIT_WINDOW_HEIGHT) / 2); @@ -269,6 +269,10 @@ public GLFlags glFlags() { return glFlags; } + public GLStateMgr gl() { + return gl; + } + public MemorySegment window() { return window; } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java index 7a22e38..159ad19 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/GameRenderer.java @@ -121,7 +121,7 @@ public void render(GLStateMgr gl, double partialTick) { final List chunks = worldRenderer.renderingChunks(player); - worldRenderer.compileChunks(chunks); + worldRenderer.compileChunks(player, chunks); worldRenderer.renderChunks(gl, chunks); hitResult = worldRenderer.selectBlock(player); @@ -206,6 +206,10 @@ public void close(GLStateMgr gl) { if (tessellator != null) tessellator.close(gl); } + public Freeworld client() { + return client; + } + public GLProgram positionColorProgram() { return positionColorProgram; } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/texture/NativeImage.java b/modules/freeworld.client/src/main/java/freeworld/client/render/texture/NativeImage.java index c7977c0..d842dc0 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/texture/NativeImage.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/texture/NativeImage.java @@ -35,8 +35,7 @@ public record NativeImage(int width, int height, MemorySegment segment, boolean failed) { private static final Logger logger = Logging.caller(); - public static NativeImage load(Arena arena, String path) { - final MemorySegment segment = BuiltinFiles.loadBinary(arena, BuiltinFiles.load(path), path); + public static NativeImage load(Arena arena, MemorySegment segment, String path) { if (Unmarshal.isNullPointer(segment)) { return fail(); } @@ -57,6 +56,10 @@ public static NativeImage load(Arena arena, String path) { ); } + public static NativeImage load(Arena arena, String path) { + return load(arena, BuiltinFiles.loadBinary(arena, BuiltinFiles.load(path), path), path); + } + public static NativeImage fail() { final class Holder { private static final NativeImage FAILED; diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/texture/TextureAtlas.java b/modules/freeworld.client/src/main/java/freeworld/client/render/texture/TextureAtlas.java index c55855b..24bf3aa 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/texture/TextureAtlas.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/texture/TextureAtlas.java @@ -44,9 +44,9 @@ public static TextureAtlas load(GLStateMgr gl, List identifierList) final Map imageMap = HashMap.newHashMap(numIds); identifierList.forEach(identifier -> imageMap.put(identifier, NativeImage.load(arena, identifier.toResourcePath(Identifier.ROOT_ASSETS, null, null)))); - final STBRPContext context = new STBRPContext(arena); - final STBRPNode nodes = new STBRPNode(arena, numIds); - final STBRPRect rects = new STBRPRect(arena, numIds); + final STBRPContext context = STBRPContext.OF.of(arena); + final STBRPNode nodes = STBRPNode.OF.of(arena, numIds); + final STBRPRect rects = STBRPRect.OF.of(arena, numIds); int mipmapLevel = 4; for (int i = 0; i < numIds; i++) { final NativeImage image = imageMap.get(identifierList.get(i)); @@ -57,9 +57,9 @@ public static TextureAtlas load(GLStateMgr gl, List identifierList) } else if (mipmapLevel > 0) { mipmapLevel = Math.min(Integer.numberOfTrailingZeros(width), Integer.numberOfTrailingZeros(height)); } - STBRPRect.id.set(rects, i, i); - STBRPRect.w.set(rects, i, width); - STBRPRect.h.set(rects, i, height); + rects.slice(i).id(i) + .w(width) + .h(height); } int packerSize = 256; @@ -86,12 +86,13 @@ public static TextureAtlas load(GLStateMgr gl, List identifierList) GL10C.UNSIGNED_BYTE, MemorySegment.NULL); for (int i = 0; i < numIds; i++) { - if (STBRPRect.wasPacked.get(rects, i) != 0) { - final Identifier identifier = identifierList.get(STBRPRect.id.get(rects, i)); - final int xo = STBRPRect.x.get(rects, i); - final int yo = STBRPRect.y.get(rects, i); - final int width = STBRPRect.w.get(rects, i); - final int height = STBRPRect.h.get(rects, i); + final STBRPRect slice = rects.slice(i); + if (slice.was_packed() != 0) { + final Identifier identifier = identifierList.get(slice.id()); + final int xo = slice.x(); + final int yo = slice.y(); + final int width = slice.w(); + final int height = slice.h(); regionMap.put(identifier, new TextureRegion(xo, yo, width, height)); gl.texSubImage2D(GL10C.TEXTURE_2D, 0, diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompileTask.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompileTask.java index 15154b9..e696c04 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompileTask.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompileTask.java @@ -10,86 +10,42 @@ package freeworld.client.render.world; -import freeworld.client.render.GameRenderer; -import freeworld.client.render.builder.DefaultVertexBuilder; -import freeworld.client.world.chunk.ClientChunk; -import freeworld.util.Direction; -import freeworld.world.chunk.Chunk; -import freeworld.world.chunk.ChunkPos; +import freeworld.world.entity.Entity; +import freeworld.world.entity.component.PositionComponent; +import org.jetbrains.annotations.NotNull; +import org.joml.Vector3d; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; /** * @author squid233 * @since 0.1.0 */ -public final class ChunkCompileTask implements Callable { - private final GameRenderer gameRenderer; - private final WorldRenderer worldRenderer; - private final ClientChunk chunk; +public final class ChunkCompileTask extends FutureTask implements Comparable { + private final Entity player; + private final int x; + private final int y; + private final int z; - public ChunkCompileTask(GameRenderer gameRenderer, WorldRenderer worldRenderer, ClientChunk chunk) { - this.gameRenderer = gameRenderer; - this.worldRenderer = worldRenderer; - this.chunk = chunk; + public ChunkCompileTask(@NotNull Callable callable, Entity player, int x, int y, int z) { + super(callable); + this.player = player; + this.x = x; + this.y = y; + this.z = z; } @Override - public ChunkVertexData call() throws Exception { - final var pool = worldRenderer.vertexBuilderPool(); - final DefaultVertexBuilder builder = pool.borrowObject(); - try { - builder.reset(); - final int cx = chunk.x(); - final int cy = chunk.y(); - final int cz = chunk.z(); - for (Direction direction : Direction.LIST) { - for (int x = 0; x < Chunk.SIZE; x++) { - for (int y = 0; y < Chunk.SIZE; y++) { - for (int z = 0; z < Chunk.SIZE; z++) { - final int nx = x + direction.axisX(); - final int ny = y + direction.axisY(); - final int nz = z + direction.axisZ(); - final int absNx = ChunkPos.relativeToAbsolute(cx, nx); - final int absNy = ChunkPos.relativeToAbsolute(cy, ny); - final int absNz = ChunkPos.relativeToAbsolute(cz, nz); - if ((chunk.isInBound(nx, ny, nz) && - chunk.getBlockType(nx, ny, nz).air()) || - (chunk.world().isBlockLoaded(absNx, absNy, absNz) && - chunk.world().getBlockType(absNx, absNy, absNz).air())) { - gameRenderer.blockRenderer().renderBlockFace( - builder, - chunk.getBlockType(x, y, z), - ChunkPos.relativeToAbsolute(cx, x), - ChunkPos.relativeToAbsolute(cy, y), - ChunkPos.relativeToAbsolute(cz, z), - direction - ); - } - } - } - } - } - - final Arena arena = Arena.ofAuto(); - final MemorySegment vertexDataSlice = builder.vertexDataSlice(); - final MemorySegment indexDataSlice = builder.indexDataSlice(); - final ChunkVertexData data = new ChunkVertexData( - builder.vertexLayout(), - builder.indexCount(), - arena.allocateFrom(ValueLayout.JAVA_BYTE, vertexDataSlice, ValueLayout.JAVA_BYTE, 0L, vertexDataSlice.byteSize()), - arena.allocateFrom(ValueLayout.JAVA_BYTE, indexDataSlice, ValueLayout.JAVA_BYTE, 0L, indexDataSlice.byteSize()), - builder.shouldReallocateVertexData(), - builder.shouldReallocateIndexData() - ); - pool.returnObject(builder); - return data; - } catch (Exception e) { - pool.invalidateObject(builder); - throw e; + public int compareTo(@NotNull ChunkCompileTask o) { + if (player.hasComponent(PositionComponent.ID)) { + return Double.compare(distanceSquared(), o.distanceSquared()); } + return 0; + } + + private double distanceSquared() { + final Vector3d value = player.position().value(); + return value.distanceSquared(x, y, z); } } diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java new file mode 100644 index 0000000..4b49b38 --- /dev/null +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/ChunkCompiler.java @@ -0,0 +1,94 @@ +/* + * freeworld - 3D sandbox game + * Copyright (C) 2024 XenFork Union + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + */ + +package freeworld.client.render.world; + +import freeworld.client.render.GameRenderer; +import freeworld.client.render.builder.DefaultVertexBuilder; +import freeworld.client.world.chunk.ClientChunk; +import freeworld.util.Direction; +import freeworld.world.chunk.Chunk; +import freeworld.world.chunk.ChunkPos; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.concurrent.Callable; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class ChunkCompiler implements Callable { + private final GameRenderer gameRenderer; + private final WorldRenderer worldRenderer; + private final ClientChunk chunk; + + public ChunkCompiler(GameRenderer gameRenderer, WorldRenderer worldRenderer, ClientChunk chunk) { + this.gameRenderer = gameRenderer; + this.worldRenderer = worldRenderer; + this.chunk = chunk; + } + + @Override + public ChunkVertexData call() throws Exception { + final var pool = worldRenderer.vertexBuilderPool(); + final DefaultVertexBuilder builder = pool.borrowObject(); + try { + final int cx = chunk.x(); + final int cy = chunk.y(); + final int cz = chunk.z(); + for (Direction direction : Direction.LIST) { + for (int x = 0; x < Chunk.SIZE; x++) { + for (int y = 0; y < Chunk.SIZE; y++) { + for (int z = 0; z < Chunk.SIZE; z++) { + final int nx = x + direction.axisX(); + final int ny = y + direction.axisY(); + final int nz = z + direction.axisZ(); + final int absNx = ChunkPos.relativeToAbsolute(cx, nx); + final int absNy = ChunkPos.relativeToAbsolute(cy, ny); + final int absNz = ChunkPos.relativeToAbsolute(cz, nz); + if ((chunk.isInBound(nx, ny, nz) && + chunk.getBlockType(nx, ny, nz).air()) || + (chunk.world().isBlockLoaded(absNx, absNy, absNz) && + chunk.world().getBlockType(absNx, absNy, absNz).air())) { + gameRenderer.blockRenderer().renderBlockFace( + builder, + chunk.getBlockType(x, y, z), + ChunkPos.relativeToAbsolute(cx, x), + ChunkPos.relativeToAbsolute(cy, y), + ChunkPos.relativeToAbsolute(cz, z), + direction + ); + } + } + } + } + } + + final Arena arena = Arena.ofAuto(); + final MemorySegment vertexDataSlice = builder.vertexDataSlice(); + final MemorySegment indexDataSlice = builder.indexDataSlice(); + final ChunkVertexData data = new ChunkVertexData( + builder.vertexLayout(), + builder.indexCount(), + arena.allocateFrom(ValueLayout.JAVA_BYTE, vertexDataSlice, ValueLayout.JAVA_BYTE, 0L, vertexDataSlice.byteSize()), + arena.allocateFrom(ValueLayout.JAVA_BYTE, indexDataSlice, ValueLayout.JAVA_BYTE, 0L, indexDataSlice.byteSize()), + builder.shouldReallocateVertexData(), + builder.shouldReallocateIndexData() + ); + pool.returnObject(builder); + return data; + } catch (Exception e) { + pool.invalidateObject(builder); + throw e; + } + } +} diff --git a/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java b/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java index b589256..80ece64 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/render/world/WorldRenderer.java @@ -33,10 +33,7 @@ import java.lang.Math; import java.lang.Runtime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -67,7 +64,7 @@ public PooledObject wrap(DefaultVertexBuilder obj) { return new DefaultPooledObject<>(obj); } }); - private final Map chunks = HashMap.newHashMap(RENDER_CHUNK_COUNT); + private final Map chunks = WeakHashMap.newWeakHashMap(RENDER_CHUNK_COUNT); private final FrustumIntersection frustumIntersection = new FrustumIntersection(); private final FrustumRayBuilder frustumRayBuilder = new FrustumRayBuilder(); private final Vector3f frustumRayOrigin = new Vector3f(); @@ -79,12 +76,13 @@ public WorldRenderer(GameRenderer gameRenderer, World world) { this.world = world; world.addListener(this); + final int processors = Runtime.getRuntime().availableProcessors(); this.executor = new ThreadPoolExecutor(processors, processors, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingDeque<>(), + new PriorityBlockingQueue<>(RENDER_CHUNK_COUNT), new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @@ -120,7 +118,7 @@ public List renderingChunks(Entity player) { return chunks; } - public void compileChunks(List renderingChunks) { + public void compileChunks(Entity player, List renderingChunks) { for (ClientChunk chunk : renderingChunks) { if (chunk.dirty) { if (chunk.future != null && chunk.future.state() == Future.State.RUNNING) { @@ -130,7 +128,9 @@ public void compileChunks(List renderingChunks) { if (chunk1 != null) { chunk.copyFrom(chunk1); } - chunk.future = executor.submit(new ChunkCompileTask(gameRenderer, this, chunk)); + final ChunkCompileTask task = new ChunkCompileTask(new ChunkCompiler(gameRenderer, this, chunk), player, chunk.x(), chunk.y(), chunk.z()); + chunk.future = task; + executor.execute(task); chunk.dirty = false; } } @@ -406,7 +406,7 @@ private ClientChunk getChunk(int x, int y, int z) { private ClientChunk getChunkOrCreate(int x, int y, int z) { return chunks.computeIfAbsent(new ChunkPos(x, y, z), - chunkPos -> new ClientChunk(world, chunkPos.x(), chunkPos.y(), chunkPos.z())); + chunkPos -> new ClientChunk(world, this, chunkPos.x(), chunkPos.y(), chunkPos.z())); } private ClientChunk getChunkByAbsolutePos(int x, int y, int z) { @@ -421,6 +421,10 @@ public GenericObjectPool vertexBuilderPool() { return vertexBuilderPool; } + public GameRenderer gameRenderer() { + return gameRenderer; + } + @Override public void close(GLStateMgr gl) { executor.close(); diff --git a/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java b/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java index 2bd3a4c..414a851 100644 --- a/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java +++ b/modules/freeworld.client/src/main/java/freeworld/client/world/chunk/ClientChunk.java @@ -10,15 +10,17 @@ package freeworld.client.world.chunk; -import freeworld.client.render.gl.GLStateMgr; import freeworld.client.render.gl.GLResource; +import freeworld.client.render.gl.GLStateMgr; import freeworld.client.render.model.VertexLayout; import freeworld.client.render.world.ChunkVertexData; +import freeworld.client.render.world.WorldRenderer; import freeworld.world.World; import freeworld.world.chunk.Chunk; import overrungl.opengl.GL15C; import java.lang.foreign.MemorySegment; +import java.lang.ref.Cleaner; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -27,32 +29,63 @@ * @since 0.1.0 */ public final class ClientChunk extends Chunk implements GLResource { + private static final Cleaner CLEANER = Cleaner.create(); + private final Cleaner.Cleanable cleanable; + private final State state; public Future future = null; /** * Is this chunk changed? */ public boolean dirty = true; private int indexCount = 0; - private int vao = 0; - private int vbo = 0; - private int ebo = 0; + private boolean allAir = false; - public ClientChunk(World world, int x, int y, int z) { + public ClientChunk(World world, WorldRenderer worldRenderer, int x, int y, int z) { super(world, x, y, z); + // Get OpenGL context directly + this.state = new State(worldRenderer.gameRenderer().client().gl()); + this.cleanable = CLEANER.register(this, state); + } + + private static final class State implements Runnable { + private final GLStateMgr gl; + private int vao = 0; + private int vbo = 0; + private int ebo = 0; + + private State(GLStateMgr gl) { + this.gl = gl; + } + + @Override + public void run() { + gl.deleteVertexArrays(vao); + gl.deleteBuffers(vbo, ebo); + } } public void render(GLStateMgr gl) { try { if (future != null && future.state() == Future.State.SUCCESS) { final ChunkVertexData data = future.get(); + + indexCount = data.indexCount(); + if (indexCount == 0) { + future = null; + allAir = true; + return; + } else { + allAir = false; + } + final MemorySegment vertexData = data.vertexData(); final MemorySegment indexData = data.indexData(); - indexCount = data.indexCount(); - if (vao == 0) vao = gl.genVertexArrays(); - if (vbo == 0) vbo = gl.genBuffers(); - if (ebo == 0) ebo = gl.genBuffers(); - gl.setVertexArrayBinding(vao); - gl.setArrayBufferBinding(vbo); + + if (state.vao == 0) state.vao = gl.genVertexArrays(); + if (state.vbo == 0) state.vbo = gl.genBuffers(); + if (state.ebo == 0) state.ebo = gl.genBuffers(); + gl.setVertexArrayBinding(state.vao); + gl.setArrayBufferBinding(state.vbo); if (data.shouldReallocateVertexData()) { gl.bufferData(GL15C.ARRAY_BUFFER, vertexData, GL15C.DYNAMIC_DRAW); final VertexLayout layout = data.vertexLayout(); @@ -61,7 +94,7 @@ public void render(GLStateMgr gl) { } else { gl.bufferSubData(GL15C.ARRAY_BUFFER, 0L, vertexData); } - gl.bindBuffer(GL15C.ELEMENT_ARRAY_BUFFER, ebo); + gl.bindBuffer(GL15C.ELEMENT_ARRAY_BUFFER, state.ebo); if (data.shouldReallocateIndexData()) { gl.bufferData(GL15C.ELEMENT_ARRAY_BUFFER, indexData, GL15C.DYNAMIC_DRAW); } else { @@ -72,8 +105,8 @@ public void render(GLStateMgr gl) { } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } - if (vao != 0) { - gl.setVertexArrayBinding(vao); + if (state.vao != 0 && !allAir) { + gl.setVertexArrayBinding(state.vao); gl.drawElements(GLStateMgr.TRIANGLES, indexCount, GLStateMgr.UNSIGNED_INT, MemorySegment.NULL); } } @@ -86,7 +119,6 @@ public void markDirty() { @Override public void close(GLStateMgr gl) { - gl.deleteVertexArrays(vao); - gl.deleteBuffers(vbo, ebo); + cleanable.clean(); } } diff --git a/modules/freeworld.core/src/main/java/freeworld/file/BuiltinFiles.java b/modules/freeworld.core/src/main/java/freeworld/file/BuiltinFiles.java index d7e5b7c..43c94ca 100644 --- a/modules/freeworld.core/src/main/java/freeworld/file/BuiltinFiles.java +++ b/modules/freeworld.core/src/main/java/freeworld/file/BuiltinFiles.java @@ -18,6 +18,7 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; import java.lang.foreign.ValueLayout; +import java.net.URL; import java.util.stream.Collectors; /** @@ -28,11 +29,20 @@ */ public final class BuiltinFiles { private static final Logger logger = Logging.caller(); - private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); private BuiltinFiles() { } + @Nullable + public static URL loadURL(ClassLoader classLoader, String name) { + return classLoader.getResource(name); + } + + @Nullable + public static URL loadURL(String name) { + return loadURL(BuiltinFiles.class.getClassLoader(), name); + } + @Nullable public static InputStream load(ClassLoader classLoader, String name) { return classLoader.getResourceAsStream(name); @@ -40,7 +50,7 @@ public static InputStream load(ClassLoader classLoader, String name) { @Nullable public static InputStream load(String name) { - return load(STACK_WALKER.getCallerClass().getClassLoader(), name); + return load(BuiltinFiles.class.getClassLoader(), name); } public static MemorySegment loadBinary(SegmentAllocator allocator, InputStream stream, String name) {