diff --git a/gradle.properties b/gradle.properties index 95c63dd..1a713b1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,7 +41,7 @@ jdkEnablePreview=true # javadoc link of JDK early access build # https://download.java.net/java/early_access/$jdkEarlyAccessDoc/docs/api/ # Uncomment it if you need to use EA build of JDK. -jdkEarlyAccessDoc=jdk22 +#jdkEarlyAccessDoc=jdk22 projVersion=0.1.0-SNAPSHOT coreVersion=0.1.0-SNAPSHOT diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java index f814179..1eedb22 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/GameRenderer.java @@ -69,7 +69,7 @@ public void init(GLStateMgr gl) { logger.info("Created {}x{}x{} {}", texture.width(), texture.height(), texture.mipmapLevel(), TextureManager.BLOCK_ATLAS); blockRenderer = new BlockRenderer(this); - worldRenderer = new WorldRenderer(client, this, client.world()); + worldRenderer = new WorldRenderer(this, client.world()); tessellator = new Tessellator(); } @@ -111,7 +111,8 @@ public void render(GLStateMgr gl, double partialTick) { positionColorTexProgram.getUniform(GLProgram.UNIFORM_PROJECTION_VIEW_MATRIX).set(projectionView); positionColorTexProgram.getUniform(GLProgram.UNIFORM_MODEL_MATRIX).set(matrix); positionColorTexProgram.uploadUniforms(gl); - worldRenderer.render(gl, tessellator); + worldRenderer.compileChunks(); + worldRenderer.renderChunks(gl); renderGui(gl, partialTick); } @@ -151,6 +152,7 @@ public void close(GLStateMgr gl) { logger.info("Closing game renderer"); if (textureManager != null) textureManager.close(gl); + if (worldRenderer != null) worldRenderer.close(gl); if (positionColorProgram != null) positionColorProgram.close(gl); if (positionColorTexProgram != null) positionColorTexProgram.close(gl); diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/builder/DefaultVertexBuilder.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/builder/DefaultVertexBuilder.java index 9cfdc24..080fc60 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/builder/DefaultVertexBuilder.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/builder/DefaultVertexBuilder.java @@ -221,4 +221,8 @@ public boolean shouldReallocateVertexData() { public boolean shouldReallocateIndexData() { return shouldReallocateIndexData; } + + public VertexLayout vertexLayout() { + return vertexLayout; + } } diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLDrawMode.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLDrawMode.java index 2030456..c37cd18 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLDrawMode.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/gl/GLDrawMode.java @@ -10,14 +10,12 @@ package io.github.xenfork.freeworld.client.render.gl; -import overrungl.opengl.GL; - /** * @author squid233 * @since 0.1.0 */ public enum GLDrawMode { - TRIANGLES(GL.TRIANGLES, 3); + TRIANGLES(GLStateMgr.TRIANGLES, 3); private final int value; private final int count; diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/ChunkCompileTask.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/ChunkCompileTask.java new file mode 100644 index 0000000..28a326d --- /dev/null +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/ChunkCompileTask.java @@ -0,0 +1,84 @@ +/* + * 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 io.github.xenfork.freeworld.client.render.world; + +import io.github.xenfork.freeworld.client.render.GameRenderer; +import io.github.xenfork.freeworld.client.render.builder.DefaultVertexBuilder; +import io.github.xenfork.freeworld.util.Direction; +import io.github.xenfork.freeworld.world.chunk.Chunk; +import io.github.xenfork.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 ChunkCompileTask implements Callable { + private final GameRenderer gameRenderer; + private final WorldRenderer worldRenderer; + private final Chunk chunk; + + public ChunkCompileTask(GameRenderer gameRenderer, WorldRenderer worldRenderer, Chunk chunk) { + this.gameRenderer = gameRenderer; + this.worldRenderer = worldRenderer; + this.chunk = chunk; + } + + @Override + public ChunkVertexData call() { + final var pool = worldRenderer.vertexBuilderPool(); + final DefaultVertexBuilder builder = pool.acquire(); + 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(); + if (chunk.isInBound(nx, ny, nz) && chunk.getBlockType(nx, ny, nz).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.ofShared(); + final MemorySegment vertexDataSlice = builder.vertexDataSlice(); + final MemorySegment indexDataSlice = builder.indexDataSlice(); + return new ChunkVertexData( + builder.vertexLayout(), + builder.indexCount(), + arena, + 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() + ); + } finally { + pool.release(builder); + } + } +} diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/ChunkVertexData.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/ChunkVertexData.java new file mode 100644 index 0000000..f3b4672 --- /dev/null +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/ChunkVertexData.java @@ -0,0 +1,31 @@ +/* + * 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 io.github.xenfork.freeworld.client.render.world; + +import io.github.xenfork.freeworld.client.render.model.VertexLayout; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +/** + * @author squid233 + * @since 0.1.0 + */ +public record ChunkVertexData( + VertexLayout vertexLayout, + int indexCount, + Arena arena, + MemorySegment vertexData, + MemorySegment indexData, + boolean shouldReallocateVertexData, + boolean shouldReallocateIndexData +) { +} diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/VertexBuilderPool.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/VertexBuilderPool.java new file mode 100644 index 0000000..85158fb --- /dev/null +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/VertexBuilderPool.java @@ -0,0 +1,67 @@ +/* + * 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 io.github.xenfork.freeworld.client.render.world; + +import io.github.xenfork.freeworld.client.render.builder.VertexBuilder; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class VertexBuilderPool { + private final Map> map = new ConcurrentHashMap<>(); + private final Supplier factory; + + public VertexBuilderPool(Supplier factory) { + this.factory = factory; + } + + private static final class Pair { + final T builder; + final AtomicBoolean acquired; + + Pair(T builder, boolean acquired) { + this.builder = builder; + this.acquired = new AtomicBoolean(acquired); + } + } + + public T acquire() { + for (var entry : map.entrySet()) { + final Pair value = entry.getValue(); + if (!value.acquired.get()) { + value.acquired.set(true); + return value.builder; + } + } + final T t = factory.get(); + final int hashCode = System.identityHashCode(t); + map.put(hashCode, new Pair<>(t, true)); + return t; + } + + public void release(T t) { + Objects.requireNonNull(t); + final int hashCode = System.identityHashCode(t); + if (map.containsKey(hashCode)) { + final Pair pair = map.get(hashCode); + if (pair.acquired.get()) { + pair.acquired.set(false); + } + } + } +} diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java index 16005d0..e985e79 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/render/world/WorldRenderer.java @@ -10,33 +10,30 @@ package io.github.xenfork.freeworld.client.render.world; -import io.github.xenfork.freeworld.client.Freeworld; import io.github.xenfork.freeworld.client.render.GameRenderer; -import io.github.xenfork.freeworld.client.render.Tessellator; -import io.github.xenfork.freeworld.client.render.gl.GLDrawMode; +import io.github.xenfork.freeworld.client.render.builder.DefaultVertexBuilder; +import io.github.xenfork.freeworld.client.render.gl.GLResource; import io.github.xenfork.freeworld.client.render.gl.GLStateMgr; -import io.github.xenfork.freeworld.util.Direction; +import io.github.xenfork.freeworld.client.render.model.VertexLayouts; +import io.github.xenfork.freeworld.client.world.chunk.ClientChunk; import io.github.xenfork.freeworld.world.World; -import io.github.xenfork.freeworld.world.chunk.Chunk; -import io.github.xenfork.freeworld.world.chunk.ChunkPos; +import org.jetbrains.annotations.NotNull; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; /** * @author squid233 * @since 0.1.0 */ -public final class WorldRenderer implements AutoCloseable { - private final Freeworld client; +public final class WorldRenderer implements GLResource { private final GameRenderer gameRenderer; private final World world; private final ExecutorService executor; + private final VertexBuilderPool vertexBuilderPool = new VertexBuilderPool<>(() -> new DefaultVertexBuilder(VertexLayouts.POSITION_COLOR_TEX, 30000, 60000)); + private final ClientChunk[] chunks; - public WorldRenderer(Freeworld client, GameRenderer gameRenderer, World world) { - this.client = client; + public WorldRenderer(GameRenderer gameRenderer, World world) { this.gameRenderer = gameRenderer; this.world = world; final int processors = Runtime.getRuntime().availableProcessors(); @@ -44,47 +41,50 @@ public WorldRenderer(Freeworld client, GameRenderer gameRenderer, World world) { processors, 0L, TimeUnit.MILLISECONDS, - new PriorityBlockingQueue<>(), + new LinkedBlockingDeque<>(), + new ThreadFactory() { + private final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(@NotNull Runnable r) { + return new Thread(r, STR."ChunkCompiler-thread-\{threadNumber.getAndIncrement()}"); + } + }, new ThreadPoolExecutor.DiscardPolicy()); + this.chunks = new ClientChunk[world.xChunks * world.yChunks * world.zChunks]; + for (int x = 0; x < world.xChunks; x++) { + for (int y = 0; y < world.yChunks; y++) { + for (int z = 0; z < world.zChunks; z++) { + this.chunks[(y * world.zChunks + z) * world.xChunks + x] = new ClientChunk(world, x, y, z); + } + } + } } public void compileChunks() { + for (ClientChunk chunk : chunks) { + if (chunk.shouldRecompile.get() && !chunk.submitted.get()) { + chunk.future.set(executor.submit(new ChunkCompileTask(gameRenderer, this, world.getChunk(chunk.x(), chunk.y(), chunk.z())))); + chunk.submitted.set(true); + } + } } - public void render(GLStateMgr gl, Tessellator tessellator) { -// final Vector3dc position = client.camera().position(); - final BlockRenderer blockRenderer = gameRenderer.blockRenderer(); - tessellator.begin(GLDrawMode.TRIANGLES); - for (Chunk chunk : world.chunks) { - 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(); - if (chunk.isInBound(nx, ny, nz) && chunk.getBlockType(nx, ny, nz).air()) { - blockRenderer.renderBlockFace( - tessellator, - chunk.getBlockType(x, y, z), - ChunkPos.relativeToAbsolute(cx, x), - ChunkPos.relativeToAbsolute(cy, y), - ChunkPos.relativeToAbsolute(cz, z), - direction); - } - } - } - } - } + public void renderChunks(GLStateMgr gl) { + for (ClientChunk chunk : chunks) { + chunk.render(gl); } - tessellator.end(gl); + } + + public VertexBuilderPool vertexBuilderPool() { + return vertexBuilderPool; } @Override - public void close() { + public void close(GLStateMgr gl) { executor.close(); + for (ClientChunk chunk : chunks) { + chunk.close(gl); + } } } diff --git a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/world/chunk/ClientChunk.java b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/world/chunk/ClientChunk.java index 9ce4ec1..5080721 100644 --- a/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/world/chunk/ClientChunk.java +++ b/modules/io.github.xenfork.freeworld.client/src/main/java/io/github/xenfork/freeworld/client/world/chunk/ClientChunk.java @@ -10,19 +10,82 @@ package io.github.xenfork.freeworld.client.world.chunk; +import io.github.xenfork.freeworld.client.render.gl.GLResource; +import io.github.xenfork.freeworld.client.render.gl.GLStateMgr; +import io.github.xenfork.freeworld.client.render.model.VertexLayout; +import io.github.xenfork.freeworld.client.render.world.ChunkVertexData; import io.github.xenfork.freeworld.world.World; import io.github.xenfork.freeworld.world.chunk.Chunk; +import overrungl.opengl.GL15C; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; /** * @author squid233 * @since 0.1.0 */ -public final class ClientChunk extends Chunk implements AutoCloseable { +public final class ClientChunk extends Chunk implements GLResource { + public final AtomicReference> future = new AtomicReference<>(); + public final AtomicBoolean submitted = new AtomicBoolean(); + public final AtomicBoolean shouldRecompile = new AtomicBoolean(true); + private int indexCount = 0; + private int vao = 0; + private int vbo = 0; + private int ebo = 0; + public ClientChunk(World world, int x, int y, int z) { super(world, x, y, z); } + public void render(GLStateMgr gl) { + if (shouldRecompile.get() && submitted.get()) { + try { + final ChunkVertexData data = future.get().get(); + try (Arena _ = data.arena()) { + 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 (data.shouldReallocateVertexData()) { + gl.bufferData(GL15C.ARRAY_BUFFER, vertexData, GL15C.DYNAMIC_DRAW); + final VertexLayout layout = data.vertexLayout(); + layout.enableAttribs(gl); + layout.specifyAttribPointers(gl); + } else { + gl.bufferSubData(GL15C.ARRAY_BUFFER, 0L, vertexData); + } + gl.bindBuffer(GL15C.ELEMENT_ARRAY_BUFFER, ebo); + if (data.shouldReallocateIndexData()) { + gl.bufferData(GL15C.ELEMENT_ARRAY_BUFFER, indexData, GL15C.DYNAMIC_DRAW); + } else { + gl.bufferSubData(GL15C.ELEMENT_ARRAY_BUFFER, 0L, indexData); + } + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + shouldRecompile.set(false); + submitted.set(false); + future.set(null); + } + if (vao != 0) { + gl.setVertexArrayBinding(vao); + gl.drawElements(GLStateMgr.TRIANGLES, indexCount, GLStateMgr.UNSIGNED_INT, MemorySegment.NULL); + } + } + @Override - public void close() { + public void close(GLStateMgr gl) { + gl.deleteVertexArrays(vao); + gl.deleteBuffers(vbo, ebo); } } diff --git a/modules/io.github.xenfork.freeworld.core/src/main/java/io/github/xenfork/freeworld/world/World.java b/modules/io.github.xenfork.freeworld.core/src/main/java/io/github/xenfork/freeworld/world/World.java index ba711c7..673c756 100644 --- a/modules/io.github.xenfork.freeworld.core/src/main/java/io/github/xenfork/freeworld/world/World.java +++ b/modules/io.github.xenfork.freeworld.core/src/main/java/io/github/xenfork/freeworld/world/World.java @@ -28,12 +28,12 @@ public final class World { private final int width = 64; private final int height = 64; private final int depth = 64; - private final int xChunks = width / Chunk.SIZE; - private final int yChunks = height / Chunk.SIZE; - private final int zChunks = depth / Chunk.SIZE; + public final int xChunks = width / Chunk.SIZE; + public final int yChunks = height / Chunk.SIZE; + public final int zChunks = depth / Chunk.SIZE; public final Chunk[] chunks = new Chunk[xChunks * yChunks * zChunks]; public final List entities = new ArrayList<>(); - public final MotionSystem motionSystem = new MotionSystem(); + private final MotionSystem motionSystem = new MotionSystem(); public World(String name) { for (int x = 0; x < xChunks; x++) { @@ -60,6 +60,10 @@ public Entity createEntity(EntityType type, double x, double y, double z) { return entity; } + public Chunk getChunk(int x, int y, int z) { + return chunks[(y * zChunks + z) * xChunks + x]; + } + public Chunk createChunk(int x, int y, int z) { return new Chunk(this, x, y, z); }