88 Commits

Author SHA1 Message Date
mcrcortex
5c19a7f6d0 remove qualifiers 2025-09-06 08:53:49 +10:00
mcrcortex
aff34fb463 e 2025-07-18 13:12:56 +10:00
mcrcortex
94f2d03f90 notes 2025-07-15 13:38:40 +10:00
mcrcortex
45e2ea897f add textures 2025-07-15 12:10:33 +10:00
mcrcortex
2023ac6ad2 thanks lwjgl 2025-07-15 01:10:34 +10:00
mcrcortex
91b5710e00 Change structs to expected 2025-07-15 01:06:36 +10:00
mcrcortex
03b425eeb8 change to structs 2025-07-15 00:13:56 +10:00
mcrcortex
653dbc8a3a fix backface meshext 2025-07-14 19:44:41 +10:00
mcrcortex
97058f24b4 fix backface culling 2025-07-14 19:43:34 +10:00
mcrcortex
e227d84306 comment out iris 2025-07-14 17:26:26 +10:00
mcrcortex
f4e0dbb001 use 2025-07-14 16:53:48 +10:00
mcrcortex
43d04febd5 ext mesh 2025-07-14 16:53:33 +10:00
mcrcortex
1078507495 improvements and started frag impl (missing uv) 2025-07-14 16:01:28 +10:00
mcrcortex
294e1f9fb6 Demo mesh 2025-07-14 12:55:46 +10:00
mcrcortex
5fe5ebc0a2 Log 2025-07-13 21:37:25 +10:00
mcrcortex
aff30961bd tweek section meta 2025-07-13 18:13:15 +10:00
mcrcortex
64d211b333 use normal hiz 2025-07-13 17:40:23 +10:00
mcrcortex
606d3b2282 hiz2 2025-07-13 17:39:48 +10:00
mcrcortex
132c6aa2e8 thing 2025-07-12 18:48:25 +10:00
mcrcortex
4a140c110f Add more types 2025-07-12 14:14:53 +10:00
mcrcortex
1c8d052544 add todo and optimized imports 2025-07-12 14:13:16 +10:00
mcrcortex
3199b77ae5 Reorder operations in attempt to fix race conditions 2025-07-12 14:12:06 +10:00
mcrcortex
f0e1f18379 mark as compatible with both 1.21.7 and 1.21.6 2025-07-07 22:26:36 +10:00
mcrcortex
492e2a707a Fix compatibility when joml.fastmath is enabled, fixes physics mod causing everything to detonate
Version bump
2025-07-07 22:20:46 +10:00
mcrcortex
7551ca3484 readd override thing 2025-07-07 12:23:09 +10:00
mcrcortex
8f3fa2e7f2 start on no subgroup impl 2025-07-06 20:01:44 +10:00
mcrcortex
936619ce12 no abstract 2025-07-06 17:28:51 +10:00
mcrcortex
d6a42f8ef3 c 2025-07-03 11:06:15 +10:00
mcrcortex
bf43e405ff b 2025-07-03 00:49:42 +10:00
mcrcortex
0c7c33304d Attempt to fix login unable to get object 2025-07-03 00:31:55 +10:00
mcrcortex
f9b1d8a9e1 Check render type before baking model 2025-07-02 18:53:45 +10:00
mcrcortex
7b4fe4bd5c thing 2025-07-02 11:49:13 +10:00
mcrcortex
6ba3111ada fix default biomes on no biome section data biome 2025-07-02 00:03:14 +10:00
mcrcortex
258ccf89e0 move init 2025-07-01 10:33:47 +10:00
mcrcortex
3e193bb675 update 1.21.7 2025-07-01 09:04:31 +10:00
mcrcortex
69b96eee96 L 2025-06-30 19:23:07 +10:00
mcrcortex
e1ba2c4ebb make null before shutdown 2025-06-30 14:20:42 +10:00
mcrcortex
dfce9dae46 Logging and checking 2025-06-30 13:53:03 +10:00
mcrcortex
f4fca865bb add git ignore 2025-06-30 11:23:31 +10:00
mcrcortex
08fa0725d3 Beans 2025-06-30 11:21:43 +10:00
mcrcortex
b92b769f7b Attempt to fix weirdness on thread change while importing 2025-06-30 11:21:01 +10:00
mcrcortex
726517a8b6 Move to global cleaner + enable tracking for all but memory buffers 2025-06-27 22:35:52 +10:00
mcrcortex
51f54c6edd version bump 2025-06-27 20:14:44 +10:00
mcrcortex
f7f260777a changes to ingest 2025-06-26 11:58:40 +10:00
mcrcortex
dd9ac2819d version bump 2025-06-25 00:28:16 +10:00
mcrcortex
1a7bb8498e fast path on not windows 2025-06-25 00:17:26 +10:00
mcrcortex
fb2d26153d attempt ultimit jank to fix shader 2025-06-24 23:44:15 +10:00
mcrcortex
a640c0e62c Add geometry override 2025-06-24 20:14:22 +10:00
mcrcortex
784322db6f Added amd shader compiler driver segfault workaround 2025-06-24 20:01:45 +10:00
mcrcortex
355a63c46f Final attempt at fixing ingest lighting 2025-06-24 00:12:38 +10:00
mcrcortex
155eb75b82 Attempt fix tracking 2025-06-24 00:03:11 +10:00
mcrcortex
64d4ef0c03 attempt improve lighting thing 2025-06-23 23:22:48 +10:00
mcrcortex
edb15db8fa add classifier if not in gha 2025-06-23 22:29:06 +10:00
mcrcortex
883f140b41 Allow enabling debug flags 2025-06-23 22:26:53 +10:00
mcrcortex
90a6765e8a Move chunk ingest into client chunk manager 2025-06-23 22:22:40 +10:00
mcrcortex
b8ede978c2 fix very theoretical incorrect ordering issue 2025-06-23 21:30:43 +10:00
mcrcortex
c1091acc6b service name logging on error 2025-06-23 20:53:30 +10:00
mcrcortex
0034940082 manual artifact workflow 2025-06-23 20:30:24 +10:00
mcrcortex
4d35fad772 Try to get the chunk at all costs 2025-06-23 20:21:18 +10:00
mcrcortex
d86c3b2eb8 Decrease cache size if max memory is small 2025-06-23 19:39:18 +10:00
mcrcortex
b3556813a9 bvec3 2025-06-23 11:21:57 +10:00
mcrcortex
a94dcf1949 Fix mesa 2025-06-23 11:13:10 +10:00
mcrcortex
7fa07ae5ea Add support for vanilla enviromental fog 2025-06-23 00:51:54 +10:00
mcrcortex
cf60d31b75 update chunky 2025-06-22 23:03:54 +10:00
mcrcortex
e1b4e1ea6a micro optimizations 2025-06-22 21:51:07 +10:00
mcrcortex
4f6b0aa04d use textureLod 2025-06-22 17:42:32 +10:00
mcrcortex
8b5e2780c7 fix 64 sized warps 2025-06-22 16:52:08 +10:00
mcrcortex
0dd730d8de nope cant do that am stupid 2025-06-22 12:14:14 +10:00
mcrcortex
0f865c7afb dont remap pipeline 2025-06-22 11:58:00 +10:00
mcrcortex
688f24a409 bean 2025-06-22 11:52:30 +10:00
mcrcortex
dcacd279b3 readd nvidium support 2025-06-22 11:31:38 +10:00
mcrcortex
37d0b755af Fix issues 2025-06-22 11:06:15 +10:00
mcrcortex
26672ce34b Move more computation into frag shader 2025-06-22 10:48:51 +10:00
mcrcortex
d1be49f474 massivly shrinked interstage attributes 2025-06-21 20:55:53 +10:00
mcrcortex
87072a4edc attempt to improve mipping 2025-06-21 15:03:28 +10:00
mcrcortex
5f8679e5d2 Fix memory leak on reload while importing 2025-06-21 12:32:24 +10:00
mcrcortex
1a7cd37741 attempted to improve ingest perfomance by only saving on section unload 2025-06-21 12:25:46 +10:00
mcrcortex
ed181c1dcd changed priority 2025-06-19 22:43:27 +10:00
mcrcortex
4d839e3662 Attempt to reduce reaquires on miss 2025-06-19 22:15:29 +10:00
mcrcortex
156b30756d Attempted optimizations for world processing 2025-06-19 16:03:31 +10:00
mcrcortex
6326870525 Attempt to fix race condition.... _again_ 2025-06-19 15:33:38 +10:00
mcrcortex
a360c9349a woops 2025-06-19 13:08:45 +10:00
mcrcortex
9e6276e0fa Attempt fix capture index buffer before it gets large 2025-06-19 13:03:39 +10:00
mcrcortex
2bbc7a8999 change fence query 2025-06-19 12:45:21 +10:00
mcrcortex
fc3e05434f add fog override (hackily) back 2025-06-18 10:05:29 +10:00
mcrcortex
388764e9c8 mostly finished 1.21.6, except fog 2025-06-18 09:11:25 +10:00
mcrcortex
3fb8323dd0 Merge branch 'mc_1215' into mc_1216 2025-06-18 08:55:06 +10:00
mcrcortex
3aa1c94c6a inital 1.21.6 2025-06-13 14:49:03 +10:00
89 changed files with 3181 additions and 473 deletions

33
.github/workflows/manual-artifact.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: manual-artifact
on: [ workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Verify wrapper
uses: gradle/actions/wrapper-validation@v3
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: false
- name: Gradle build
run: ./gradlew build
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: voxy-artifacts
path: build/libs/*.jar

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
/build/
/run/
/out/
/logs/

View File

@@ -1,5 +1,5 @@
plugins {
id 'fabric-loom' version "1.10.1"
id 'fabric-loom' version "1.10-SNAPSHOT"
id 'maven-publish'
}
@@ -26,6 +26,24 @@ repositories {
}
maven { url = "https://maven.shedaniel.me/" }
maven { url = "https://maven.terraformersmc.com/releases/" }
exclusiveContent {
forRepository {
ivy {
name = "github"
url = "https://github.com/"
patternLayout {
artifact '/[organisation]/[module]/releases/download/[revision]/[module]-[revision]-[classifier].[ext]'
}
metadataSources {
artifact()
}
}
}
filter {
includeModuleByRegex("[^\\.]+", "nvidium")
}
}
}
@@ -87,29 +105,29 @@ dependencies {
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
//TODO: this is to eventually not need sodium installed as atm its just used for parsing shaders
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.5-0.6.13-fabric"
modCompileOnly "maven.modrinth:sodium:mc1.21.5-0.6.13-fabric"
modRuntimeOnlyMsk "maven.modrinth:sodium:mc1.21.6-0.6.13-fabric"
modCompileOnly "maven.modrinth:sodium:mc1.21.6-0.6.13-fabric"
modImplementation("maven.modrinth:lithium:mc1.21.5-0.16.0-fabric")
modImplementation("maven.modrinth:lithium:mc1.21.7-0.18.0-fabric")
//modRuntimeOnly "maven.modrinth:nvidium:0.2.6-beta"
//modCompileOnly "maven.modrinth:nvidium:0.2.8-beta"
//modRuntimeOnlyMsk "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
modCompileOnly "drouarb:nvidium:0.4.1-beta4:1.21.6@jar"
modCompileOnly("maven.modrinth:modmenu:14.0.0-rc.2")
modRuntimeOnlyMsk("maven.modrinth:modmenu:14.0.0-rc.2")
modCompileOnly("maven.modrinth:modmenu:15.0.0-beta.3")
modRuntimeOnlyMsk("maven.modrinth:modmenu:15.0.0-beta.3")
modCompileOnly("maven.modrinth:iris:1.8.11+1.21.5-fabric")
modRuntimeOnlyMsk("maven.modrinth:iris:1.8.11+1.21.5-fabric")
modCompileOnly("maven.modrinth:iris:1.9.1+1.21.7-fabric")
//modRuntimeOnlyMsk("maven.modrinth:iris:1.9.1+1.21.7-fabric")
//modCompileOnly("maven.modrinth:starlight:1.1.3+1.20.4")
//modCompileOnly("maven.modrinth:immersiveportals:v5.1.7-mc1.20.4")
modCompileOnly("maven.modrinth:chunky:1.4.36-fabric")
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.36-fabric")
modCompileOnly("maven.modrinth:chunky:1.4.40-fabric")
modRuntimeOnlyMsk("maven.modrinth:chunky:1.4.40-fabric")
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.121-fabric")
modRuntimeOnlyMsk("maven.modrinth:spark:1.10.139-fabric")
modRuntimeOnlyMsk("maven.modrinth:fabric-permissions-api:0.3.3")
//modRuntimeOnly("maven.modrinth:nsight-loader:1.2.0")
@@ -192,9 +210,11 @@ remapJar {
delete fileTree(getDestinationDirectory().get())
}
def hash = gitCommitHash();
if (!hash.equals("<UnknownCommit>")) {
archiveClassifier.set(hash);
if (!isInGHA) {
def hash = gitCommitHash();
if (!hash.equals("<UnknownCommit>")) {
archiveClassifier.set(hash);
}
}
}

View File

@@ -6,14 +6,14 @@ org.gradle.parallel=true
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.5
yarn_mappings=1.21.5+build.1
loader_version=0.16.10
minecraft_version=1.21.7
yarn_mappings=1.21.7+build.1
loader_version=0.16.14
# Fabric API
fabric_version=0.119.5+1.21.5
fabric_version=0.128.1+1.21.7
# Mod Properties
mod_version = 0.2.0-alpha
mod_version = 0.2.3-alpha
maven_group = me.cortex
archives_base_name = voxy

View File

@@ -0,0 +1,7 @@
package me.cortex.voxy.client;
import net.minecraft.world.chunk.WorldChunk;
public interface ICheekyClientChunkManager {
WorldChunk voxy$cheekyGetChunk(int x, int z);
}

View File

@@ -1,6 +1,8 @@
package me.cortex.voxy.client;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.fabricmc.api.ClientModInitializer;
@@ -18,9 +20,20 @@ public class VoxyClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ClientLifecycleEvents.CLIENT_STARTED.register(client->{
Capabilities.init();//Ensure clinit is called
boolean systemSupported = Capabilities.INSTANCE.compute && Capabilities.INSTANCE.indirectParameters;
if (systemSupported) {
SharedIndexBuffer.INSTANCE.id();
BudgetBufferRenderer.init();
VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
if (!Capabilities.INSTANCE.subgroup) {
Logger.warn("GPU does not support subgroup operations, expect some performance degradation");
}
} else {
Logger.error("Voxy is unsupported on your system.");
}

View File

@@ -31,6 +31,7 @@ public class VoxyConfig implements OptionStorage<VoxyConfig> {
public int serviceThreads = (int) Math.max(CpuLayout.CORES.length/1.5, 1);
public float subDivisionSize = 64;
public boolean renderVanillaFog = false;
public boolean useEnvironmentalFog = false;
public boolean renderStatistics = false;
public static VoxyConfig loadOrCreate() {

View File

@@ -70,13 +70,10 @@ public abstract class VoxyConfigScreenPages {
if (wasEnabled) {
VoxyCommon.createInstance();
if (vrsh != null && s.enableRendering) {
vrsh.createRenderer();
}
}
}, s -> s.serviceThreads)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.ingest"))
@@ -129,6 +126,14 @@ public abstract class VoxyConfigScreenPages {
}, s -> s.sectionRenderDistance)
.setImpact(OptionImpact.LOW)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.environmental_fog"))
.setTooltip(Text.translatable("voxy.config.general.environmental_fog.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.VARIES)
.setBinding((s, v)-> s.useEnvironmentalFog = v, s -> s.useEnvironmentalFog)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
).add(OptionImpl.createBuilder(boolean.class, storage)
.setName(Text.translatable("voxy.config.general.vanilla_fog"))
.setTooltip(Text.translatable("voxy.config.general.vanilla_fog.tooltip"))

View File

@@ -2,19 +2,15 @@ package me.cortex.voxy.client.core;
import com.mojang.blaze3d.opengl.GlConst;
import com.mojang.blaze3d.opengl.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.model.ModelBakerySubsystem;
import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer;
import me.cortex.voxy.client.core.rendering.RenderDistanceTracker;
import me.cortex.voxy.client.core.rendering.RenderService;
import me.cortex.voxy.client.core.rendering.building.RenderDataFactory;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.post.PostProcessing;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
@@ -24,31 +20,20 @@ import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.thread.ServiceThreadPool;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.WorldSection;
import me.cortex.voxy.common.world.other.Mapper;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.GlBackend;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Frustum;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.lwjgl.opengl.GL11.GL_ONE;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_VIEWPORT;
import static org.lwjgl.opengl.GL11.glGetIntegerv;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.opengl.GL14.glBlendFuncSeparate;
import static org.lwjgl.opengl.GL11C.glFinish;
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
import static org.lwjgl.opengl.GL33.glBindSampler;
@@ -69,10 +54,6 @@ public class VoxyRenderSystem {
glFinish();
glFinish();
//Trigger the shared index buffer loading
SharedIndexBuffer.INSTANCE.id();
Capabilities.init();//Ensure clinit is called
this.worldIn = world;
this.renderer = new RenderService(world, threadPool);
this.postProcessing = new PostProcessing();
@@ -143,7 +124,7 @@ public class VoxyRenderSystem {
).mulLocal(makeProjectionMatrix(16, 16*3000));
}
public void renderOpaque(ChunkRenderMatrices matrices, double cameraX, double cameraY, double cameraZ) {
public void renderOpaque(ChunkRenderMatrices matrices, FogParameters fogParameters, double cameraX, double cameraY, double cameraZ) {
if (IrisUtil.irisShadowActive()) {
return;
}
@@ -180,11 +161,13 @@ public class VoxyRenderSystem {
int[] dims = new int[4];
glGetIntegerv(GL_VIEWPORT, dims);
var viewport = this.renderer.getViewport();
viewport
.setProjection(projection)
.setModelView(new Matrix4f(matrices.modelView()))
.setCamera(cameraX, cameraY, cameraZ)
.setScreenSize(dims[2], dims[3])
.setFogParameters(fogParameters)
.update();
viewport.frameId++;
@@ -211,7 +194,7 @@ public class VoxyRenderSystem {
TimingStatistics.F.start();
this.postProcessing.renderPost(projection, matrices.projection(), boundFB);
this.postProcessing.renderPost(viewport, matrices.projection(), boundFB);
TimingStatistics.F.stop();
TimingStatistics.main.stop();

View File

@@ -24,10 +24,12 @@ public class Capabilities {
public final boolean compute;
public final boolean indirectParameters;
public final boolean isIntel;
public final boolean subgroup;
public Capabilities() {
var cap = GL.getCapabilities();
this.compute = cap.glDispatchComputeIndirect != 0;
this.subgroup = cap.GL_KHR_shader_subgroup;
this.indirectParameters = cap.glMultiDrawElementsIndirectCountARB != 0;
this.repFragTest = cap.GL_NV_representative_fragment_test;
this.meshShaders = cap.GL_NV_mesh_shader;

View File

@@ -0,0 +1,25 @@
package me.cortex.voxy.client.core.gl;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.JNI;
public class EXTMeshShader {
public static final int
GL_MESH_SHADER_EXT = 0x9559,
GL_TASK_SHADER_EXT = 0x955A;
private static final long glDrawMeshTasksIndirectEXT_ptr;
static {
if (GL.getFunctionProvider() == null) {
throw new IllegalStateException("Class must be initalized after gl context has been created");
}
glDrawMeshTasksIndirectEXT_ptr = GL.getFunctionProvider().getFunctionAddress("glDrawMeshTasksIndirectEXT");
}
public static void glDrawMeshTasksIndirectEXT(long indirect) {
if (glDrawMeshTasksIndirectEXT_ptr == 0) {
throw new IllegalStateException("glDrawMeshTasksIndirectEXT not supported");
}
JNI.callPV(indirect, glDrawMeshTasksIndirectEXT_ptr);
}
}

View File

@@ -1,6 +1,7 @@
package me.cortex.voxy.client.core.gl;
import me.cortex.voxy.common.util.TrackedObject;
import org.lwjgl.system.MemoryUtil;
import static org.lwjgl.opengl.GL32.*;
@@ -12,13 +13,24 @@ public class GlFence extends TrackedObject {
this.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
private static final long SCRATCH = MemoryUtil.nmemCalloc(1,4);
public boolean signaled() {
if (!this.signaled) {
/*
int ret = glClientWaitSync(this.fence, 0, 0);
if (ret == GL_ALREADY_SIGNALED || ret == GL_CONDITION_SATISFIED) {
this.signaled = true;
} else if (ret != GL_TIMEOUT_EXPIRED) {
throw new IllegalStateException("Poll for fence failed, glError: " + glGetError());
}*/
MemoryUtil.memPutInt(SCRATCH, -1);
nglGetSynciv(this.fence, GL_SYNC_STATUS, 1, 0, SCRATCH);
int val = MemoryUtil.memGetInt(SCRATCH);
if (val == GL_SIGNALED) {
this.signaled = true;
} else if (val != GL_UNSIGNALED) {
throw new IllegalStateException("Unknown data from glGetSync: "+val);
}
}
return this.signaled;

View File

@@ -101,8 +101,10 @@ public class GlTexture extends TrackedObject {
private long getEstimatedSize() {
this.assertAllocated();
long elemSize = switch (this.format) {
case GL_RGBA8, GL_DEPTH24_STENCIL8 -> 4;
case GL_RGBA8, GL_DEPTH24_STENCIL8, GL_R32F -> 4;
case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right????
case GL_DEPTH_COMPONENT32F -> 4;
case GL_DEPTH_COMPONENT32 -> 4;
default -> throw new IllegalStateException("Unknown element size");
};

View File

@@ -14,6 +14,7 @@ import static org.lwjgl.opengl.GL30.glBindBufferRange;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL44.*;
//TODO: rewrite the entire shader builder system
@@ -26,6 +27,8 @@ public class AutoBindingShader extends Shader {
private final List<BufferBinding> bindings = new ArrayList<>();
private final List<TextureBinding> textureBindings = new ArrayList<>();
private boolean rebuild = true;
AutoBindingShader(Shader.Builder<AutoBindingShader> builder, int program) {
super(program);
this.defines = builder.defines;
@@ -70,6 +73,8 @@ public class AutoBindingShader extends Shader {
}
private void insertOrReplaceBinding(BufferBinding binding) {
this.rebuild = true;
//Check if there is already a binding at the index with the target, if so, replace it
for (int i = 0; i < this.bindings.size(); i++) {
var entry = this.bindings.get(i);
@@ -92,6 +97,16 @@ public class AutoBindingShader extends Shader {
}
public AutoBindingShader texture(int unit, int sampler, GlTexture texture) {
this.rebuild = true;
for (int i = 0; i < this.textureBindings.size(); i++) {
var entry = this.textureBindings.get(i);
if (entry.unit == unit) {
this.textureBindings.set(i, new TextureBinding(unit, sampler, texture));
return this;
}
}
this.textureBindings.add(new TextureBinding(unit, sampler, texture));
return this;
}
@@ -99,6 +114,13 @@ public class AutoBindingShader extends Shader {
@Override
public void bind() {
super.bind();
//TODO: replace with multibind and use the invalidate flag
/*
glBindSamplers();
glBindTextures();
glBindBuffersBase();
glBindBuffersRange();
*/
if (!this.bindings.isEmpty()) {
for (var binding : this.bindings) {
binding.buffer.assertNotFreed();

View File

@@ -4,8 +4,11 @@ import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlDebug;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.ThreadUtils;
import me.cortex.voxy.common.util.TrackedObject;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.nio.file.Files;
@@ -149,6 +152,7 @@ public class Shader extends TrackedObject {
public T compile() {
this.defineIf("IS_INTEL", Capabilities.INSTANCE.isIntel);
this.defineIf("IS_WINDOWS", ThreadUtils.isWindows);
return this.constructor.make(this, this.compileToProgram());
}
@@ -170,7 +174,13 @@ public class Shader extends TrackedObject {
private static int createShader(ShaderType type, String src) {
int shader = GL20C.glCreateShader(type.gl);
GL20C.glShaderSource(shader, src);
{//https://github.com/CaffeineMC/sodium/blob/fc42a7b19836c98a35df46e63303608de0587ab6/src/main/java/me/jellysquid/mods/sodium/client/gl/shader/ShaderWorkarounds.java
long ptr = MemoryUtil.memAddress(MemoryUtil.memUTF8(src, true));
try (var stack = MemoryStack.stackPush()) {
GL20C.nglShaderSource(shader, 1, stack.pointers(ptr).address0(), 0);
}
MemoryUtil.nmemFree(ptr);
}
GL20C.glCompileShader(shader);
String log = GL20C.glGetShaderInfoLog(shader);

View File

@@ -18,6 +18,7 @@ import net.minecraft.block.LeavesBlock;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.color.block.BlockColorProvider;
import net.minecraft.client.render.BlockRenderLayer;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.RenderLayers;
import net.minecraft.fluid.FluidState;
@@ -256,19 +257,19 @@ public class ModelFactory {
this.fluidStateLUT[modelId] = clientFluidStateId;
}
RenderLayer blockRenderLayer = null;
BlockRenderLayer blockRenderLayer = null;
if (blockState.getBlock() instanceof FluidBlock) {
blockRenderLayer = RenderLayers.getFluidLayer(blockState.getFluidState());
} else {
if (blockState.getBlock() instanceof LeavesBlock) {
blockRenderLayer = RenderLayer.getSolid();
blockRenderLayer = BlockRenderLayer.SOLID;
} else {
blockRenderLayer = RenderLayers.getBlockLayer(blockState);
}
}
int checkMode = blockRenderLayer==RenderLayer.getSolid()?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
int checkMode = blockRenderLayer==BlockRenderLayer.SOLID?TextureUtils.WRITE_CHECK_STENCIL:TextureUtils.WRITE_CHECK_ALPHA;
if (Capabilities.INSTANCE.isMesa) {
//Mesa does not work with GL_DEPTH_STENCIL_TEXTURE_MODE GL_STENCIL_INDEX
@@ -339,7 +340,7 @@ public class ModelFactory {
//Each face gets 1 byte, with the top 2 bytes being for whatever
long metadata = 0;
metadata |= isBiomeColourDependent?1:0;
metadata |= blockRenderLayer == RenderLayer.getTranslucent()?2:0;
metadata |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?2:0;
metadata |= needsDoubleSidedQuads?4:0;
metadata |= ((!isFluid) && !blockState.getFluidState().isEmpty())?8:0;//Has a fluid state accosiacted with it and is not itself a fluid
metadata |= isFluid?16:0;//Is a fluid
@@ -373,7 +374,7 @@ public class ModelFactory {
//TODO: add alot of config options for the following
boolean occludesFace = true;
occludesFace &= blockRenderLayer != RenderLayer.getTranslucent();//If its translucent, it doesnt occlude
occludesFace &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//If its translucent, it doesnt occlude
//TODO: make this an option, basicly if the face is really close, it occludes otherwise it doesnt
occludesFace &= offset < 0.1;//If the face is rendered far away from the other face, then it doesnt occlude
@@ -393,7 +394,7 @@ public class ModelFactory {
metadata |= canBeOccluded?4:0;
//Face uses its own lighting if its not flat against the adjacent block & isnt traslucent
metadata |= (offset > 0.01 || blockRenderLayer == RenderLayer.getTranslucent())?0b1000:0;
metadata |= (offset > 0.01 || blockRenderLayer == BlockRenderLayer.TRANSLUCENT)?0b1000:0;
@@ -411,11 +412,11 @@ public class ModelFactory {
int area = (faceSize[1]-faceSize[0]+1) * (faceSize[3]-faceSize[2]+1);
boolean needsAlphaDiscard = ((float)writeCount)/area<0.9;//If the amount of area covered by written pixels is less than a threashold, disable discard as its not needed
needsAlphaDiscard |= blockRenderLayer != RenderLayer.getSolid();
needsAlphaDiscard &= blockRenderLayer != RenderLayer.getTranslucent();//Translucent doesnt have alpha discard
needsAlphaDiscard |= blockRenderLayer != BlockRenderLayer.SOLID;
needsAlphaDiscard &= blockRenderLayer != BlockRenderLayer.TRANSLUCENT;//Translucent doesnt have alpha discard
faceModelData |= needsAlphaDiscard?1<<22:0;
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != RenderLayer.getTranslucent())?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
faceModelData |= ((!faceCoversFullBlock)&&blockRenderLayer != BlockRenderLayer.TRANSLUCENT)?1<<23:0;//Alpha discard override, translucency doesnt have alpha discard
@@ -435,8 +436,8 @@ public class ModelFactory {
int modelFlags = 0;
modelFlags |= colourProvider != null?1:0;
modelFlags |= isBiomeColourDependent?2:0;//Basicly whether to use the next int as a colour or as a base index/id into a colour buffer for biome dependent colours
modelFlags |= blockRenderLayer == RenderLayer.getTranslucent()?4:0;//Is translucent
modelFlags |= blockRenderLayer == RenderLayer.getCutout()?0:8;//Dont use mipmaps (AND ALSO FKING SPECIFIES IF IT HAS AO, WHY??? GREAT QUESTION, TODO FIXE THIS)
modelFlags |= blockRenderLayer == BlockRenderLayer.TRANSLUCENT?4:0;//Is translucent
modelFlags |= blockRenderLayer == BlockRenderLayer.CUTOUT?0:8;//Dont use mipmaps (AND ALSO FKING SPECIFIES IF IT HAS AO, WHY??? GREAT QUESTION, TODO FIXE THIS)
//modelFlags |= blockRenderLayer == RenderLayer.getSolid()?0:1;// should discard alpha
MemoryUtil.memPutInt(uploadPtr, modelFlags);
@@ -679,7 +680,7 @@ public class ModelFactory {
private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
//TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping
private void putTextures(int id, ColourDepthTextureData[] textures) {
if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
//TODO: need to use a write mask to see what pixels must be used to contribute to mipping
// as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont

View File

@@ -24,14 +24,14 @@ public class ModelStore {
public ModelStore() {
this.modelBuffer = new GlBuffer(MODEL_SIZE * (1<<16)).name("ModelData");
this.modelColourBuffer = new GlBuffer(4 * (1<<16)).name("ModelColour");
this.textures = new GlTexture().store(GL_RGBA8, 4, ModelFactory.MODEL_TEXTURE_SIZE*3*256,ModelFactory.MODEL_TEXTURE_SIZE*2*256).name("ModelTextures");
this.textures = new GlTexture().store(GL_RGBA8, Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE), ModelFactory.MODEL_TEXTURE_SIZE*3*256,ModelFactory.MODEL_TEXTURE_SIZE*2*256).name("ModelTextures");
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4);
glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, Integer.numberOfTrailingZeros(ModelFactory.MODEL_TEXTURE_SIZE));
}

View File

@@ -46,6 +46,21 @@ public class BakedBlockEntityModel {
this.layers.forEach(layer->layer.consumer.free());
}
private static int getMetaFromLayer(RenderLayer layer) {
boolean hasDiscard = layer == RenderLayer.getCutout() ||
layer == RenderLayer.getCutoutMipped() ||
layer == RenderLayer.getTripwire();
boolean isMipped = layer == RenderLayer.getCutoutMipped() ||
layer == RenderLayer.getSolid() ||
layer.isTranslucent() ||
layer == RenderLayer.getTripwire();
int meta = hasDiscard?1:0;
meta |= isMipped?2:0;
return meta;
}
public static BakedBlockEntityModel bake(BlockState state) {
Map<RenderLayer, LayerConsumer> map = new HashMap<>();
var entity = ((BlockEntityProvider)state.getBlock()).createBlockEntity(BlockPos.ORIGIN, state);
@@ -56,7 +71,7 @@ public class BakedBlockEntityModel {
entity.setWorld(MinecraftClient.getInstance().world);
if (renderer != null) {
try {
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(ModelTextureBakery.getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
renderer.render(entity, 0.0f, new MatrixStack(), layer->map.computeIfAbsent(layer, rl -> new LayerConsumer(rl, new ReuseVertexConsumer().setDefaultMeta(getMetaFromLayer(rl)))).consumer, 0, 0, new Vec3d(0,0,0));
} catch (Exception e) {
Logger.error("Unable to bake block entity: " + entity, e);
}

View File

@@ -26,6 +26,7 @@ public class BudgetBufferRenderer {
.compile();
public static void init(){}
private static final GlBuffer indexBuffer;
static {
var i = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS);

View File

@@ -1,12 +1,9 @@
package me.cortex.voxy.client.core.model.bakery;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.FluidBlock;
import net.minecraft.block.LeavesBlock;
import net.minecraft.block.*;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.BlockRenderLayer;
import net.minecraft.client.render.RenderLayers;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.fluid.FluidState;
@@ -21,6 +18,8 @@ import net.minecraft.world.biome.ColorResolver;
import net.minecraft.world.chunk.light.LightingProvider;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL14;
import static org.lwjgl.opengl.GL11.*;
@@ -43,22 +42,25 @@ public class ModelTextureBakery {
this.height = height;
}
public static int getMetaFromLayer(RenderLayer layer) {
boolean hasDiscard = layer == RenderLayer.getCutout() ||
layer == RenderLayer.getCutoutMipped() ||
layer == RenderLayer.getTripwire();
public static int getMetaFromLayer(BlockRenderLayer layer) {
boolean hasDiscard = layer == BlockRenderLayer.CUTOUT ||
layer == BlockRenderLayer.CUTOUT_MIPPED ||
layer == BlockRenderLayer.TRIPWIRE;
boolean isMipped = layer == RenderLayer.getCutoutMipped() ||
layer == RenderLayer.getSolid() ||
layer == RenderLayer.getTranslucent() ||
layer == RenderLayer.getTripwire();
boolean isMipped = layer == BlockRenderLayer.CUTOUT_MIPPED ||
layer == BlockRenderLayer.SOLID ||
layer == BlockRenderLayer.TRANSLUCENT ||
layer == BlockRenderLayer.TRIPWIRE;
int meta = hasDiscard?1:0;
meta |= isMipped?2:0;
return meta;
}
private void bakeBlockModel(BlockState state, RenderLayer layer) {
private void bakeBlockModel(BlockState state, BlockRenderLayer layer) {
if (state.getRenderType() == BlockRenderType.INVISIBLE) {
return;//Dont bake if invisible
}
var model = MinecraftClient.getInstance()
.getBakedModelManager()
.getBlockModels()
@@ -79,7 +81,7 @@ public class ModelTextureBakery {
}
private void bakeFluidState(BlockState state, RenderLayer layer, int face) {
private void bakeFluidState(BlockState state, BlockRenderLayer layer, int face) {
this.vc.setDefaultMeta(getMetaFromLayer(layer));//Set the meta while baking
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
@Override
@@ -163,13 +165,13 @@ public class ModelTextureBakery {
public void renderToStream(BlockState state, int streamBuffer, int streamOffset) {
this.capture.clear();
boolean isBlock = true;
RenderLayer layer;
BlockRenderLayer layer;
if (state.getBlock() instanceof FluidBlock) {
layer = RenderLayers.getFluidLayer(state.getFluidState());
isBlock = false;
} else {
if (state.getBlock() instanceof LeavesBlock) {
layer = RenderLayer.getSolid();
layer = BlockRenderLayer.SOLID;
} else {
layer = RenderLayers.getBlockLayer(state);
}
@@ -189,7 +191,7 @@ public class ModelTextureBakery {
glEnable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
if (layer == RenderLayer.getTranslucent()) {
if (layer == BlockRenderLayer.TRANSLUCENT) {
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
} else {
@@ -311,7 +313,7 @@ public class ModelTextureBakery {
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
glClearDepth(1);
glClear(GL_DEPTH_BUFFER_BIT);
if (layer == RenderLayer.getTranslucent()) {
if (layer == BlockRenderLayer.TRANSLUCENT) {
//reset the blend func
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
@@ -335,11 +337,22 @@ public class ModelTextureBakery {
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
var stack = new MatrixStack();
stack.translate(0.5f,0.5f,0.5f);
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
stack.multiply(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
stack.multiply(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
stack.multiplyPositionMatrix(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
stack.translate(-0.5f,-0.5f,-0.5f);
VIEWS[i] = new Matrix4f(stack.peek().getPositionMatrix());
}
private static Quaternionf makeQuatFromAxisExact(Vector3f vec, float angle) {
angle = (float) Math.toRadians(angle);
float hangle = angle / 2.0f;
float sinAngle = (float) Math.sin(hangle);
float invVLength = (float) (1/Math.sqrt(vec.lengthSquared()));
return new Quaternionf(vec.x * invVLength * sinAngle,
vec.y * invVLength * sinAngle,
vec.z * invVLength * sinAngle,
Math.cos(hangle));
}
}

View File

@@ -10,10 +10,8 @@ import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.hierachical.AsyncNodeManager;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.hierachical.NodeCleaner;
import me.cortex.voxy.client.core.rendering.section.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.*;
import me.cortex.voxy.client.core.rendering.section.geometry.*;
import me.cortex.voxy.client.core.rendering.section.IUsesMeshlets;
import me.cortex.voxy.client.core.rendering.section.MDICSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
@@ -58,6 +56,10 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
}
//geometryCapacity = 1<<28;
//geometryCapacity = 1<<30;//1GB test
var override = System.getProperty("voxy.geometryBufferSizeOverrideMB", "");
if (!override.isEmpty()) {
geometryCapacity = Long.parseLong(override)*1024L*1024L;
}
return geometryCapacity;
}
@@ -71,7 +73,9 @@ public class RenderService<T extends AbstractSectionRenderer<J, Q>, J extends Vi
this.geometryData = (Q) new BasicSectionGeometryData(1<<20, geometryCapacity);
//Max sections: ~500k
this.sectionRenderer = (T) new MDICSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
//this.sectionRenderer = (T) new MDICSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
//this.sectionRenderer = (T) new MeshSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
this.sectionRenderer = (T) new MeshEXTSectionRenderer(this.modelService.getStore(), (BasicSectionGeometryData) this.geometryData);
Logger.info("Using renderer: " + this.sectionRenderer.getClass().getSimpleName() + " with geometry buffer of: " + geometryCapacity + " bytes");
//Do something incredibly hacky, we dont need to keep the reference to this around, so just connect and discard

View File

@@ -2,12 +2,15 @@ package me.cortex.voxy.client.core.rendering;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer2;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.util.math.MathHelper;
import org.joml.*;
import java.lang.reflect.Field;
public abstract class Viewport <A extends Viewport<A>> {
//public final HiZBuffer2 hiZBuffer = new HiZBuffer2();
public final HiZBuffer hiZBuffer = new HiZBuffer();
private static final Field planesField;
static {
@@ -29,6 +32,7 @@ public abstract class Viewport <A extends Viewport<A>> {
public double cameraX;
public double cameraY;
public double cameraZ;
public FogParameters fogParameters;
public final Matrix4f MVP = new Matrix4f();
public final Vector3i section = new Vector3i();
@@ -75,6 +79,11 @@ public abstract class Viewport <A extends Viewport<A>> {
return (A) this;
}
public A setFogParameters(FogParameters fogParameters) {
this.fogParameters = fogParameters;
return (A) this;
}
public A update() {
//MVP
this.projection.mul(this.modelView, this.MVP);

View File

@@ -4,6 +4,7 @@ import me.cortex.voxy.client.core.model.IdNotYetComputedException;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.client.core.model.ModelQueries;
import me.cortex.voxy.client.core.util.ScanMesher2D;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.util.UnsafeUtil;
import me.cortex.voxy.common.world.WorldEngine;
@@ -1607,6 +1608,10 @@ public class RenderDataFactory {
return BuiltSection.emptyWithChildren(section.key, section.getNonEmptyChildren());
}
if (this.quadCount >= 1<<16) {
Logger.warn("Large quad count for section " + WorldEngine.pprintPos(section.key) + " is " + this.quadCount);
}
if (this.minX<0 || this.minY<0 || this.minZ<0 || 32<this.maxX || 32<this.maxY || 32<this.maxZ) {
throw new IllegalStateException();
}

View File

@@ -246,7 +246,7 @@ public class AsyncNodeManager {
//Limit uploading as well as by geometry capacity being available
// must have 50 mb of free geometry space to upload
for (int limit = 0; limit < 200 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000); limit++) {
for (int limit = 0; limit < 200 && ((this.geometryCapacity-this.geometryManager.getGeometryUsedBytes())>50_000_000L); limit++) {
var job = this.geometryUpdateQueue.poll();
if (job == null)
break;

View File

@@ -7,11 +7,10 @@ import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.shader.AutoBindingShader;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.building.RenderGenerationService;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.util.MemoryBuffer;
import me.cortex.voxy.common.world.WorldEngine;

View File

@@ -3,6 +3,8 @@ package me.cortex.voxy.client.core.rendering.post;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import java.util.function.Function;
import static org.lwjgl.opengl.GL11C.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11C.glDrawArrays;
import static org.lwjgl.opengl.GL30C.glBindVertexArray;
@@ -13,9 +15,12 @@ public class FullscreenBlit {
private final Shader shader;
public FullscreenBlit(String fragId) {
this.shader = Shader.make()
this(fragId, (a)->a);
}
public <T extends Shader> FullscreenBlit(String fragId, Function<Shader.Builder<T>, Shader.Builder<T>> builder) {
this.shader = builder.apply((Shader.Builder<T>) Shader.make()
.add(ShaderType.VERTEX, "voxy:post/fullscreen.vert")
.add(ShaderType.FRAGMENT, fragId)
.add(ShaderType.FRAGMENT, fragId))
.compile();
}

View File

@@ -1,14 +1,14 @@
package me.cortex.voxy.client.core.rendering.post;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.util.GlStateCapture;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11C;
import static org.lwjgl.opengl.ARBComputeShader.glDispatchCompute;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glBindImageTexture;
@@ -20,6 +20,7 @@ import static org.lwjgl.opengl.GL43.GL_DEPTH_STENCIL_TEXTURE_MODE;
import static org.lwjgl.opengl.GL45C.*;
public class PostProcessing {
private final boolean useEnvFog = VoxyConfig.CONFIG.useEnvironmentalFog;
private final GlFramebuffer framebuffer;
private final GlFramebuffer framebufferSSAO;
private int width;
@@ -31,7 +32,8 @@ public class PostProcessing {
private final FullscreenBlit setDepth0 = new FullscreenBlit("voxy:post/depth0.frag");
private final FullscreenBlit emptyBlit = new FullscreenBlit("voxy:post/noop.frag");
//private final FullscreenBlit blitTexture = new FullscreenBlit("voxy:post/blit_texture_cutout.frag");
private final FullscreenBlit blitTexture = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag");
private final FullscreenBlit blitTexture = new FullscreenBlit("voxy:post/blit_texture_depth_cutout.frag",
a->a.defineIf("USE_ENV_FOG", useEnvFog));
private final Shader ssaoComp = Shader.make()
.add(ShaderType.COMPUTE, "voxy:post/ssao.comp")
.compile();
@@ -158,7 +160,7 @@ public class PostProcessing {
//Executes the post processing and emits to whatever framebuffer is currently bound via a blit
public void renderPost(Matrix4f fromProjection, Matrix4fc tooProjection, int outputFB) {
public void renderPost(Viewport vp, Matrix4fc tooProjection, int outputFB) {
glDisable(GL_STENCIL_TEST);
@@ -172,12 +174,17 @@ public class PostProcessing {
this.blitTexture.bind();
float[] data = new float[4*4];
var mat = new Matrix4f(fromProjection).invert();
mat.get(data);
new Matrix4f(vp.MVP).invert().get(data);
glUniformMatrix4fv(2, false, data);//inverse fromProjection
tooProjection.get(data);
new Matrix4f(tooProjection).mul(vp.modelView).get(data);
glUniformMatrix4fv(3, false, data);//tooProjection
if (useEnvFog) {
float start = vp.fogParameters.environmentalStart();
float end = vp.fogParameters.environmentalEnd();
float invEndFogDelta = 1f/(end-start);
glUniform3f(4, vp.fogParameters.environmentalEnd()/2f, invEndFogDelta, start*invEndFogDelta);
glUniform3f(5, vp.fogParameters.red(), vp.fogParameters.green(), vp.fogParameters.blue());
}
glBindTextureUnit(0, this.didSSAO?this.colourSSAO.id:this.colour.id);

View File

@@ -3,7 +3,6 @@ package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
import me.cortex.voxy.client.core.rendering.util.HiZBuffer;
public class MDICViewport extends Viewport<MDICViewport> {
public final GlBuffer drawCountCallBuffer = new GlBuffer(1024).zero();

View File

@@ -0,0 +1,227 @@
package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.RenderService;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static me.cortex.voxy.client.core.gl.EXTMeshShader.glDrawMeshTasksIndirectEXT;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_R32UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.*;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
//Uses MDIC to render the sections
public class MeshEXTSectionRenderer extends AbstractSectionRenderer<MeshViewport, BasicSectionGeometryData> {
private static final int STATISTICS_BUFFER_BINDING = 8;
private final Shader terrainShader = Shader.make()
.define("MESH_SIZE", 32)//16
.defineIf("HAS_STATISTICS", RenderStatistics.enabled)
.defineIf("STATISTICS_BUFFER_BINDING", RenderStatistics.enabled, STATISTICS_BUFFER_BINDING)
.add(ShaderType.TASK, "voxy:lod/meshext/task.glsl")
.add(ShaderType.MESH, "voxy:lod/meshext/mesh.glsl")
.add(ShaderType.FRAGMENT, "voxy:lod/meshext/frag.glsl")
.compile();
private final Shader cullShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
.compile();
private final GlBuffer uniform = new GlBuffer(1024).zero();
private final GlBuffer cullAndMeshDrawCommand = new GlBuffer(8*4).zero();//TODO: this needs tobe in the viewport
//Statistics
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
public MeshEXTSectionRenderer(ModelStore modelStore, BasicSectionGeometryData geometryData) {
super(modelStore, geometryData);
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{6*2*3});//count
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{(1<<16)*6*2});//firstIndex
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,5*4+4, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{1});//y
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,5*4+8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{1});//z
}
private void uploadUniformBuffer(MeshViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniform, 0, 1024);
var mat = new Matrix4f(viewport.MVP);
mat.translate(-viewport.innerTranslation.x, -viewport.innerTranslation.y, -viewport.innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
viewport.section.getToAddress(ptr); ptr += 4*3;
if (viewport.frameId<0) {
Logger.error("Frame ID negative, this will cause things to break, wrapping around");
viewport.frameId &= 0x7fffffff;
}
MemoryUtil.memPutInt(ptr, viewport.frameId&0x7fffffff); ptr += 4;
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
ptr += 4;// padd
MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
UploadStream.INSTANCE.commit();
}
private void bindRenderingBuffers(MeshViewport viewport, GlTexture depthBoundTexture) {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, viewport.getRenderList().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometryManager.getGeometryBuffer().id);
this.modelStore.bind(5, 6, 0);
LightMapHelper.bind(1);
glBindTextureUnit(2, depthBoundTexture.id);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
if (RenderStatistics.enabled) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, STATISTICS_BUFFER_BINDING, this.statisticsBuffer.id);
}
}
private void renderTerrain(MeshViewport viewport, GlTexture depthBoundTexture) {
//RenderLayer.getCutoutMipped().startDrawing();
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
this.terrainShader.bind();
this.bindRenderingBuffers(viewport, depthBoundTexture);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
glDrawMeshTasksIndirectEXT(20);
glEnable(GL_CULL_FACE);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
glBindSampler(1, 0);
glBindTextureUnit(1, 0);
//RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderOpaque(MeshViewport viewport, GlTexture dbt) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
this.renderTerrain(viewport, dbt);
//We need todo the statistics here as rastering is part of them, download then clear
if (RenderStatistics.enabled) {
DownloadStream.INSTANCE.download(this.statisticsBuffer, down->{
final int LAYERS = WorldEngine.MAX_LOD_LAYER+1;
for (int i = 0; i < LAYERS; i++) {
RenderStatistics.visibleSections[i] = MemoryUtil.memGetInt(down.address+i*4L);
}
for (int i = 0; i < LAYERS; i++) {
RenderStatistics.quadCount[i] = MemoryUtil.memGetInt(down.address+LAYERS*4L+i*4L);
}
});
this.statisticsBuffer.zero();
}
}
@Override
public void renderTranslucent(MeshViewport viewport, GlTexture depthBoundTexture) {
return;
}
@Override
public void buildDrawCalls(MeshViewport viewport) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
//Can do a sneeky trick, since the sectionRenderList is a list to things to render, it invokes the culler
// which only marks visible sections
{//Test occlusion
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 4, 4);//Copy counts to the draw buffer
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 20, 4);//Copy counts to the draw buffer
this.cullShader.bind();
if (Capabilities.INSTANCE.repFragTest) {
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
glBindVertexArray(RenderService.STATIC_VAO);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getMetadataBuffer().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.getRenderList().id);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
glEnable(GL_DEPTH_TEST);
glColorMask(false, false, false, false);
glDepthMask(false);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 0);
glDepthMask(true);
glColorMask(true, true, true, true);
glDisable(GL_DEPTH_TEST);
if (Capabilities.INSTANCE.repFragTest) {
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
}
}
@Override
public void renderTemporal(MeshViewport viewport, GlTexture dbt) {
return;
}
@Override
public void addDebug(List<String> lines) {
super.addDebug(lines);
//lines.add("SC/GS: " + this.geometryManager.getSectionCount() + "/" + (this.geometryManager.getGeometryUsed()/(1024*1024)));//section count/geometry size (MB)
}
@Override
public MeshViewport createViewport() {
return new MeshViewport(this.geometryManager.getMaxSectionCount());
}
@Override
public void free() {
this.cullAndMeshDrawCommand.free();
this.uniform.free();
this.terrainShader.free();
this.cullShader.free();
this.statisticsBuffer.free();
}
}

View File

@@ -0,0 +1,228 @@
package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.RenderStatistics;
import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.model.ModelStore;
import me.cortex.voxy.client.core.rendering.RenderService;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.LightMapHelper;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.world.WorldEngine;
import org.joml.Matrix4f;
import org.lwjgl.system.MemoryUtil;
import java.util.List;
import static org.lwjgl.opengl.ARBIndirectParameters.GL_PARAMETER_BUFFER_ARB;
import static org.lwjgl.opengl.ARBIndirectParameters.glMultiDrawElementsIndirectCountARB;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL11C.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL30.glBindBufferBase;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30C.GL_R32UI;
import static org.lwjgl.opengl.GL30C.GL_RED_INTEGER;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER;
import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.*;
import static org.lwjgl.opengl.GL45.*;
import static org.lwjgl.opengl.GL45C.glClearNamedBufferData;
import static org.lwjgl.opengl.NVMeshShader.glDrawMeshTasksIndirectNV;
import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV;
//Uses MDIC to render the sections
public class MeshSectionRenderer extends AbstractSectionRenderer<MeshViewport, BasicSectionGeometryData> {
private static final int STATISTICS_BUFFER_BINDING = 8;
private final Shader terrainShader = Shader.make()
.define("MESH_SIZE", 32)//16
.defineIf("HAS_STATISTICS", RenderStatistics.enabled)
.defineIf("STATISTICS_BUFFER_BINDING", RenderStatistics.enabled, STATISTICS_BUFFER_BINDING)
.add(ShaderType.TASK, "voxy:lod/mesh/task.glsl")
.add(ShaderType.MESH, "voxy:lod/mesh/mesh.glsl")
.add(ShaderType.FRAGMENT, "voxy:lod/mesh/frag.glsl")
.compile();
private final Shader cullShader = Shader.make()
.add(ShaderType.VERTEX, "voxy:lod/gl46/cull/raster.vert")
.add(ShaderType.FRAGMENT, "voxy:lod/gl46/cull/raster.frag")
.compile();
private final GlBuffer uniform = new GlBuffer(1024).zero();
private final GlBuffer cullAndMeshDrawCommand = new GlBuffer(7*4).zero();//TODO: this needs tobe in the viewport
//Statistics
private final GlBuffer statisticsBuffer = new GlBuffer(1024).zero();
public MeshSectionRenderer(ModelStore modelStore, BasicSectionGeometryData geometryData) {
super(modelStore, geometryData);
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,0, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{6*2*3});//count
glClearNamedBufferSubData(this.cullAndMeshDrawCommand.id, GL_R32UI,8, 4, GL_RED_INTEGER, GL_UNSIGNED_INT, new int[]{(1<<16)*6*2});//firstIndex
}
private void uploadUniformBuffer(MeshViewport viewport) {
long ptr = UploadStream.INSTANCE.upload(this.uniform, 0, 1024);
var mat = new Matrix4f(viewport.MVP);
mat.translate(-viewport.innerTranslation.x, -viewport.innerTranslation.y, -viewport.innerTranslation.z);
mat.getToAddress(ptr); ptr += 4*4*4;
viewport.section.getToAddress(ptr); ptr += 4*3;
if (viewport.frameId<0) {
Logger.error("Frame ID negative, this will cause things to break, wrapping around");
viewport.frameId &= 0x7fffffff;
}
MemoryUtil.memPutInt(ptr, viewport.frameId&0x7fffffff); ptr += 4;
viewport.innerTranslation.getToAddress(ptr); ptr += 4*3;
ptr += 4;// padd
MemoryUtil.memPutFloat(ptr, viewport.width); ptr += 4;
MemoryUtil.memPutFloat(ptr, viewport.height); ptr += 4;
UploadStream.INSTANCE.commit();
}
private void bindRenderingBuffers(MeshViewport viewport, GlTexture depthBoundTexture) {
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, viewport.getRenderList().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, this.geometryManager.getMetadataBuffer().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, this.geometryManager.getGeometryBuffer().id);
this.modelStore.bind(5, 6, 0);
LightMapHelper.bind(1);
glBindTextureUnit(2, depthBoundTexture.id);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
if (RenderStatistics.enabled) {
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, STATISTICS_BUFFER_BINDING, this.statisticsBuffer.id);
}
}
private void renderTerrain(MeshViewport viewport, GlTexture depthBoundTexture) {
//RenderLayer.getCutoutMipped().startDrawing();
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
this.terrainShader.bind();
this.bindRenderingBuffers(viewport, depthBoundTexture);
glMemoryBarrier(GL_COMMAND_BARRIER_BIT|GL_SHADER_STORAGE_BARRIER_BIT);//Barrier everything is needed
glDrawMeshTasksIndirectNV(20);
glEnable(GL_CULL_FACE);
glBindSampler(0, 0);
glBindTextureUnit(0, 0);
glBindSampler(1, 0);
glBindTextureUnit(1, 0);
//RenderLayer.getCutoutMipped().endDrawing();
}
@Override
public void renderOpaque(MeshViewport viewport, GlTexture dbt) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
this.renderTerrain(viewport, dbt);
//We need todo the statistics here as rastering is part of them, download then clear
if (RenderStatistics.enabled) {
DownloadStream.INSTANCE.download(this.statisticsBuffer, down->{
final int LAYERS = WorldEngine.MAX_LOD_LAYER+1;
for (int i = 0; i < LAYERS; i++) {
RenderStatistics.visibleSections[i] = MemoryUtil.memGetInt(down.address+i*4L);
}
for (int i = 0; i < LAYERS; i++) {
RenderStatistics.quadCount[i] = MemoryUtil.memGetInt(down.address+LAYERS*4L+i*4L);
}
});
this.statisticsBuffer.zero();
}
}
@Override
public void renderTranslucent(MeshViewport viewport, GlTexture depthBoundTexture) {
return;
}
@Override
public void buildDrawCalls(MeshViewport viewport) {
if (this.geometryManager.getSectionCount() == 0) return;
this.uploadUniformBuffer(viewport);
//Can do a sneeky trick, since the sectionRenderList is a list to things to render, it invokes the culler
// which only marks visible sections
{//Test occlusion
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 4, 4);//Copy counts to the draw buffer
glCopyNamedBufferSubData(viewport.getRenderList().id, this.cullAndMeshDrawCommand.id, 0, 20, 4);//Copy counts to the draw buffer
this.cullShader.bind();
if (Capabilities.INSTANCE.repFragTest) {
glEnable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
glBindVertexArray(RenderService.STATIC_VAO);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniform.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.geometryManager.getMetadataBuffer().id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, viewport.visibilityBuffer.id);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, viewport.getRenderList().id);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, this.cullAndMeshDrawCommand.id);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id());
glEnable(GL_DEPTH_TEST);
glColorMask(false, false, false, false);
glDepthMask(false);
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT|GL_COMMAND_BARRIER_BIT);
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_BYTE, 0);
glDepthMask(true);
glColorMask(true, true, true, true);
glDisable(GL_DEPTH_TEST);
if (Capabilities.INSTANCE.repFragTest) {
glDisable(GL_REPRESENTATIVE_FRAGMENT_TEST_NV);
}
}
}
@Override
public void renderTemporal(MeshViewport viewport, GlTexture dbt) {
return;
}
@Override
public void addDebug(List<String> lines) {
super.addDebug(lines);
//lines.add("SC/GS: " + this.geometryManager.getSectionCount() + "/" + (this.geometryManager.getGeometryUsed()/(1024*1024)));//section count/geometry size (MB)
}
@Override
public MeshViewport createViewport() {
return new MeshViewport(this.geometryManager.getMaxSectionCount());
}
@Override
public void free() {
this.cullAndMeshDrawCommand.free();
this.uniform.free();
this.terrainShader.free();
this.cullShader.free();
this.statisticsBuffer.free();
}
}

View File

@@ -0,0 +1,26 @@
package me.cortex.voxy.client.core.rendering.section;
import me.cortex.voxy.client.core.gl.GlBuffer;
import me.cortex.voxy.client.core.rendering.Viewport;
import me.cortex.voxy.client.core.rendering.hierachical.HierarchicalOcclusionTraverser;
public class MeshViewport extends Viewport<MeshViewport> {
public final GlBuffer indirectLookupBuffer = new GlBuffer(HierarchicalOcclusionTraverser.MAX_QUEUE_SIZE *4+4);
public final GlBuffer visibilityBuffer;
public MeshViewport(int maxSectionCount) {
this.visibilityBuffer = new GlBuffer(maxSectionCount*4L);
}
@Override
protected void delete0() {
super.delete0();
this.visibilityBuffer.free();
this.indirectLookupBuffer.free();
}
@Override
public GlBuffer getRenderList() {
return this.indirectLookupBuffer;
}
}

View File

@@ -0,0 +1,149 @@
package me.cortex.voxy.client.core.rendering.util;
import me.cortex.voxy.client.core.gl.GlFramebuffer;
import me.cortex.voxy.client.core.gl.GlTexture;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.gl.shader.ShaderType;
import me.cortex.voxy.client.core.rendering.RenderService;
import org.lwjgl.opengl.GL11;
import static org.lwjgl.opengl.ARBDirectStateAccess.*;
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_TEXTURE_FETCH_BARRIER_BIT;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.opengl.GL30C.*;
import static org.lwjgl.opengl.GL30C.glBindVertexArray;
import static org.lwjgl.opengl.GL33.glBindSampler;
import static org.lwjgl.opengl.GL33.glGenSamplers;
import static org.lwjgl.opengl.GL33C.glDeleteSamplers;
import static org.lwjgl.opengl.GL33C.glSamplerParameteri;
import static org.lwjgl.opengl.GL42C.*;
import static org.lwjgl.opengl.GL43C.glDispatchCompute;
import static org.lwjgl.opengl.GL45C.glTextureBarrier;
public class HiZBuffer2 {
private final Shader hizMip = Shader.make()
.add(ShaderType.COMPUTE, "voxy:hiz/hiz.comp")
.compile();
private final Shader hizInitial = Shader.make()
.add(ShaderType.VERTEX, "voxy:hiz/blit.vsh")
.add(ShaderType.FRAGMENT, "voxy:hiz/blit.fsh")
.define("OUTPUT_COLOUR")
.compile();
private final GlFramebuffer fb = new GlFramebuffer().name("HiZ");
private final int sampler = glGenSamplers();
private final int type;
private GlTexture texture;
private int levels;
private int width;
private int height;
public HiZBuffer2() {
this(GL_R32F);
}
public HiZBuffer2(int type) {
glNamedFramebufferDrawBuffer(this.fb.id, GL_COLOR_ATTACHMENT0);
this.type = type;
}
private void alloc(int width, int height) {
this.levels = (int)Math.ceil(Math.log(Math.max(width, height))/Math.log(2));
//We dont care about e.g. 1x1 size texture since you dont get meshlets that big to cover such a large area
//this.levels -= 1;//Arbitrary size, shinks the max level by alot and saves a significant amount of processing time
// (could probably increase it to be defined by a max meshlet coverage computation thing)
//GL_DEPTH_COMPONENT32F //Cant use this as it does not match the depth format of the provided depth buffer
this.texture = new GlTexture().store(this.type, this.levels, width, height).name("HiZ");
glTextureParameteri(this.texture.id, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTextureParameteri(this.texture.id, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureParameteri(this.texture.id, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glTextureParameteri(this.texture.id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTextureParameteri(this.texture.id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glSamplerParameteri(this.sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glSamplerParameteri(this.sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(this.sampler, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glSamplerParameteri(this.sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(this.sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
this.width = width;
this.height = height;
this.fb.bind(GL_COLOR_ATTACHMENT0, this.texture, 0).verify();
}
public void buildMipChain(int srcDepthTex, int width, int height) {
if (this.width != Integer.highestOneBit(width) || this.height != Integer.highestOneBit(height)) {
if (this.texture != null) {
this.texture.free();
this.texture = null;
}
this.alloc(Integer.highestOneBit(width), Integer.highestOneBit(height));
}
{//Mip down to initial chain
int boundFB = GL11.glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING);
glBindVertexArray(RenderService.STATIC_VAO);
this.hizInitial.bind();
glBindFramebuffer(GL_FRAMEBUFFER, this.fb.id);
glDisable(GL_DEPTH_TEST);
glBindTextureUnit(0, srcDepthTex);
glBindSampler(0, this.sampler);
glUniform1i(0, 0);
glViewport(0, 0, this.width, this.height);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glTextureBarrier();
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_FETCH_BARRIER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, boundFB);
glViewport(0, 0, width, height);
glBindVertexArray(0);
}
{//Compute based Mipping
this.hizMip.bind();
glUniform2f(0, 1f/this.width, 1f/this.height);
glBindTextureUnit(0, this.texture.id);
glBindSampler(0, this.sampler);
for (int i = 1; i < 7; i++) {
glBindImageTexture(i, this.texture.id, i, false, 0, GL_WRITE_ONLY, GL_R32F);
}
glDispatchCompute(this.width/64, this.height/64, 1);
glBindSampler(0, 0);
for (int i =0;i<7;i++)
glBindTextureUnit(i, 0);
}
}
public void free() {
this.fb.free();
if (this.texture != null) {
this.texture.free();
this.texture = null;
}
glDeleteSamplers(this.sampler);
this.hizInitial.free();
this.hizMip.free();
}
public int getHizTextureId() {
return this.texture.id;
}
public int getPackedLevels() {
return ((Integer.numberOfTrailingZeros(this.width))<<16)|(Integer.numberOfTrailingZeros(this.height));//+1
}
}

View File

@@ -15,6 +15,6 @@ import static org.lwjgl.opengl.GL45.glBindTextureUnit;
public class LightMapHelper {
public static void bind(int lightingIndex) {
glBindSampler(lightingIndex, 0);
glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTexture())).getGlId());
glBindTextureUnit(lightingIndex, ((net.minecraft.client.texture.GlTexture)(MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().getGlTextureView().texture())).getGlId());
}
}

View File

@@ -1,27 +0,0 @@
package me.cortex.voxy.client.mixin.minecraft;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BackgroundRenderer;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.Fog;
import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Inject;
@Mixin(BackgroundRenderer.class)
public class MixinBackgroundRenderer {
@WrapMethod(method = "applyFog")
private static Fog voxy$overrideFog(Camera camera, BackgroundRenderer.FogType fogType, Vector4f color, float viewDistance, boolean thickenFog, float tickProgress, Operation<Fog> original) {
var vrs = (IGetVoxyRenderSystem)MinecraftClient.getInstance().worldRenderer;
if (VoxyConfig.CONFIG.renderVanillaFog || vrs == null || vrs.getVoxyRenderSystem() == null) {
return original.call(camera, fogType, color, viewDistance, thickenFog, tickProgress);
} else {
return Fog.DUMMY;
}
}
}

View File

@@ -0,0 +1,39 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.ICheekyClientChunkManager;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.common.world.service.VoxelIngestService;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.world.ClientChunkManager;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientChunkManager.class)
public class MixinClientChunkManager implements ICheekyClientChunkManager {
@Unique
private static final boolean BOBBY_INSTALLED = FabricLoader.getInstance().isModLoaded("bobby");
@Shadow volatile ClientChunkManager.ClientChunkMap chunks;
@Override
public WorldChunk voxy$cheekyGetChunk(int x, int z) {
//This doesnt do the in range check stuff, it just gets the chunk at all costs
return this.chunks.getChunk(this.chunks.getIndex(x, z));
}
@Inject(method = "unload", at = @At("HEAD"))
public void voxy$captureChunkBeforeUnload(ChunkPos pos, CallbackInfo ci) {
if (VoxyConfig.CONFIG.ingestEnabled && BOBBY_INSTALLED) {
var chunk = this.voxy$cheekyGetChunk(pos.x, pos.z);
if (chunk != null) {
VoxelIngestService.tryAutoIngestChunk(chunk);
}
}
}
}

View File

@@ -18,13 +18,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.time.Duration;
import java.util.function.Consumer;
@Mixin(ClientLoginNetworkHandler.class)
@Mixin(ClientPlayNetworkHandler.class)
public class MixinClientLoginNetworkHandler {
@Inject(method = "<init>", at = @At(value = "TAIL"))
private void voxy$init(ClientConnection connection, MinecraftClient client, ServerInfo serverInfo, Screen parentScreen, boolean newWorld, Duration worldLoadTime, Consumer statusConsumer, CookieStorage cookieStorage, CallbackInfo ci) {
@Inject(method = "onGameJoin", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;<init>(Lnet/minecraft/client/MinecraftClient;Lnet/minecraft/client/network/ClientPlayNetworkHandler;)V", shift = At.Shift.BY, by = 2))
private void voxy$init(GameJoinS2CPacket packet, CallbackInfo ci) {
if (VoxyCommon.isAvailable()) {
VoxyClientInstance.isInGame = true;
if (VoxyConfig.CONFIG.enabled) {
if (VoxyCommon.getInstance() != null) {
VoxyCommon.shutdownInstance();
}
VoxyCommon.createInstance();
}
}

View File

@@ -0,0 +1,30 @@
package me.cortex.voxy.client.mixin.minecraft;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.fog.FogData;
import net.minecraft.client.render.fog.FogRenderer;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(FogRenderer.class)
public class MixinFogRenderer {
@Redirect(method = "applyFog(Lnet/minecraft/client/render/Camera;IZLnet/minecraft/client/render/RenderTickCounter;FLnet/minecraft/client/world/ClientWorld;)Lorg/joml/Vector4f;", at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/fog/FogData;renderDistanceEnd:F", opcode = Opcodes.PUTFIELD), require = 0)
private void voxy$modifyFog(FogData instance, float distance) {
var vrs = (IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer;
if (VoxyConfig.CONFIG.renderVanillaFog || vrs == null || vrs.getVoxyRenderSystem() == null) {
instance.renderDistanceEnd = distance;
} else {
instance.renderDistanceStart = 999999999;
instance.renderDistanceEnd = 999999999;
if (!VoxyConfig.CONFIG.useEnvironmentalFog) {
instance.environmentalStart = 99999999;
instance.environmentalEnd = 99999999;
}
}
}
}

View File

@@ -0,0 +1,24 @@
package me.cortex.voxy.client.mixin.nvidium;
import me.cortex.nvidium.RenderPipeline;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.client.MinecraftClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = RenderPipeline.class, remap = false)
public class MixinRenderPipeline {
@Inject(method = "renderFrame", at = @At("RETURN"))
private void voxy$injectRender(TerrainRenderPass pass, Viewport frustum, FogParameters fogParameters, ChunkRenderMatrices crm, double px, double py, double pz, CallbackInfo ci) {
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
if (renderer != null) {
renderer.renderOpaque(crm, fogParameters, px, py, pz);
}
}
}

View File

@@ -8,6 +8,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderListItera
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses;
import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass;
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
import net.caffeinemc.mods.sodium.client.util.FogParameters;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.util.math.MatrixStack;
import org.joml.Matrix4f;
@@ -20,11 +21,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public class MixinDefaultChunkRenderer {
@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer;end(Lnet/caffeinemc/mods/sodium/client/render/chunk/terrain/TerrainRenderPass;)V", shift = At.Shift.BEFORE))
private void injectRender(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, CallbackInfo ci) {
private void injectRender(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, FogParameters fogParameters, CallbackInfo ci) {
if (renderPass == DefaultTerrainRenderPasses.CUTOUT) {
var renderer = ((IGetVoxyRenderSystem) MinecraftClient.getInstance().worldRenderer).getVoxyRenderSystem();
if (renderer != null) {
renderer.renderOpaque(matrices, camera.x, camera.y, camera.z);
renderer.renderOpaque(matrices, fogParameters, camera.x, camera.y, camera.z);
}
}
}

View File

@@ -1,25 +1,23 @@
package me.cortex.voxy.client.mixin.sodium;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import me.cortex.voxy.client.VoxyClientInstance;
import me.cortex.voxy.client.ICheekyClientChunkManager;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.client.core.IGetVoxyRenderSystem;
import me.cortex.voxy.client.core.VoxyRenderSystem;
import me.cortex.voxy.common.world.WorldEngine;
import me.cortex.voxy.common.world.service.VoxelIngestService;
import me.cortex.voxy.commonImpl.VoxyCommon;
import me.cortex.voxy.commonImpl.WorldIdentifier;
import net.caffeinemc.mods.sodium.client.gl.device.CommandList;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionManager;
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.world.chunk.WorldChunk;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
@@ -27,6 +25,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = RenderSectionManager.class, remap = false)
public class MixinRenderSectionManager {
@Unique
private static final boolean BOBBY_INSTALLED = FabricLoader.getInstance().isModLoaded("bobby");
@Shadow @Final private ClientWorld level;
@Inject(method = "<init>", at = @At("TAIL"))
@@ -39,6 +40,20 @@ public class MixinRenderSectionManager {
}
}
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
private void injectIngest(int x, int z, CallbackInfo ci) {
//TODO: Am not quite sure if this is right
if (VoxyConfig.CONFIG.ingestEnabled && !BOBBY_INSTALLED) {
var cccm = (ICheekyClientChunkManager)this.level.getChunkManager();
if (cccm != null) {
var chunk = cccm.voxy$cheekyGetChunk(x, z);
if (chunk != null) {
VoxelIngestService.tryAutoIngestChunk(chunk);
}
}
}
}
/*
@Inject(method = "onChunkAdded", at = @At("HEAD"))
private void voxy$trackChunkAdd(int x, int z, CallbackInfo ci) {
@@ -61,14 +76,6 @@ public class MixinRenderSectionManager {
}
}*/
@Inject(method = "onChunkRemoved", at = @At("HEAD"))
private void injectIngest(int x, int z, CallbackInfo ci) {
//TODO: Am not quite sure if this is right
if (VoxyConfig.CONFIG.ingestEnabled) {
VoxelIngestService.tryAutoIngestChunk(this.level.getChunk(x, z));
}
}
@Redirect(method = "updateSectionInfo", at = @At(value = "INVOKE", target = "Lnet/caffeinemc/mods/sodium/client/render/chunk/RenderSection;setInfo(Lnet/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionInfo;)Z"))
private boolean voxy$updateOnUpload(RenderSection instance, BuiltSectionInfo info) {
boolean wasBuilt = instance.isBuilt();

View File

@@ -7,10 +7,10 @@ import me.cortex.voxy.common.world.SaveLoadSystem;
import java.lang.ref.Cleaner;
import static me.cortex.voxy.common.util.GlobalCleaner.CLEANER;
import static org.lwjgl.util.zstd.Zstd.*;
public class ZSTDCompressor implements StorageCompressor {
private static final Cleaner CLEANER = Cleaner.create();
private record Ref(long ptr) {}
private static Ref createCleanableCompressionContext() {

View File

@@ -59,7 +59,7 @@ public class ServiceSlice extends TrackedObject {
//Check that we are still alive
if (!this.alive) {
if (this.activeCount.decrementAndGet() < 0) {
throw new IllegalStateException("Alive count negative!");
throw new IllegalStateException("Alive count negative!:" + this.name);
}
return true;
}
@@ -77,14 +77,14 @@ public class ServiceSlice extends TrackedObject {
try {
ctx.run();
} catch (Exception e) {
Logger.error("Unexpected error occurred while executing a service job, expect things to break badly", e);
Logger.error("Unexpected error occurred while executing a service job, expect things to break badly: " + this.name, e);
MinecraftClient.getInstance().execute(()->MinecraftClient.getInstance().player.sendMessage(Text.literal("A voxy service had an exception while executing please check logs and report error"), true));
} finally {
if (this.activeCount.decrementAndGet() < 0) {
throw new IllegalStateException("Alive count negative!");
throw new IllegalStateException("Alive count negative!: " + this.name);
}
if (this.jobCount2.decrementAndGet() < 0) {
throw new IllegalStateException("Job count negative!");
throw new IllegalStateException("Job count negative!" + this.name);
}
}
return true;
@@ -96,9 +96,10 @@ public class ServiceSlice extends TrackedObject {
Logger.error("Tried to do work on a dead service: " + this.name, new Throwable());
return;
}
this.jobCount.release();
this.threadPool.addWeight(this);
this.jobCount2.incrementAndGet();
this.threadPool.execute(this);
this.jobCount.release();
this.threadPool.execute();
}
public void shutdown() {
@@ -145,7 +146,7 @@ public class ServiceSlice extends TrackedObject {
public void blockTillEmpty() {
while (this.activeCount.get() != 0 && this.alive) {
while (this.jobCount2.get() != 0 && this.alive) {
while ((this.jobCount2.get() != 0 || this.jobCount.availablePermits()!=0) && this.alive) {
Thread.onSpinWait();
try {
Thread.sleep(10);
@@ -163,7 +164,7 @@ public class ServiceSlice extends TrackedObject {
return false;
}
if (this.jobCount2.decrementAndGet() < 0) {
throw new IllegalStateException("Job count negative!!!");
throw new IllegalStateException("Job count negative!!!:" + this.name);
}
this.threadPool.steal(this, 1);
return true;
@@ -176,7 +177,7 @@ public class ServiceSlice extends TrackedObject {
}
if (this.jobCount2.addAndGet(-count) < 0) {
throw new IllegalStateException("Job count negative!!!");
throw new IllegalStateException("Job count negative!!!:" + this.name);
}
this.threadPool.steal(this, count);
return count;

View File

@@ -29,7 +29,7 @@ public class ServiceThreadPool {
private final ThreadGroup threadGroup;
public ServiceThreadPool(int threadCount) {
this(threadCount, 1);//Maybe change to 3
this(threadCount, 3);//Maybe change to 3
}
public ServiceThreadPool(int threadCount, int priority) {
@@ -47,7 +47,7 @@ public class ServiceThreadPool {
CpuLayout.setThreadAffinity(CpuLayout.CORES[2 + (threadId % (CpuLayout.CORES.length - 2))]);
}
if (threadId != 0) {
ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_PRIORITY_LOWEST);
ThreadUtils.SetSelfThreadPriorityWin32(-1);
//ThreadUtils.SetSelfThreadPriorityWin32(ThreadUtils.WIN32_THREAD_MODE_BACKGROUND_BEGIN);
}
this.worker(threadId);
@@ -137,8 +137,11 @@ public class ServiceThreadPool {
this.serviceSlices = newArr;
}
void execute(ServiceSlice service) {
this.totalJobWeight.addAndGet(service.weightPerJob);
long addWeight(ServiceSlice service) {
return this.totalJobWeight.addAndGet(service.weightPerJob);
}
void execute() {
this.jobCounter.release(1);
}
@@ -268,11 +271,19 @@ public class ServiceThreadPool {
//Consumed a job from the service, decrease weight by the amount
if (this.totalJobWeight.addAndGet(-service.weightPerJob)<0) {
throw new IllegalStateException("Total job weight is negative");
Logger.error("Total job weight is negative");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (this.totalJobWeight.get()<0) {
throw new IllegalStateException("Total job weight still negative");
}
}
//Sleep for a bit after running a job, yeild the thread
Thread.yield();
//Thread.yield();
break;
}
}

View File

@@ -0,0 +1,7 @@
package me.cortex.voxy.common.util;
import java.lang.ref.Cleaner;
public class GlobalCleaner {
public static final Cleaner CLEANER = Cleaner.create();
}

View File

@@ -1,5 +1,6 @@
package me.cortex.voxy.common.util;
import me.cortex.voxy.commonImpl.VoxyCommon;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
@@ -7,6 +8,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class MemoryBuffer extends TrackedObject {
private static final boolean TRACK_MEMORY_BUFFERS = VoxyCommon.isVerificationFlagOn("trackBuffers");
public final long address;
public final long size;
private final boolean freeable;
@@ -21,7 +24,7 @@ public class MemoryBuffer extends TrackedObject {
}
private MemoryBuffer(boolean track, long address, long size, boolean freeable) {
super(track);
super(track && TRACK_MEMORY_BUFFERS);
this.tracked = track;
this.size = size;
this.address = address;

View File

@@ -7,13 +7,9 @@ import java.util.*;
public class MultiGson {
private final List<Class<?>> classes;
private final Gson GSON = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.create();
private MultiGson(List<Class<?>> classes) {
private final Gson gson;
private MultiGson(Gson gson, List<Class<?>> classes) {
this.gson = gson;
this.classes = classes;
}
@@ -38,27 +34,38 @@ public class MultiGson {
var json = new JsonObject();
for (Object entry : map) {
GSON.toJsonTree(entry).getAsJsonObject().asMap().forEach((i,j) -> {
this.gson.toJsonTree(entry).getAsJsonObject().asMap().forEach((i,j) -> {
if (json.has(i)) {
throw new IllegalArgumentException("Duplicate name inside unified json: " + i);
}
json.add(i, j);
});
}
return GSON.toJson(json);
return this.gson.toJson(json);
}
public Map<Class<?>, Object> fromJson(String json) {
var obj = GSON.fromJson(json, JsonObject.class);
var obj = this.gson.fromJson(json, JsonObject.class);
LinkedHashMap<Class<?>, Object> objects = new LinkedHashMap<>();
for (var cls : this.classes) {
objects.put(cls, GSON.fromJson(obj, cls));
objects.put(cls, this.gson.fromJson(obj, cls));
}
return objects;
}
public static class Builder {
private final LinkedHashSet<Class<?>> classes = new LinkedHashSet<>();
private final GsonBuilder gsonBuilder;
public Builder(GsonBuilder gsonBuilder) {
this.gsonBuilder = gsonBuilder;
}
public Builder() {
this(new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.excludeFieldsWithModifiers(Modifier.PRIVATE));
}
public Builder add(Class<?> clz) {
if (!this.classes.add(clz)) {
throw new IllegalArgumentException("Class has already been added");
@@ -67,7 +74,7 @@ public class MultiGson {
}
public MultiGson build() {
return new MultiGson(new ArrayList<>(this.classes));
return new MultiGson(this.gsonBuilder.create(), new ArrayList<>(this.classes));
}
}

View File

@@ -2,8 +2,9 @@ package me.cortex.voxy.common.util;
import java.lang.ref.Cleaner;
import static me.cortex.voxy.common.util.GlobalCleaner.CLEANER;
public class ThreadLocalMemoryBuffer {
private static final Cleaner CLEANER = Cleaner.create();
private static MemoryBuffer createMemoryBuffer(long size) {
var buffer = new MemoryBuffer(size);
var ref = MemoryBuffer.createUntrackedUnfreeableRawFrom(buffer.address, buffer.size);

View File

@@ -13,7 +13,7 @@ public class ThreadUtils {
public static final int WIN32_THREAD_PRIORITY_LOWEST = -2;
public static final int WIN32_THREAD_MODE_BACKGROUND_BEGIN = 0x00010000;
public static final int WIN32_THREAD_MODE_BACKGROUND_END = 0x00020000;
private static final boolean isWindows = Platform.get() == Platform.WINDOWS;
public static final boolean isWindows = Platform.get() == Platform.WINDOWS;
private static final long SetThreadPriority;
private static final long SetThreadSelectedCpuSetMasks;
private static final long schedSetaffinity;

View File

@@ -5,9 +5,11 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
import java.lang.ref.Cleaner;
import static me.cortex.voxy.common.util.GlobalCleaner.CLEANER;
public abstract class TrackedObject {
//TODO: maybe make this false? for performance overhead?
public static final boolean TRACK_OBJECT_ALLOCATIONS = VoxyCommon.isVerificationFlagOn("ensureTrackedObjectsAreFreed");
public static final boolean TRACK_OBJECT_ALLOCATIONS = VoxyCommon.isVerificationFlagOn("ensureTrackedObjectsAreFreed", true);
public static final boolean TRACK_OBJECT_ALLOCATION_STACKS = VoxyCommon.isVerificationFlagOn("trackObjectAllocationStacks");
private final Ref ref;
@@ -49,14 +51,6 @@ public abstract class TrackedObject {
public record Ref(Cleaner.Cleanable cleanable, boolean[] freedRef) {}
private static final Cleaner cleaner;
static {
if (TRACK_OBJECT_ALLOCATIONS) {
cleaner = Cleaner.create();
} else {
cleaner = null;
}
}
public static Ref register(boolean track, Object obj) {
boolean[] freed = new boolean[1];
Cleaner.Cleanable cleanable = null;
@@ -69,7 +63,7 @@ public abstract class TrackedObject {
} else {
trace = null;
}
cleanable = cleaner.register(obj, () -> {
cleanable = CLEANER.register(obj, () -> {
if (!freed[0]) {
Logger.error("Object named: " + clazz + " was not freed, location at:\n", trace==null?"Enable allocation stack tracing":trace);
}

View File

@@ -1,5 +0,0 @@
package me.cortex.voxy.common.util;
public class VolatileHolder <T> {
public volatile T obj;
}

View File

@@ -10,6 +10,7 @@ public class VoxelizedSection {
public int x;
public int y;
public int z;
public int lvl0NonAirCount;
public final long[] section;
public VoxelizedSection(long[] section) {
this.section = section;
@@ -51,6 +52,7 @@ public class VoxelizedSection {
}
public VoxelizedSection zero() {
this.lvl0NonAirCount = 0;
Arrays.fill(this.section, 0);
return this;
}

View File

@@ -143,7 +143,7 @@ public class WorldConversionFactory {
}
int nonZeroCnt = 0;
if (blockContainer.data.storage instanceof PackedIntegerArray bStor) {
var bDat = bStor.getData();
int iterPerLong = (64 / bStor.getElementBits()) - 1;
@@ -168,7 +168,7 @@ public class WorldConversionFactory {
sample >>>= eBits;
byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF);
nonZeroCnt += (bId != 0)?1:0;
data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]);
}
} else {
@@ -181,12 +181,14 @@ public class WorldConversionFactory {
data[i] = Mapper.airWithLight(lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF));
}
} else {
nonZeroCnt = 4096;
for (int i = 0; i <= 0xFFF; i++) {
byte light = lightSupplier.supply(i&0xF, (i>>8)&0xF, (i>>4)&0xF);
data[i] = Mapper.composeMappingId(light, bId, biomes[Integer.compress(i,0b1100_1100_1100)]);
}
}
}
section.lvl0NonAirCount = nonZeroCnt;
return section;
}

View File

@@ -3,14 +3,13 @@ package me.cortex.voxy.common.world;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.VolatileHolder;
import me.cortex.voxy.common.world.other.Mapper;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.StampedLock;
public class ActiveSectionTracker {
@@ -19,6 +18,21 @@ public class ActiveSectionTracker {
public interface SectionLoader {int load(WorldSection section);}
//Loaded section world cache, TODO: get rid of VolatileHolder and use something more sane
private static final class VolatileHolder <T> {
private static final VarHandle PRE_ACQUIRE_COUNT;
private static final VarHandle POST_ACQUIRE_COUNT;
static {
try {
PRE_ACQUIRE_COUNT = MethodHandles.lookup().findVarHandle(VolatileHolder.class, "preAcquireCount", int.class);
POST_ACQUIRE_COUNT = MethodHandles.lookup().findVarHandle(VolatileHolder.class, "postAcquireCount", int.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public volatile int preAcquireCount;
public volatile int postAcquireCount;
public volatile T obj;
}
private final AtomicInteger loadedSections = new AtomicInteger();
private final Long2ObjectOpenHashMap<VolatileHolder<WorldSection>>[] loadedSectionCache;
@@ -96,9 +110,13 @@ public class ActiveSectionTracker {
if (isLoader) {
this.loadedSections.incrementAndGet();
long stamp2 = lock.readLock();
long stamp = this.lruLock.writeLock();
section = this.lruSecondaryCache.remove(key);
this.lruLock.unlockWrite(stamp);
lock.unlockRead(stamp2);
} else {
VolatileHolder.PRE_ACQUIRE_COUNT.getAndAdd(holder, 1);
}
//If this thread was the one to create the reference then its the thread to load the section
@@ -128,8 +146,12 @@ public class ActiveSectionTracker {
} else {
section.primeForReuse();
}
int preAcquireCount = (int) VolatileHolder.PRE_ACQUIRE_COUNT.getAndSet(holder, 0);
section.acquire(preAcquireCount+1);//pre acquire amount
VolatileHolder.POST_ACQUIRE_COUNT.set(holder, preAcquireCount);
//TODO: mark if the section was loaded null
section.acquire();
VarHandle.storeStoreFence();//Do not reorder setting this object
holder.obj = section;
VarHandle.releaseFence();
@@ -139,6 +161,7 @@ public class ActiveSectionTracker {
}
return section;
} else {
//TODO: mark the time the loading started in nanos, then here if it has been a while, spin lock, else jump back to the executing service and do work
VarHandle.fullFence();
while ((section = holder.obj) == null) {
VarHandle.fullFence();
@@ -146,32 +169,62 @@ public class ActiveSectionTracker {
Thread.yield();
}
//lock.lock();
{//Dont think need to lock here
if (section.tryAcquire()) {
return section;
//Try to acquire a pre lock
if (0<((int)VolatileHolder.POST_ACQUIRE_COUNT.getAndAdd(holder, -1))) {
//We managed to acquire one of the pre locks, so just return the section
return section;
} else {
//lock.lock();
{//Dont think need to lock here
if (section.tryAcquire()) {
return section;
}
}
}
//lock.unlock();
//lock.unlock();
//We failed everything, try get it again
return this.acquire(key, nullOnEmpty);
//We failed everything, try get it again
return this.acquire(key, nullOnEmpty);
}
}
}
void tryUnload(WorldSection section) {
if (this.engine != null) this.engine.lastActiveTime = System.currentTimeMillis();
if (section.isDirty&&this.engine!=null) {
if (section.tryAcquire()) {
if (section.setNotDirty()) {//If the section is dirty we must enqueue for saving
this.engine.saveSection(section);
}
section.release(false);//Special
}
}
if (section.getRefCount() != 0) {
return;
}
int index = this.getCacheArrayIndex(section.key);
final var cache = this.loadedSectionCache[index];
WorldSection sec = null;
final var lock = this.locks[index];
long stamp = lock.writeLock();
{
if (section.trySetFreed()) {
VarHandle.loadLoadFence();
if (section.isDirty) {
if (section.tryAcquire()) {
if (section.setNotDirty()) {//If the section is dirty we must enqueue for saving
if (this.engine != null)
this.engine.saveSection(section);
}
section.release(false);//Special
} else {
throw new IllegalStateException("Section was dirty but is also unloaded, this is very bad");
}
}
if (section.getRefCount() == 0 && section.trySetFreed()) {
var cached = cache.remove(section.key);
var obj = cached.obj;
if (obj == null) {
throw new IllegalStateException("This should be impossible");
throw new IllegalStateException("This should be impossible: " + WorldEngine.pprintPos(section.key));
}
if (obj != section) {
throw new IllegalStateException("Removed section not the same as the referenced section in the cache: cached: " + obj + " got: " + section + " A: " + WorldSection.ATOMIC_STATE_HANDLE.get(obj) + " B: " +WorldSection.ATOMIC_STATE_HANDLE.get(section));

View File

@@ -10,6 +10,8 @@ import me.cortex.voxy.commonImpl.VoxyCommon;
import org.lwjgl.system.MemoryUtil;
public class SaveLoadSystem3 {
public static final int STORAGE_VERSION = 0;
private record SerializationCache(Long2ShortOpenHashMap lutMapCache, MemoryBuffer memoryBuffer) {
public SerializationCache() {
this(new Long2ShortOpenHashMap(512), ThreadLocalMemoryBuffer.create(WorldSection.SECTION_VOLUME*2+WorldSection.SECTION_VOLUME*8+1024));
@@ -60,6 +62,7 @@ public class SaveLoadSystem3 {
throw new IllegalStateException();
}
//TODO: note! can actually have the first (last?) byte of metadata be the storage version!
long metadata = 0;
metadata |= Integer.toUnsignedLong(LUT.size());//Bottom 2 bytes
metadata |= Byte.toUnsignedLong(section.getNonEmptyChildren())<<16;//Next byte
@@ -82,21 +85,26 @@ public class SaveLoadSystem3 {
return false;
}
long metadata = MemoryUtil.memGetLong(ptr); ptr += 8;
final long metadata = MemoryUtil.memGetLong(ptr); ptr += 8;
section.nonEmptyChildren = (byte) ((metadata>>>16)&0xFF);
int nonEmptyBlockCount = 0;
long lutBasePtr = ptr + WorldSection.SECTION_VOLUME*2;
var blockData = section.data;
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
short lutId = MemoryUtil.memGetShort(ptr); ptr+=2;
long blockId = MemoryUtil.memGetLong(lutBasePtr+Short.toUnsignedLong(lutId)*8L);
nonEmptyBlockCount += Mapper.isAir(blockId)?0:1;
blockData[i] = blockId;
final long lutBasePtr = ptr + WorldSection.SECTION_VOLUME * 2;
if (section.lvl == 0) {
int nonEmptyBlockCount = 0;
final var blockData = section.data;
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
final short lutId = MemoryUtil.memGetShort(ptr); ptr += 2;
final long blockId = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(lutId) * 8L);
nonEmptyBlockCount += Mapper.isAir(blockId) ? 0 : 1;
blockData[i] = blockId;
}
section.nonEmptyBlockCount = nonEmptyBlockCount;
} else {
final var blockData = section.data;
for (int i = 0; i < WorldSection.SECTION_VOLUME; i++) {
blockData[i] = MemoryUtil.memGetLong(lutBasePtr + Short.toUnsignedLong(MemoryUtil.memGetShort(ptr)) * 8L);ptr += 2;
}
}
section.nonEmptyBlockCount = nonEmptyBlockCount;
ptr = lutBasePtr + (metadata&0xFFFF)*8L;
ptr = lutBasePtr + (metadata & 0xFFFF) * 8L;
return true;
}
}

View File

@@ -52,10 +52,15 @@ public class WorldEngine {
public WorldEngine(SectionStorage storage, @Nullable VoxyInstance instance) {
this.instanceIn = instance;
int cacheSize = 1024;
if (Runtime.getRuntime().maxMemory()>=(1L<<32)-200<<20) {
cacheSize = 2048;
}
this.storage = storage;
this.mapper = new Mapper(this.storage);
//5 cache size bits means that the section tracker has 32 separate maps that it uses
this.sectionTracker = new ActiveSectionTracker(6, storage::loadSection, 2048, this);
this.sectionTracker = new ActiveSectionTracker(6, storage::loadSection, cacheSize, this);
}
public WorldSection acquireIfExists(int lvl, int x, int y, int z) {
@@ -118,8 +123,8 @@ public class WorldEngine {
if (this.dirtyCallback != null) {
this.dirtyCallback.accept(section, changeState);
}
if (this.saveCallback != null) {
this.saveCallback.save(this, section);
if (!section.inSaveQueue) {
section.markDirty();
}
}
@@ -181,4 +186,11 @@ public class WorldEngine {
//TODO: maybe dont need to tick the last active time?
this.lastActiveTime = System.currentTimeMillis();
}
public void saveSection(WorldSection section) {
section.setNotDirty();
if (this.saveCallback != null) {
this.saveCallback.save(this, section);
}
}
}

View File

@@ -22,12 +22,16 @@ public final class WorldSection {
static final VarHandle ATOMIC_STATE_HANDLE;
private static final VarHandle NON_EMPTY_CHILD_HANDLE;
private static final VarHandle NON_EMPTY_BLOCK_HANDLE;
private static final VarHandle IN_SAVE_QUEUE_HANDLE;
private static final VarHandle IS_DIRTY_HANDLE;
static {
try {
ATOMIC_STATE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "atomicState", int.class);
NON_EMPTY_CHILD_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyChildren", byte.class);
NON_EMPTY_BLOCK_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "nonEmptyBlockCount", int.class);
IN_SAVE_QUEUE_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "inSaveQueue", boolean.class);
IS_DIRTY_HANDLE = MethodHandles.lookup().findVarHandle(WorldSection.class, "isDirty", boolean.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
@@ -51,11 +55,12 @@ public final class WorldSection {
//Serialized states
long metadata;
long[] data = null;
volatile int nonEmptyBlockCount = 0;
volatile int nonEmptyBlockCount = 0;//Note: only needed for level 0 sections
volatile byte nonEmptyChildren;
final ActiveSectionTracker tracker;
public final AtomicBoolean inSaveQueue = new AtomicBoolean();
volatile boolean inSaveQueue;
volatile boolean isDirty;
//When the first bit is set it means its loaded
@SuppressWarnings("all")
@@ -120,7 +125,7 @@ public final class WorldSection {
public int acquire(int count) {
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, count<<1)) + (count<<1);
if ((state & 1) == 0) {
throw new IllegalStateException("Tried to acquire unloaded section");
throw new IllegalStateException("Tried to acquire unloaded section: " + WorldEngine.pprintPos(this.key));
}
return state>>1;
}
@@ -131,6 +136,10 @@ public final class WorldSection {
//TODO: add the ability to hint to the tracker that yes the section is unloaded, try to cache it in a secondary cache since it will be reused/needed later
public int release() {
return release(true);
}
int release(boolean unload) {
int state = ((int) ATOMIC_STATE_HANDLE.getAndAdd(this, -2)) - 2;
if (state < 1) {
throw new IllegalStateException("Section got into an invalid state");
@@ -138,7 +147,7 @@ public final class WorldSection {
if ((state & 1) == 0) {
throw new IllegalStateException("Tried releasing a freed section");
}
if ((state>>1)==0) {
if ((state>>1)==0 && unload) {
if (this.tracker != null) {
this.tracker.tryUnload(this);
} else {
@@ -271,4 +280,20 @@ public final class WorldSection {
public static WorldSection _createRawUntrackedUnsafeSection(int lvl, int x, int y, int z) {
return new WorldSection(lvl, x, y, z, null);
}
public boolean exchangeIsInSaveQueue(boolean state) {
return ((boolean) IN_SAVE_QUEUE_HANDLE.compareAndExchange(this, !state, state)) == !state;
}
public void markDirty() {
IS_DIRTY_HANDLE.getAndSet(this, true);
}
public boolean setNotDirty() {
return (boolean) IS_DIRTY_HANDLE.getAndSet(this, false);
}
public boolean isFreed() {
return (((int)ATOMIC_STATE_HANDLE.get(this))&1)==0;
}
}

View File

@@ -6,7 +6,6 @@ import me.cortex.voxy.common.world.other.Mapper;
import static me.cortex.voxy.common.world.WorldEngine.*;
public class WorldUpdater {
//TODO: move this to auxilery class so that it can take into account larger than 4 mip levels
//Executes an update to the world and automatically updates all the parent mip layers up to level 4 (e.g. where 1 chunk section is 1 block big)
//NOTE: THIS RUNS ON THE THREAD IT WAS EXECUTED ON, when this method exits, the calling method may assume that VoxelizedSection is no longer needed
@@ -23,7 +22,7 @@ public class WorldUpdater {
WorldSection previousSection = null;
final var vdat = section.section;
for (int lvl = 0; lvl < MAX_LOD_LAYER+1; lvl++) {
for (int lvl = 0; lvl <= MAX_LOD_LAYER; lvl++) {
var worldSection = into.acquire(lvl, section.x >> (lvl + 1), section.y >> (lvl + 1), section.z >> (lvl + 1));
int emptinessStateChange = 0;
@@ -41,36 +40,59 @@ public class WorldUpdater {
int by = (section.y&msk)<<(4-lvl);
int bz = (section.z&msk)<<(4-lvl);
int nonAirCountDelta = 0;
int airCount = 0;
boolean didStateChange = false;
//TODO: remove the nonAirCountDelta stuff if level != 0
{//Do a bunch of funny math
var secD = worldSection.data;
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
int baseSec = bx | (bz << 5) | (by << 10);
if (lvl == 0) {
final int secMsk = 0b1100|(0xf << 5) | (0xf << 10);
final int iSecMsk1 = (~secMsk) + 1;
int secMsk = 0xF >> lvl;
secMsk |= (secMsk << 5) | (secMsk << 10);
int iSecMsk1 =(~secMsk)+1;
int secIdx = 0;
//TODO: manually unroll and do e.g. 4 iterations per loop
for (int i = 0; i <= 0xFFF; i+=4) {
int cSecIdx = secIdx + baseSec;
secIdx = (secIdx + iSecMsk1) & secMsk;
int secIdx = 0;
//TODO: manually unroll and do e.g. 4 iterations per loop
for (int i = baseVIdx; i <= (0xFFF >> (lvl * 3)) + baseVIdx; i++) {
int cSecIdx = secIdx+baseSec;
secIdx = (secIdx + iSecMsk1)&secMsk;
long oldId0 = secD[cSecIdx+0]; secD[cSecIdx+0] = vdat[i+0];
long oldId1 = secD[cSecIdx+1]; secD[cSecIdx+1] = vdat[i+1];
long oldId2 = secD[cSecIdx+2]; secD[cSecIdx+2] = vdat[i+2];
long oldId3 = secD[cSecIdx+3]; secD[cSecIdx+3] = vdat[i+3];
long newId = vdat[i];
long oldId = secD[cSecIdx]; secD[cSecIdx] = newId;
nonAirCountDelta += (Mapper.isAir(newId)?0:1)-(Mapper.isAir(oldId)?0:1);//its 0:1 cause its nonAir
didStateChange |= newId != oldId;
airCount += Mapper.isAir(oldId0)?1:0; didStateChange |= vdat[i+0] != oldId0;
airCount += Mapper.isAir(oldId1)?1:0; didStateChange |= vdat[i+1] != oldId1;
airCount += Mapper.isAir(oldId2)?1:0; didStateChange |= vdat[i+2] != oldId2;
airCount += Mapper.isAir(oldId3)?1:0; didStateChange |= vdat[i+3] != oldId3;
}
} else {
int baseVIdx = VoxelizedSection.getBaseIndexForLevel(lvl);
int secMsk = 0xF >> lvl;
secMsk |= (secMsk << 5) | (secMsk << 10);
int iSecMsk1 = (~secMsk) + 1;
int secIdx = 0;
//TODO: manually unroll and do e.g. 4 iterations per loop
for (int i = baseVIdx; i <= (0xFFF >> (lvl * 3)) + baseVIdx; i++) {
int cSecIdx = secIdx + baseSec;
secIdx = (secIdx + iSecMsk1) & secMsk;
long newId = vdat[i];
long oldId = secD[cSecIdx];
didStateChange |= newId != oldId;
secD[cSecIdx] = newId;
}
}
}
if (nonAirCountDelta != 0) {
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
if (lvl == 0) {
if (lvl == 0) {
int nonAirCountDelta = section.lvl0NonAirCount-(4096-airCount);
if (nonAirCountDelta != 0) {
worldSection.addNonEmptyBlockCount(nonAirCountDelta);
emptinessStateChange = worldSection.updateLvl0State() ? 2 : 0;
}
}

View File

@@ -7,6 +7,7 @@ import me.cortex.voxy.common.config.section.SectionStorage;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.LeavesBlock;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtOps;
@@ -54,9 +55,8 @@ public class Mapper {
public static boolean isAir(long id) {
int bId = getBlockId(id);
//Note: air can mean void, cave or normal air, as the block state is remapped during ingesting
return bId == 0;
return (id&(((1L<<20)-1)<<27)) == 0;
}
public static int getBlockId(long id) {
@@ -299,7 +299,12 @@ public class Mapper {
public StateEntry(int id, BlockState state) {
this.id = id;
this.state = state;
this.opacity = state.getOpacity();
//Override opacity of leaves to be solid
if (state.getBlock() instanceof LeavesBlock) {
this.opacity = 15;
} else {
this.opacity = state.getOpacity();
}
}
public byte[] serialize() {

View File

@@ -28,8 +28,9 @@ public class SectionSavingService {
var section = task.section;
section.assertNotFree();
try {
section.inSaveQueue.set(false);
task.engine.storage.saveSection(section);
if (section.exchangeIsInSaveQueue(false)) {
task.engine.storage.saveSection(section);
}
} catch (Exception e) {
Logger.error("Voxy saver had an exception while executing please check logs and report error", e);
}
@@ -47,7 +48,7 @@ public class SectionSavingService {
public void enqueueSave(WorldEngine in, WorldSection section) {
//If its not enqueued for saving then enqueue it
if (!section.inSaveQueue.getAndSet(true)) {
if (section.exchangeIsInSaveQueue(true)) {
//Acquire the section for use
section.acquire();

View File

@@ -16,6 +16,7 @@ import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkNibbleArray;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.light.LightStorage;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ConcurrentLinkedDeque;
@@ -93,8 +94,7 @@ public class VoxelIngestService {
throw new IllegalStateException("Tried inserting chunk into WorldEngine that was not alive");
}
var lightingProvider = chunk.getWorld().getLightingProvider();
var blp = lightingProvider.get(LightType.BLOCK);
var slp = lightingProvider.get(LightType.SKY);
boolean gotLighting = false;
int i = chunk.getBottomSectionCoord() - 1;
for (var section : chunk.getSectionArray()) {
@@ -102,23 +102,41 @@ public class VoxelIngestService {
if (section == null || !shouldIngestSection(section, chunk.getPos().x, i, chunk.getPos().z)) continue;
//if (section.isEmpty()) continue;
var pos = ChunkSectionPos.from(chunk.getPos(), i);
if (lightingProvider.getStatus(LightType.SKY, pos) != LightStorage.Status.LIGHT_AND_DATA && lightingProvider.getStatus(LightType.BLOCK, pos) != LightStorage.Status.LIGHT_AND_DATA)
continue;
gotLighting = true;
}
if (!gotLighting) {
return;
}
var blp = lightingProvider.get(LightType.BLOCK);
var slp = lightingProvider.get(LightType.SKY);
i = chunk.getBottomSectionCoord() - 1;
for (var section : chunk.getSectionArray()) {
i++;
if (section == null || !shouldIngestSection(section, chunk.getPos().x, i, chunk.getPos().z)) continue;
//if (section.isEmpty()) continue;
var pos = ChunkSectionPos.from(chunk.getPos(), i);
var bl = blp.getLightSection(pos);
if (!(bl == null || bl.isUninitialized())) {
if (bl != null) {
bl = bl.copy();
} else {
bl = null;
}
var sl = slp.getLightSection(pos);
if (!(sl == null || sl.isUninitialized())) {
sl = sl.copy();
} else {
sl = null;
}
if ((bl == null && sl == null) && section.isEmpty()) {
continue;
var sl = slp.getLightSection(pos);
if (sl != null) {
sl = sl.copy();
}
//If its null for either, assume failure to obtain lighting and ignore section
//if (blNone && slNone) {
// continue;
//}
this.ingestQueue.add(new IngestSection(chunk.getPos().x, i, chunk.getPos().z, engine, section, bl, sl));
try {
this.threads.execute();

View File

@@ -7,6 +7,8 @@ import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import java.lang.invoke.VarHandle;
public class VoxyCommon implements ModInitializer {
public static final String MOD_VERSION;
public static final boolean IS_DEDICATED_SERVER;
@@ -30,9 +32,12 @@ public class VoxyCommon implements ModInitializer {
}
//This is hardcoded like this because people do not understand what they are doing
private static final boolean GlobalVerificationDisableOverride = true;//System.getProperty("voxy.verificationDisableOverride", "false").equals("true");
public static boolean isVerificationFlagOn(String name) {
return (!GlobalVerificationDisableOverride) && System.getProperty("voxy."+name, "true").equals("true");
return isVerificationFlagOn(name, false);
}
public static boolean isVerificationFlagOn(String name, boolean defaultOn) {
return System.getProperty("voxy."+name, defaultOn?"true":"false").equals("true");
}
public static void breakpoint() {
@@ -61,8 +66,9 @@ public class VoxyCommon implements ModInitializer {
public static void shutdownInstance() {
if (INSTANCE != null) {
INSTANCE.shutdown();
INSTANCE = null;
var instance = INSTANCE;
INSTANCE = null;//Make it null before shutdown
instance.shutdown();
}
}

View File

@@ -73,6 +73,7 @@ public abstract class VoxyInstance {
// note, the reference count should be separate from the number of active chunks to prevent many issues
// a world is no longer active once it has no reference counts and no active chunks associated with it
public WorldEngine getNullable(WorldIdentifier identifier) {
if (!this.isRunning) return null;
var cache = identifier.cachedEngineObject;
WorldEngine world;
if (cache == null) {
@@ -109,11 +110,21 @@ public abstract class VoxyInstance {
}
public WorldEngine getOrCreate(WorldIdentifier identifier) {
if (!this.isRunning) {
Logger.error("Tried getting world object on voxy instance but its not running");
return null;
}
var world = this.getNullable(identifier);
if (world != null) {
return world;
}
long stamp = this.activeWorldLock.writeLock();
if (!this.isRunning) {
Logger.error("Tried getting world object on voxy instance but its not running");
return null;
}
world = this.activeWorlds.get(identifier);
if (world == null) {
//Create world here
@@ -128,10 +139,13 @@ public abstract class VoxyInstance {
protected abstract SectionStorage createStorage(WorldIdentifier identifier);
private WorldEngine createWorld(WorldIdentifier identifier) {
if (!this.isRunning) {
throw new IllegalStateException("Cannot create world while not running");
}
if (this.activeWorlds.containsKey(identifier)) {
throw new IllegalStateException("Existing world with identifier");
}
Logger.info("Creating new world engine: " + identifier.getLongHash());
Logger.info("Creating new world engine: " + identifier.getLongHash() + "@" + System.identityHashCode(this));
var world = new WorldEngine(this.createStorage(identifier), this);
world.setSaveCallback(this.savingService::enqueueSave);
this.activeWorlds.put(identifier, world);

View File

@@ -40,13 +40,19 @@ public class WorldIdentifier {
if (obj instanceof WorldIdentifier other) {
return other.hashCode == this.hashCode &&
other.biomeSeed == this.biomeSeed &&
other.key == this.key &&//other.key.equals(this.key) &&
other.dimension == this.dimension//other.dimension.equals(this.dimension)
equal(other.key, this.key) &&//other.key.equals(this.key) &&
equal(other.dimension, this.dimension)//other.dimension.equals(this.dimension)
;
}
return false;
}
private static <T> boolean equal(RegistryKey<T> a, RegistryKey<T> b) {
if (a == b) return true;
if (a == null || b == null) return false;
return a.getRegistry().equals(b.getRegistry()) && a.getValue().equals(b.getValue());
}
//Quick access utility method to get or create a world object in the current instance
public WorldEngine getOrCreateEngine() {
var instance = VoxyCommon.getInstance();

View File

@@ -0,0 +1,121 @@
package me.cortex.voxy.commonImpl.configuration;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import me.cortex.voxy.client.config.VoxyConfig;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.common.util.MultiGson;
import me.cortex.voxy.common.util.cpu.CpuLayout;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.fabricmc.loader.api.FabricLoader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
public class VoxyConfigStore {
private final MultiGson gson;
private VoxyConfigStore(Object... defaultValues) {
var gb = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.excludeFieldsWithModifiers(Modifier.PRIVATE);
Map<Class<?>, Object> defaultValueMap = new HashMap<>();
var mgb = new MultiGson.Builder(gb);
for (var i : defaultValues) {
mgb.add(i.getClass());
defaultValueMap.put(i.getClass(), i);
}
gb.registerTypeAdapterFactory(new TypeAdapterFactory() {
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (defaultValueMap.containsKey(typeToken.getRawType())) {
var defVal = (T)defaultValueMap.get(typeToken.getRawType());
var adapter = gson.getDelegateAdapter(this, typeToken);
return new TypeAdapter<T>() {
@Override
public void write(JsonWriter writer, T obj) throws IOException {
var defJson = adapter.toJsonTree(defVal).getAsJsonObject();
var val = adapter.toJsonTree(obj).getAsJsonObject();
for (var key : defJson.keySet()) {
if (val.has(key)) {
if (defJson.get(key).equals(val.get(key))) {
val.addProperty(key, "DEFAULT_VALUE");
}
}
}
gson.toJson(val, writer);
}
@Override
public T read(JsonReader reader) throws IOException {
var defJson = adapter.toJsonTree(defVal).getAsJsonObject();
var val = ((JsonElement)gson.fromJson(reader, JsonElement.class)).getAsJsonObject();
for (var key : defJson.keySet()) {
if (val.has(key)) {
if (val.get(key).equals(new JsonPrimitive("DEFAULT_VALUE"))) {
val.add(key, defJson.get(key));
}
}
}
return adapter.fromJsonTree(val);
}
};
}
return null;
}
});
this.gson = mgb.build();
}
/*
private static void loadOrCreate() {
if (VoxyCommon.isAvailable()) {
var path = getConfigPath();
if (Files.exists(path)) {
try (FileReader reader = new FileReader(path.toFile())) {
var conf = GSON.fromJson(reader, VoxyConfig.class);
if (conf != null) {
conf.save();
return conf;
} else {
Logger.error("Failed to load voxy config, resetting");
}
} catch (IOException e) {
Logger.error("Could not parse config", e);
}
}
var config = new VoxyConfig();
config.save();
return config;
} else {
var config = new VoxyConfig();
config.enabled = false;
config.enableRendering = false;
return config;
}
}
*/
public void save() {
try {
Files.writeString(getConfigPath(), this.gson.toJson(this));
} catch (IOException e) {
Logger.error("Failed to write config file", e);
}
}
private static Path getConfigPath() {
return FabricLoader.getInstance()
.getConfigDir()
.resolve("voxy-config.json");
}
}

View File

@@ -309,7 +309,16 @@ public class DHImporter implements IDataImporter {
if ((x+1)%16==0) {
for (int sz = 0; sz < 4; sz++) {
for (int sy = 0; sy < this.worldHeightSections; sy++) {
System.arraycopy(storage, (sz|(sy<<2))<<12, section.section, 0, 16 * 16 * 16);
{
int base = (sz|(sy<<2))<<12;
int nonAirCount = 0;
final var dat = section.section;
for (int i = 0; i < 4096; i++) {
nonAirCount += Mapper.isAir(dat[i] = storage[i+base])?0:1;
}
section.lvl0NonAirCount = nonAirCount;
}
WorldConversionFactory.mipSection(section, this.engine.getMapper());
section.setPosition(X*4+(x>>4), sy+(this.bottomOfWorld>>4), (Z*4)+sz);

View File

@@ -149,6 +149,10 @@ public class WorldImporter implements IDataImporter {
this.world.releaseRef();
this.threadPool.shutdown();
}
//Free all the remaining entries by running the lambda
while (!this.jobQueue.isEmpty()) {
this.jobQueue.poll().run();
}
}
private interface IImporterMethod <T> {
@@ -481,8 +485,11 @@ public class WorldImporter implements IDataImporter {
return;
}
var blockStates = blockStatesRes.getPartialOrThrow();
var biomes = this.biomeCodec.parse(NbtOps.INSTANCE, section.getCompound("biomes").get()).result().orElse(this.defaultBiomeProvider);
var biomes = this.defaultBiomeProvider;
var optBiomes = section.getCompound("biomes");
if (optBiomes.isPresent()) {
biomes = this.biomeCodec.parse(NbtOps.INSTANCE, optBiomes.get()).result().orElse(this.defaultBiomeProvider);
}
VoxelizedSection csec = WorldConversionFactory.convert(
SECTION_CACHE.get().setPosition(x, y, z),
this.world.getMapper(),

View File

@@ -19,6 +19,9 @@
"voxy.config.general.renderDistance": "Render distance",
"voxy.config.general.renderDistance.tooltip": "Render distance of voxy in chunks",
"voxy.config.general.environmental_fog": "Enable environmental fog",
"voxy.config.general.environmental_fog.tooltip": "Enables or disables voxy rendering environmental fog",
"voxy.config.general.vanilla_fog": "Enable vanilla fog",
"voxy.config.general.vanilla_fog.tooltip": "Enables or disables vanilla fog effect",

View File

@@ -3,6 +3,9 @@
layout(location = 0) in vec2 uv;
layout(binding = 0) uniform sampler2D depthTex;
#ifdef OUTPUT_COLOUR
layout(location=0) out vec4 colour;
#endif
void main() {
vec4 depths = textureGather(depthTex, uv, 0); // Get depth values from all surrounding texels.
@@ -10,6 +13,11 @@ void main() {
if (any(cv)) {//Patch holes (its very dodgy but should work :tm:, should clamp it to the first 3 levels)
depths = mix(vec4(0.0f), depths, cv);
}
float res = max(max(depths.x, depths.y), max(depths.z, depths.w));
gl_FragDepth = max(max(depths.x, depths.y), max(depths.z, depths.w)); // Write conservative depth.
#ifdef OUTPUT_COLOUR
colour = vec4(res);
#else
gl_FragDepth = res; // Write conservative depth.
#endif
}

View File

@@ -0,0 +1,93 @@
#version 460 core
#extension GL_KHR_shader_subgroup_arithmetic: require
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_clustered : require
//64x64 reduction
layout(local_size_x=256) in;
const uint spread[64] = {
0x11100100, 0x13120302, 0x31302120, 0x33322322, 0x15140504, 0x17160706, 0x35342524, 0x37362726,
0x51504140, 0x53524342, 0x71706160, 0x73726362, 0x55544544, 0x57564746, 0x75746564, 0x77766766,
0x19180908, 0x1b1a0b0a, 0x39382928, 0x3b3a2b2a, 0x1d1c0d0c, 0x1f1e0f0e, 0x3d3c2d2c, 0x3f3e2f2e,
0x59584948, 0x5b5a4b4a, 0x79786968, 0x7b7a6b6a, 0x5d5c4d4c, 0x5f5e4f4e, 0x7d7c6d6c, 0x7f7e6f6e,
0x91908180, 0x93928382, 0xb1b0a1a0, 0xb3b2a3a2, 0x95948584, 0x97968786, 0xb5b4a5a4, 0xb7b6a7a6,
0xd1d0c1c0, 0xd3d2c3c2, 0xf1f0e1e0, 0xf3f2e3e2, 0xd5d4c5c4, 0xd7d6c7c6, 0xf5f4e5e4, 0xf7f6e7e6,
0x99988988, 0x9b9a8b8a, 0xb9b8a9a8, 0xbbbaabaa, 0x9d9c8d8c, 0x9f9e8f8e, 0xbdbcadac, 0xbfbeafae,
0xd9d8c9c8, 0xdbdacbca, 0xf9f8e9e8, 0xfbfaebea, 0xdddccdcc, 0xdfdecfce, 0xfdfcedec, 0xfffeefee
};
uint swizzleId(uint id) {
//swizzel to z curve
return bitfieldExtract(spread[id>>2], (int(id)&3)*8, 8);
}
layout(location = 0) uniform vec2 invImSize;
layout(binding = 0) uniform sampler2D mip_0;
layout(binding = 1, r32f) uniform restrict writeonly image2D mip_1;
layout(binding = 2, r32f) uniform restrict writeonly image2D mip_2;
layout(binding = 3, r32f) uniform restrict writeonly image2D mip_3;
layout(binding = 4, r32f) uniform restrict writeonly image2D mip_4;
layout(binding = 5, r32f) uniform restrict writeonly image2D mip_5;
layout(binding = 6, r32f) uniform restrict writeonly image2D mip_6;
float getReduce2x2(ivec2 pos) {//w.r.t mip_1
vec4 data = textureGather(mip_0, vec2(pos*2+1)*invImSize);
float ret = max(max(data.x,data.y),max(data.z,data.w));
imageStore(mip_1, pos, vec4(ret));
return ret;
}
float getReduce4x4(ivec2 pos) {//w.r.t mip_2
ivec2 pos2 = pos*2;
float ret = max(max(getReduce2x2(pos2+ivec2(0,0)),getReduce2x2(pos2+ivec2(0,1))),
max(getReduce2x2(pos2+ivec2(1,0)),getReduce2x2(pos2+ivec2(1,1))));
imageStore(mip_2, pos, vec4(ret));
return ret;
}
//This is where the funny happens
// since we swizzeled the id when getting the value, our ordering within the subgroup should be z ordered
// we sadly cannot use the full subgroup reduction as wave size is 32 and we need a square pow2 values, so 16, sad beep
float getReduceWave(ivec2 pos, float value) {
float reduced;
subgroupBarrier();//Wait for active threads in subgroup
//Now do clustered reduction, with exploiting dropout
reduced = subgroupClusteredMax(value, 4);
if ((gl_SubgroupInvocationID&0x3)==0) {//root writes
imageStore(mip_3, pos>>1, vec4(reduced));
}
//could exit 3/4 of the threads here if wanted
subgroupBarrier();//Wait for active threads in subgroup
reduced = subgroupClusteredMax(value, 16);
if ((gl_SubgroupInvocationID&0xF)==0) {//root writes
imageStore(mip_4, pos>>2, vec4(reduced));
}
return reduced;
}
shared float values[16];
void main() {
uint id = swizzleId(gl_LocalInvocationID.x);
//(ivec2(gl_WorkGroupID.xy)*64+ivec2(id&0xFU, id>>4)*4)/4;
ivec2 wavePos = ivec2(gl_WorkGroupID.xy)*16+ivec2(id&0xFU, id>>4);
float value = getReduce4x4(wavePos);
value = getReduceWave(wavePos, value);//Reduced to 4x4 across all threads and warps
if ((gl_LocalInvocationID.x&0xFU)==0) {
values[gl_LocalInvocationID.x>>4] = value;
}
barrier();//Wait for all
if ((gl_LocalInvocationID.x>>2)!=0) {
return;//Discard all but 4 threads
}
uint i = gl_LocalInvocationID.x*4;
value = max(max(values[i],values[i+1]),max(values[i+2],values[i+3]));//Is funny is already in spread order
imageStore(mip_5, ivec2(gl_WorkGroupID.xy)*2+ivec2(gl_LocalInvocationID.x&1u,gl_LocalInvocationID.x>>1), vec4(value));
subgroupBarrier();
value = subgroupMax(value);
if (gl_LocalInvocationID.x==0) {
imageStore(mip_6, ivec2(gl_WorkGroupID.xy), vec4(value));
}
}

View File

@@ -1,5 +1,3 @@
#line 1
layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP;
ivec3 baseSectionPos;

View File

@@ -47,5 +47,5 @@ void main() {
dist = (TRANSLUCENT_WRITE_BASE-1)-min(dist, (TRANSLUCENT_WRITE_BASE-1));
uint drawPtr = atomicAdd(translucentCommandData[dist],1)+TRANSLUCENT_OFFSET;
writeCmd(drawPtr, drawId, extractQuadStart(meta), meta.cntA&0xFFFF);
writeCmd(drawPtr, drawId, extractQuadStart(meta), meta.b.x&0xFFFF);
}

View File

@@ -78,18 +78,18 @@ void main() {
ivec3 relative = ipos-(baseSectionPos>>detail);
uint drawId = gl_GlobalInvocationID.x;
positionBuffer[drawId] = uvec2(meta.posA, meta.posB);
positionBuffer[drawId] = extractRawPos(meta);
uvec4 counts = meta.b;
uint msk = 0;
msk |= uint(((meta.cntB &0xFFFF)!=0) && (relative.y>-1))<<0;
msk |= uint((((meta.cntB>>16)&0xFFFF)!=0) && (relative.y<1 ))<<1;
msk |= uint(((meta.cntC &0xFFFF)!=0) && (relative.z>-1))<<2;
msk |= uint((((meta.cntC>>16)&0xFFFF)!=0) && (relative.z<1 ))<<3;
msk |= uint(((meta.cntD &0xFFFF)!=0) && (relative.x>-1))<<4;
msk |= uint((((meta.cntD>>16)&0xFFFF)!=0) && (relative.x<1 ))<<5;
msk |= uint(((counts.y &0xFFFFu)!=0) && (relative.y>-1))<<0;
msk |= uint((((counts.y>>16)&0xFFFFu)!=0) && (relative.y<1 ))<<1;
msk |= uint(((counts.z &0xFFFFu)!=0) && (relative.z>-1))<<2;
msk |= uint((((counts.z>>16)&0xFFFFu)!=0) && (relative.z<1 ))<<3;
msk |= uint(((counts.w &0xFFFFu)!=0) && (relative.x>-1))<<4;
msk |= uint((((counts.w>>16)&0xFFFFu)!=0) && (relative.x<1 ))<<5;
msk |= uint(((meta.cntA>>16)&0xFFFF)!=0)<<6;
msk |= uint(((counts.x>>16)&0xFFFFu)!=0)<<6;
uint cmdCnt = bitCount(msk);
@@ -107,7 +107,7 @@ void main() {
uint count = 0;
//Translucency
count = meta.cntA&0xFFFF;
count = counts.x&0xFFFFu;
if (count != 0) {
uint tp = atomicAdd(translucentDrawCount, 1)+TRANSLUCENT_WRITE_BASE;
translucentCommandData[tp] = drawId;
@@ -123,7 +123,7 @@ void main() {
ptr += count;
//Double sided quads
count = (meta.cntA>>16)&0xFFFF;
count = (counts.x>>16)&0xFFFFu;
if (count != 0) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {
@@ -136,7 +136,7 @@ void main() {
ptr += count;
//Down
count = (meta.cntB)&0xFFFF;
count = (counts.y)&0xFFFFu;
if (((msk&(1u<<0))!=0)) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {
@@ -149,7 +149,7 @@ void main() {
ptr += count;
//Up
count = (meta.cntB>>16)&0xFFFF;
count = (counts.y>>16)&0xFFFFu;
if ((msk&(1u<<1))!=0) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {
@@ -162,7 +162,7 @@ void main() {
ptr += count;
//North
count = (meta.cntC)&0xFFFF;
count = (counts.z)&0xFFFFu;
if ((msk&(1u<<2))!=0) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {
@@ -175,7 +175,7 @@ void main() {
ptr += count;
//South
count = (meta.cntC>>16)&0xFFFF;
count = (counts.z>>16)&0xFFFFu;
if ((msk&(1u<<3))!=0) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {
@@ -188,7 +188,7 @@ void main() {
ptr += count;
//West
count = (meta.cntD)&0xFFFF;
count = (counts.w)&0xFFFFu;
if ((msk&(1u<<4))!=0) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {
@@ -201,7 +201,7 @@ void main() {
ptr += count;
//East
count = (meta.cntD>>16)&0xFFFF;
count = (counts.w>>16)&0xFFFFu;
if ((msk&(1u<<5))!=0) {
writeCmd(cmdPtr++, drawId, ptr, count);
if (renderTemporally) {

View File

@@ -32,6 +32,7 @@ void main() {
//Write to the section id, to track temporal over time (litterally just need a single bit, 1 fking bit, but no)
id = sid;
//Me when data race condition between visibilityData in the vert shader and frag shader
uint previous = visibilityData[sid]&0x7fffffffu;
bool wasVisibleLastFrame = previous==(frameId-1);
value = (frameId&0x7fffffffu)|(uint(wasVisibleLastFrame)<<31);//Encode if it was visible last frame

View File

@@ -12,46 +12,68 @@ layout(binding = 2) uniform sampler2D depthTex;
// however they are not a full block
layout(location = 0) in vec2 uv;
layout(location = 1) in flat vec2 baseUV;
layout(location = 2) in flat vec4 tinting;
layout(location = 3) in flat vec4 addin;
layout(location = 4) in flat uint flags;
layout(location = 5) in flat vec4 conditionalTinting;
layout(location = 6) in flat vec2 quadSize;
layout(location = 1) in flat uvec4 interData;
#ifdef DEBUG_RENDER
layout(location = 7) in flat uint quadDebug;
#endif
layout(location = 0) out vec4 outColour;
vec4 computeColour(vec4 colour) {
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
if ((flags&(1u<<2)) != 0 && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
colour *= conditionalTinting;
}
return (colour * tinting) + addin;
#import <voxy:lod/gl46/bindings.glsl>
vec4 uint2vec4RGBA(uint colour) {
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
}
bool useMipmaps() {
return ((flags>>1)&1u)==0u;
return (interData.x&2u)==0u;
}
bool useTinting() {
return (interData.x&4u)!=0u;
}
bool useCutout() {
return (interData.x&1u)==1u;
}
vec4 computeColour(vec4 colour) {
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
if (useTinting() && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
colour *= uint2vec4RGBA(interData.z).yzwx;
}
return (colour * uint2vec4RGBA(interData.y)) + vec4(0,0,0,float(interData.w&0xFFu)/255);
}
uint getFace() {
return (interData.w>>8)&7u;
}
vec2 getBaseUV() {
uint face = getFace();
uint modelId = interData.x>>16;
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
return modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
}
void main() {
//Tile is the tile we are in
vec2 tile;
vec2 uv2 = modf(uv, tile)*(1.0/(vec2(3.0,2.0)*256.0));
vec4 colour = vec4(1);
vec2 texPos = uv2 + baseUV;
vec4 colour;
vec2 texPos = uv2 + getBaseUV();
if (useMipmaps()) {
vec2 uvSmol = uv*(1.0/(vec2(3.0,2.0)*256.0));
vec2 dx = dFdx(uvSmol);//vec2(lDx, dDx);
vec2 dy = dFdy(uvSmol);//vec2(lDy, dDy);
colour = textureGrad(blockModelAtlas, texPos, dx, dy);
} else {
colour = texture(blockModelAtlas, texPos, -5.0);
colour = textureLod(blockModelAtlas, texPos, 0);
}
if (any(notEqual(clamp(tile, vec2(0), quadSize), tile))) {
if (any(notEqual(clamp(tile, vec2(0), vec2((interData.x>>8)&0xFu, (interData.x>>12)&0xFu)), tile))) {
discard;
}
@@ -62,7 +84,7 @@ void main() {
//Also, small quad is really fking over the mipping level somehow
if ((flags&1u) == 1 && (texture(blockModelAtlas, texPos, -16.0).a <= 0.1f)) {
if (useCutout() && (textureLod(blockModelAtlas, texPos, 0).a <= 0.1f)) {
//This is stupidly stupidly bad for divergence
//TODO: FIXME, basicly what this do is sample the exact pixel (no lod) for discarding, this stops mipmapping fucking it over
#ifndef DEBUG_RENDER
@@ -70,7 +92,9 @@ void main() {
#endif
}
outColour = computeColour(colour);
colour = computeColour(colour);
outColour = colour;
#ifdef DEBUG_RENDER

View File

@@ -15,12 +15,22 @@
//#define DEBUG_RENDER
layout(location = 0) out vec2 uv;
layout(location = 1) out flat vec2 baseUV;
layout(location = 2) out flat vec4 tinting;
layout(location = 3) out flat vec4 addin;
layout(location = 4) out flat uint flags;
layout(location = 5) out flat vec4 conditionalTinting;
layout(location = 6) out flat vec2 size;
layout(location = 1) out flat uvec4 interData;
uint packVec4(vec4 vec) {
uvec4 vec_=uvec4(vec*255)<<uvec4(24,16,8,0);
return vec_.x|vec_.y|vec_.z|vec_.w;
}
void setSizeAndFlags(uint modelId, uint _flags, ivec2 quadSize) {
interData.x = (modelId<<16) | _flags | (uint(quadSize.x-1)<<8) | (uint(quadSize.y-1)<<12);
}
void setTintingAndExtra(vec4 _tinting, uint _conditionalTinting, uint addin) {
interData.y = packVec4(_tinting);
interData.z = _conditionalTinting;
interData.w = addin;
}
#ifdef DEBUG_RENDER
layout(location = 7) out flat uint quadDebug;
@@ -37,38 +47,22 @@ ivec3 extractRelativeLodPos() {
return (ivec3(gl_BaseInstance)<<ivec3(5,14,23))>>ivec3(23);
}*/
vec4 uint2vec4RGBA(uint colour) {
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
}
vec4 getFaceSize(uint faceData) {
float EPSILON = 0.00005f;
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
//Expand the quads by a very small amount (because of the subtraction after this also becomes an implicit add)
faceOffsetsSizes.xz -= vec2(EPSILON);
//Make the end relative to the start
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
//Expand the quads by a very small amount
faceOffsetsSizes.xz -= vec2(EPSILON);
faceOffsetsSizes.yw += vec2(EPSILON);
return faceOffsetsSizes;
}
//TODO: make branchless by using ternaries i think
vec3 swizzelDataAxis(uint axis, vec3 data) {
if (axis == 0) { //Up/down
data = data.xzy;
}
//Not needed, here for readability
//if (axis == 1) {//north/south
// offset = offset.xyz;
//}
if (axis == 2) { //west/east
data = data.zxy;
}
return data;
return mix(mix(data.zxy,data.xzy,bvec3(axis==0)),data,bvec3(axis==1));
}
uint extractDetail(uvec2 encPos) {
@@ -106,15 +100,28 @@ void main() {
ivec2 quadSize = extractSize(quad);
vec4 faceSize = getFaceSize(faceData);
vec2 cQuadSize = (faceSize.yw + quadSize - 1) * vec2((cornerIdx>>1)&1, cornerIdx&1);
uv = faceSize.xz + cQuadSize;
vec3 cornerPos = extractPos(quad);
float depthOffset = extractFaceIndentation(faceData);
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
vec3 origin = vec3(((extractLoDPosition(encPos)<<lodLevel) - baseSectionPos)<<5);
vec3 pointPos = (cornerPos+swizzelDataAxis(face>>1,vec3(cQuadSize,0)))*(1<<lodLevel)+origin;
gl_Position = MVP*vec4(pointPos, 1.0);
if (cornerIdx == 1) //Only if we are the provoking vertex
{
size = vec2(quadSize-1);
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
baseUV = modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
//Generate tinting and flag data
flags = faceHasAlphaCuttout(faceData);
uint flags = faceHasAlphaCuttout(faceData);
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1
flags |= uint(any(greaterThan(quadSize, ivec2(1)))) & faceHasAlphaCuttoutOverride(faceData);
@@ -122,7 +129,7 @@ void main() {
flags |= uint(!modelHasMipmaps(model))<<1;
//Compute lighting
tinting = getLighting(extractLightId(quad));
vec4 tinting = getLighting(extractLightId(quad));
//Apply model colour tinting
uint tintColour = model.colourTint;
@@ -130,13 +137,13 @@ void main() {
tintColour = colourData[tintColour + extractBiomeId(quad)];
}
conditionalTinting = vec4(0);
uint conditionalTinting = 0;
if (tintColour != uint(-1)) {
flags |= 1u<<2;
conditionalTinting = uint2vec4RGBA(tintColour).yzwx;
conditionalTinting = tintColour;
}
addin = vec4(0.0);
uint addin = 0;
if (!isTranslucent) {
tinting.w = 0.0;
//Encode the face, the lod level and
@@ -144,7 +151,7 @@ void main() {
encodedData |= face;
encodedData |= (lodLevel<<3);
encodedData |= uint(hasAO)<<6;
addin.w = float(encodedData)/255.0;
addin = encodedData;
}
//Apply face tint
@@ -159,25 +166,12 @@ void main() {
tinting.xyz *= 0.5f;
}
}
setSizeAndFlags(modelId, flags, quadSize);
setTintingAndExtra(tinting, conditionalTinting, addin|(face<<8));
}
vec4 faceSize = getFaceSize(faceData);
vec2 cQuadSize = (faceSize.yw + quadSize - 1) * vec2((cornerIdx>>1)&1, cornerIdx&1);
uv = faceSize.xz + cQuadSize;
vec3 cornerPos = extractPos(quad);
float depthOffset = extractFaceIndentation(faceData);
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
vec3 origin = vec3(((extractLoDPosition(encPos)<<lodLevel) - baseSectionPos)<<5);
gl_Position = MVP*vec4((cornerPos+swizzelDataAxis(face>>1,vec3(cQuadSize,0)))*(1<<lodLevel)+origin, 1.0);
#ifdef DEBUG_RENDER
quadDebug = lodLevel;
#endif

View File

@@ -133,6 +133,7 @@ bool isCulledByHiz() {
float miplevel = log2(max(max(size.x, size.y),1));
miplevel = floor(miplevel)-1;
//miplevel = clamp(miplevel, 0, 6);
miplevel = clamp(miplevel, 0, textureQueryLevels(hizDepthSampler)-1);
int ml = int(miplevel);

View File

@@ -0,0 +1,112 @@
#version 460 core
#extension GL_NV_fragment_shader_barycentric : require
layout(binding = 0) uniform sampler2D blockModelAtlas;
layout(binding = 2) uniform sampler2D depthTex;
perprimitiveNV in uvec4 primData;
perprimitiveNV in vec4 uvData;
layout(location = 0) out vec4 outColour;
vec4 uint2vec4RGBA(uint colour) {
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
}
bool useMipmaps() {
return (primData.x&2u)==0u;
}
bool useTinting() {
return (primData.x&4u)!=0u;
}
bool useCutout() {
return (primData.x&1u)==1u;
}
vec4 computeColour(vec4 colour) {
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
if (useTinting() && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
colour *= uint2vec4RGBA(primData.z).yzwx;
}
return (colour * uint2vec4RGBA(primData.y)) + vec4(0,0,0,float(primData.w&0xFFu)/255);
}
uint getFace() {
return (primData.w>>8)&7u;
}
vec2 getBaseUV() {
uint face = getFace();
uint modelId = primData.x>>16;
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
return modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
}
bool isTri0() {
return (gl_PrimitiveID&(1<<31))==0;
}
void main() {
bool tri0 = isTri0();
//1,2,0
//1,3,2
//vec2((corner>>1)&1u, corner&1u)
//vec2(0,gl_BaryCoordNV.x)+vec2(gl_BaryCoordNV.y,0)+vec2(0,0);
//vec2(0,gl_BaryCoordNV.x)+vec2(gl_BaryCoordNV.y,gl_BaryCoordNV.y)+vec2(gl_BaryCoordNV.z,0);
vec2 uv = fma(mix(gl_BaryCoordNV.zx+gl_BaryCoordNV.y, gl_BaryCoordNV.yx, bvec2(tri0)), uvData.zw, uvData.xy);
//Need to interpolate
//Tile is the tile we are in
vec2 tile;
vec2 uv2 = modf(uv, tile)*(1.0/(vec2(3.0,2.0)*256.0));
vec4 colour;
vec2 texPos = uv2 + getBaseUV();
if (useMipmaps()) {
vec2 uvSmol = uv*(1.0/(vec2(3.0,2.0)*256.0));
vec2 dx = dFdx(uvSmol);//vec2(lDx, dDx);
vec2 dy = dFdy(uvSmol);//vec2(lDy, dDy);
colour = textureGrad(blockModelAtlas, texPos, dx, dy);
} else {
colour = textureLod(blockModelAtlas, texPos, 0);
}
if (any(notEqual(clamp(tile, vec2(0), vec2((primData.x>>8)&0xFu, (primData.x>>12)&0xFu)), tile))) {
discard;
}
//Check the minimum bounding texture and ensure we are greater than it
if (gl_FragCoord.z < texelFetch(depthTex, ivec2(gl_FragCoord.xy), 0).r) {
discard;
}
//Also, small quad is really fking over the mipping level somehow
if (useCutout() && (textureLod(blockModelAtlas, texPos, 0).a <= 0.1f)) {
//This is stupidly stupidly bad for divergence
//TODO: FIXME, basicly what this do is sample the exact pixel (no lod) for discarding, this stops mipmapping fucking it over
#ifndef DEBUG_RENDER
discard;
#endif
}
colour = computeColour(colour);
outColour = colour;
/*
uint hash = gl_PrimitiveID*1231421+123141;
hash ^= hash>>16;
hash = hash*1231421+123141;
hash ^= hash>>16;
hash = hash * 1827364925 + 123325621;
outColour = vec4(float(hash&15u)/15, float((hash>>4)&15u)/15, float((hash>>8)&15u)/15, 0);
*/
}

View File

@@ -0,0 +1,361 @@
#version 460 core
#extension GL_NV_mesh_shader : require
#extension GL_ARB_gpu_shader_int64 : enable
#extension GL_KHR_shader_subgroup_arithmetic: require
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_ballot : require
#extension GL_KHR_shader_subgroup_vote : require
//TODO: finetune the local size and emission size
layout(local_size_x = MESH_SIZE) in;
layout(triangles, max_vertices=(MESH_SIZE*4), max_primitives=(MESH_SIZE*2)) out;
layout(std430) taskNV in Task {
//Tightly packed, prefix sum + offset
//uvec4 binA;
//uvec4 binB;
uint bins[8];
vec3 cameraOffset;
uint lodLvl;
uint baseQuad;
uint quadCount;
} task;
perprimitiveNV out uvec4 primData[MESH_SIZE*2];
perprimitiveNV out vec4 uvData[MESH_SIZE*2];
uint getQuadId() {
uint mid = gl_GlobalInvocationID.x;
uint cv = (mid<<16)|0xFFFFu;
/*
//Funny method
uvec4 a = mix(uvec4(0), uvec4( 1, 2, 4, 8), lessThanEqual(uvec4(task.bins[0],task.bins[1],task.bins[2],task.bins[3]), uvec4(cv))) +
mix(uvec4(0), uvec4(16,32,64,128), lessThanEqual(uvec4(task.bins[4],task.bins[5],task.bins[6],task.bins[7]), uvec4(cv)));
uint act = a.x+a.y+a.z+a.w;
uint id = findLSB(act^(act>>1));
//uint point = mix(binB, binA, id<4)[id&3u];
uint point = task.bins[id];
return (point&0xFFFFu)+(mid-(point>>16));
*/
#pragma unroll
for (uint i = 0; i<7; i++) {
uint point = task.bins[i];
if (point<=cv&&cv<task.bins[i+1]) {
return (point&0xFFFFu)+(mid-(point>>16));
}
}
return -1;
/*
for (uint i = 0; i<7; i++) {
uint point = task.bins[i];
if (point <= ((mid<<16)|0xFFFFu) && ((mid<<16)|0xFFFFu)<task.bins[i+1]) {
binId = i;
return (point&0xFFFFu)+(mid-(point>>16));
}
}
return -1;
*/
}
#import <voxy:lod/quad_format.glsl>
#import <voxy:lod/block_model.glsl>
layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP;
ivec3 baseSectionPos;
uint frameId;
vec3 cameraSubPos;
uint pad_;
vec2 screenSize;
};
layout(binding = 4, std430) readonly restrict buffer QuadBuffer {
Quad quadData[];
};
layout(binding = 5, std430) readonly restrict buffer ModelBuffer {
BlockModel modelData[];
};
layout(binding = 6, std430) readonly restrict buffer ModelColourBuffer {
uint colourData[];
};
layout(binding = 1) uniform sampler2D lightSampler;
vec4 getLighting(uint index) {
int i2 = int(index);
return texture(lightSampler, clamp((vec2((i2>>4)&0xF, i2&0xF))/16, vec2(8.0f/255), vec2(248.0f/255)));
}
//===============
vec3 swizzelDataAxis(uint axis, vec3 data) {
return mix(mix(data.zxy,data.xzy,bvec3(axis==0)),data,bvec3(axis==1));
}
vec4 getFaceSize(uint faceData) {
float EPSILON = 0.00005f;
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
//Expand the quads by a very small amount (because of the subtraction after this also becomes an implicit add)
faceOffsetsSizes.xz -= vec2(EPSILON);
//Make the end relative to the start
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
return faceOffsetsSizes;
}
vec3 faceNormal(uint face) {
//TODO: optimize this garbage
return vec3(uint((face>>1)==2), uint((face>>1)==0), uint((face>>1)==1)) * (float(int(face)&1)*2-1);
}
uint packVec4(vec4 vec) {
uvec4 vec_=uvec4(vec*255)<<uvec4(24,16,8,0);
return vec_.x|vec_.y|vec_.z|vec_.w;
}
//===============
vec3 cornerPos;//Does not include cameraSubPos to get exact need to - cameraSubPos
vec2 axisFaceSize;
uint face;
vec4 faceSize;
uint modelId;
BlockModel model;
uint faceData;
bool isTranslucent;
bool hasAO;
bool isShaded;
void setup(Quad quad) {
face = extractFace(quad);
modelId = extractStateId(quad);
model = modelData[modelId];
faceData = model.faceData[face];
isTranslucent = modelIsTranslucent(model);
hasAO = modelHasMipmaps(model);//TODO: replace with per face AO flag
isShaded = hasAO;//TODO: make this a per face flag
ivec2 quadSize = extractSize(quad);
faceSize = getFaceSize(faceData);
cornerPos = extractPos(quad);
float depthOffset = extractFaceIndentation(faceData);
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
cornerPos *= (1<<task.lodLvl);
cornerPos += task.cameraOffset;
axisFaceSize = (faceSize.yw + quadSize - 1);
//uv =
}
vec2 getUvCorner(uint corner) {
return faceSize.xz + axisFaceSize*vec2((corner>>1)&1u, corner&1u);
}
uvec4 createQuadData(Quad quad) {
uint flags = faceHasAlphaCuttout(faceData);
ivec2 quadSize = extractSize(quad);
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1
flags |= uint(any(greaterThan(quadSize, ivec2(1)))) & faceHasAlphaCuttoutOverride(faceData);
flags |= uint(!modelHasMipmaps(model))<<1;
//Compute lighting
vec4 tinting = getLighting(extractLightId(quad));
//Apply model colour tinting
uint tintColour = model.colourTint;
if (modelHasBiomeLUT(model)) {
tintColour = colourData[tintColour + extractBiomeId(quad)];
}
uint conditionalTinting = 0;
if (tintColour != uint(-1)) {
flags |= 1u<<2;
conditionalTinting = tintColour;
}
uint addin = 0;
if (!isTranslucent) {
tinting.w = 0.0;
//Encode the face, the lod level and
uint encodedData = 0;
encodedData |= face;
encodedData |= (task.lodLvl<<3);
encodedData |= uint(hasAO)<<6;
addin = encodedData;
}
//Apply face tint
if (isShaded) {
//TODO: make branchless, infact apply ahead of time to the texture itself in ModelManager since that is
// per face
if ((face>>1) == 1) {//NORTH, SOUTH
tinting.xyz *= 0.8f;
} else if ((face>>1) == 2) {//EAST, WEST
tinting.xyz *= 0.6f;
} else if (face == 0) {//DOWN
tinting.xyz *= 0.5f;
}
}
uvec4 interData;
interData.x = (modelId<<16) | flags | (uint(quadSize.x-1)<<8) | (uint(quadSize.y-1)<<12);
interData.y = packVec4(tinting);
interData.z = conditionalTinting;
interData.w = addin|(face<<8);
return interData;
}
vec4 emitVertexPos(int corner) {
vec3 pointPos = swizzelDataAxis(face>>1,vec3(axisFaceSize*mix(vec2(0),vec2(1<<task.lodLvl),bvec2((corner>>1)&1, corner&1)),0))+cornerPos;
return MVP*vec4(pointPos, 1.0);
}
#ifdef HAS_STATISTICS
layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBuffer {
uint visibleSectionCounts[5];
uint quadCounts[5];
};
#endif
void main() {
if (subgroupElect()) {
gl_PrimitiveCountNV = 0;
}
if (task.quadCount<=gl_GlobalInvocationID.x) {
return;//dont emit a quad
}
uint qid = getQuadId() + task.baseQuad;
Quad quad = quadData[qid];
setup(quad);
bool render = dot(faceNormal(face), cornerPos-cameraSubPos) <= 0;
subgroupBarrier();
uint qId = subgroupExclusiveAdd(render?1:0);
if (render) {
uvec4 data = createQuadData(quad);
subgroupBarrier();
primData[qId*2] = data;
uvData[qId*2] = vec4(faceSize.xz, axisFaceSize);
primData[qId*2+1] = data;
uvData[qId*2+1] = vec4(faceSize.xz, axisFaceSize);
#define VID(i) (gl_LocalInvocationIndex*4+i)
gl_MeshVerticesNV[VID(0)].gl_Position = emitVertexPos(1);
gl_MeshVerticesNV[VID(1)].gl_Position = emitVertexPos(2);
gl_MeshVerticesNV[VID(2)].gl_Position = emitVertexPos(0);
gl_MeshVerticesNV[VID(3)].gl_Position = emitVertexPos(3);
gl_PrimitiveIndicesNV[qId*6+0] = VID(0);
gl_PrimitiveIndicesNV[qId*6+1] = VID(1);
gl_PrimitiveIndicesNV[qId*6+2] = VID(2);
gl_PrimitiveIndicesNV[qId*6+3] = VID(0);
gl_PrimitiveIndicesNV[qId*6+4] = VID(3);
gl_PrimitiveIndicesNV[qId*6+5] = VID(1);
gl_MeshPrimitivesNV[qId*2].gl_PrimitiveID = int(qid|(0u<<31));
gl_MeshPrimitivesNV[qId*2+1].gl_PrimitiveID = int(qid|(1u<<31));
/*
//vec4 p1 = ;
//vec4 p2 = ;
//vec4 p0 = emitVertexPos(0);
//vec4 p3 = emitVertexPos(3);
//Emit common
{
gl_PrimitiveIndicesNV[idxId++] = vertId_+0;
gl_PrimitiveIndicesNV[idxId++] = vertId_+1;
gl_PrimitiveIndicesNV[idxId++] = vertId;
gl_MeshVerticesNV[vertId++].gl_Position = p0;
primOut[triId].data = data;
primOut[triId].uvData = vec4(faceSize.xz, axisFaceSize);
gl_MeshPrimitivesNV[triId++].gl_PrimitiveID = int(qid|(0u<<31));
}
{
gl_PrimitiveIndicesNV[idxId++] = vertId_+0;
gl_PrimitiveIndicesNV[idxId++] = vertId;
gl_PrimitiveIndicesNV[idxId++] = vertId_+1;
gl_MeshVerticesNV[vertId++].gl_Position = p3;
primOut[triId].data = data;
primOut[triId].uvData = vec4(faceSize.xz, axisFaceSize);
gl_MeshPrimitivesNV[triId++].gl_PrimitiveID = int(qid|(1u<<31));
}
*/
subgroupBarrier();
uint count = subgroupMax(qId);
if (count != 0 && subgroupElect()) {
count = count *2+2;
gl_PrimitiveCountNV = count;
#ifdef HAS_STATISTICS
atomicAdd(quadCounts[task.lodLvl], count);
#endif
}
}
/*
uint triId = subgroupExclusiveAdd(render?2:0);
uint vertId = subgroupExclusiveAdd(render?4:0);
if (render) {
//common corners
gl_MeshVerticesNV[vertId+0].gl_Position = emitVertexPos(1);
gl_MeshVerticesNV[vertId+1].gl_Position = emitVertexPos(2);
//tri corners
gl_MeshVerticesNV[vertId+2].gl_Position = emitVertexPos(0);
gl_MeshVerticesNV[vertId+3].gl_Position = emitVertexPos(3);
//Emit tris
uint idxId = triId*3;
gl_PrimitiveIndicesNV[idxId++] = vertId+0;
gl_PrimitiveIndicesNV[idxId++] = vertId+1;
gl_PrimitiveIndicesNV[idxId++] = vertId+2;
gl_PrimitiveIndicesNV[idxId++] = vertId+0;
gl_PrimitiveIndicesNV[idxId++] = vertId+3;
gl_PrimitiveIndicesNV[idxId++] = vertId+1;
gl_MeshPrimitivesNV[triId].gl_PrimitiveID = int(qid);
gl_MeshPrimitivesNV[triId+1].gl_PrimitiveID = int(qid);
}*/
}

View File

@@ -0,0 +1,137 @@
#version 460 core
#extension GL_NV_mesh_shader : require
//TODO: maybe do 2 sections per workgroup instead of 1, this should double throughput with more complex sections
// however will require a rewrite of how the task payload functions, since we want to still keep it under 108 bytes
// in theory the maximum we can do is 4 sections in a workgroup
layout(local_size_x=1) in;
#import <voxy:lod/section.glsl>
layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP;
ivec3 baseSectionPos;
uint frameId;
vec3 cameraSubPos;
uint pad_;
vec2 screenSize;
};
layout(binding = 1, std430) restrict readonly buffer IndirectSectionLookupBuffer {
uint sectionCount;
uint indirectLookup[];
};
layout(binding = 2, std430) restrict readonly buffer SectionBuffer {
SectionMeta sectionData[];
};
layout(binding = 3, std430) restrict readonly buffer VisibilityBuffer {
uint visibilityData[];
};
#ifdef HAS_STATISTICS
layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBuffer {
uint visibleSectionCounts[5];
uint quadCounts[5];
};
#endif
taskNV out Task {
//Tightly packed, prefix sum + offset
//uvec4 binA;
//uvec4 binB;
uint bins[8];
vec3 cameraOffset;
uint lodLvl;
uint baseQuad;
uint quadCount;
} task;
#define BIN(br, cnt) if (br) { task.bins[i++] = (sum<<16)|off; sum += cnt; } off += cnt;
//#define BIN(br, cnt) if (br) { batch[i++] = (sum<<16)|off; sum += cnt; } off += cnt;
bvec3 and(bvec3 a, bvec3 b) {
return bvec3(a.x&&b.x, a.y&&b.y, a.z&&b.z);
}
uint fillBins(uvec4 counts, ivec3 relative) {//Returns quad count
#pragma unroll
for (uint i = 0; i < 8; i++) task.bins[i] = uint(-1);
uvec3 cA = counts.yzw&0xFFFFu;
uvec3 cB = counts.yzw>>16;
bvec3 a = and(notEqual(cA, uvec3(0)), lessThanEqual(ivec3(0), relative.yzx));
bvec3 b = and(notEqual(cB, uvec3(0)), lessThanEqual(relative.yzx, ivec3(0)));
uint dsc = counts.x>>16;//double sided quads
uint sum = 0;
uint off = counts.x&0xFFFFu;//translucent quads
uint i = 0;
//TODO: might need to move this into shared memory or somethign? so that compiler can reason about it (or make the bin an array in here and mesh)
//uint batch[8] = {uint(-1), uint(-1), uint(-1), uint(-1), uint(-1),uint(-1),uint(-1),uint(-1)};
//TODO IDEA: add inline merging, meaning if previous bin was true and so are we, just increment sum, dont take up new bucket
// this should allow for a new minimum number of bins especially when combined with other sections in the subgroup
// with merging, worst case bin count is 4
BIN(dsc!=0, dsc);//Double sided quads
//TODO: compute prefix sums and then jsut batch set into the array (this is an optimization)
BIN(a.x, cA.x);//Down
BIN(b.x, cB.x);//Up
BIN(a.y, cA.y);//North
BIN(b.y, cB.y);//South
BIN(a.z, cA.z);//West
BIN(b.z, cB.z);//East
//task.binA = uvec4(batch[0], batch[1], batch[2], batch[3]);
//task.binB = uvec4(batch[4], batch[5], batch[6], batch[7]);
return sum;
}
void main() {
uint secId = indirectLookup[gl_WorkGroupID.x];
uint vis = visibilityData[secId];
bool shouldRender = (vis&0x7fffffffu) == frameId-1;//-1 since we are technically in the next frame for the primary rasterization
bool renderTemporally = (vis&0x80000000u)==0;// If we are the temporal specialization, only render if marked as render temporally
task.quadCount = 0;
if (shouldRender) {
SectionMeta section = sectionData[secId];
uint detail = extractDetail(section);
ivec3 ipos = extractPosition(section);
ivec3 relative = ipos-(baseSectionPos>>detail);
#ifdef HAS_STATISTICS
atomicAdd(visibleSectionCounts[detail], 1);
#endif
//TODO: here enqueue the id here for both translucent and temporal (if relevant) (* note technically dont need for temporal as can just check :tm: if we are in temporal render mode)
//TODO: in the temporal phase, extract the sections that are ment to be rendered and are also translucent
// enqueue them into a seperate buffer and increment the bin counters based on distance
// this should allow a massive simplificattion of the raster pipeline by eliminating all command gen shaders + prep shaders
task.baseQuad = extractQuadStart(section);
task.quadCount = fillBins(section.b, relative);
task.cameraOffset = vec3(((ipos<<detail) - baseSectionPos)<<5);
task.lodLvl = detail;
}
gl_TaskCountNV = (task.quadCount+(MESH_SIZE-1))/MESH_SIZE;
}

View File

@@ -0,0 +1,154 @@
#version 460 core
#extension GL_NV_mesh_shader : require
layout(local_size_x=4) in;
#import <voxy:lod/section.glsl>
bvec3 and(bvec3 a, bvec3 b) {
return bvec3(a.x&&b.x, a.y&&b.y, a.z&&b.z);
}
layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP;
ivec3 baseSectionPos;
uint frameId;
vec3 cameraSubPos;
uint pad_;
vec2 screenSize;
};
layout(binding = 1, std430) restrict readonly buffer IndirectSectionLookupBuffer {
uint sectionCount;
uint indirectLookup[];
};
layout(binding = 2, std430) restrict readonly buffer SectionBuffer {
SectionMeta sectionData[];
};
layout(binding = 3, std430) restrict readonly buffer VisibilityBuffer {
uint visibilityData[];
};
#ifdef HAS_STATISTICS
layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBuffer {
uint visibleSectionCounts[5];
uint quadCounts[5];
};
#endif
taskNV out Task {
uvec4 control;//the control vec it defines what subvector to use, it is effectivly the terminating ranges of each bin
uvec4 bins[4];//the bins for each section the last component of each bin is the quad offset
uint launchSize;
} task;
#define BIN(br, cnt) if (br) { if (!pset) {bin[i++] = (sum<<16)|off;} sum += cnt; } pset = br; off += cnt;
/*
void createBin(out uvec4 bin, out uint sum, out uint offset, uint dsc, bvec3 a, bvec3 b, uvec3 cA, uvec3 cB) {
bin = uvec4(-1);
bool pset = false;
uint i = 0;
sum = 0;
offset = counts.x&0xFFFFu;//translucent quads
uint dsc = counts.x>>16;//double sided quads
uint off = counts.x&0xFFFFu;//translucent quads
uint i = 0;
BIN(dsc!=0, dsc);//Double sided quads
BIN(a.x, cA.x);//Down
BIN(b.x, cB.x);//Up
BIN(a.y, cA.y);//North
BIN(b.y, cB.y);//South
BIN(a.z, cA.z);//West
BIN(b.z, cB.z);//East
}*/
uint fillBins(uvec4 counts, ivec3 relative) {//Returns quad count
uvec3 cA = counts.yzw&0xFFFFu;
uvec3 cB = counts.yzw>>16;
bvec3 a = and(notEqual(cA, uvec3(0)), lessThanEqual(ivec3(0), relative.yzx));
bvec3 b = and(notEqual(cB, uvec3(0)), lessThanEqual(relative.yzx, ivec3(0)));
//compute the merged bin values
uvec4 bin = uvec4(-1);
bool pset = false;
uint i = 0;
uint sum = 0;
uint offset = counts.x&0xFFFFu;//translucent quads
uint dsc = counts.x>>16;//double sided quads
uint off = counts.x&0xFFFFu;//translucent quads
uint i = 0;
BIN(dsc!=0, dsc);//Double sided quads
BIN(a.x, cA.x);//Down
BIN(b.x, cB.x);//Up
BIN(a.y, cA.y);//North
BIN(b.y, cB.y);//South
BIN(a.z, cA.z);//West
BIN(b.z, cB.z);//East
//bin contains filled bin data, non filled slots contain -1
return sum;
}
void main() {
if (sectionCount<=gl_GlobalInvocationID.x) {
return;
}
if (subgroupElect()) {
task.quadCount = 0;
}
uint secId = indirectLookup[gl_GlobalInvocationID.x];
uint vis = visibilityData[secId];
bool shouldRender = (vis&0x7fffffffu) == frameId-1;//-1 since we are technically in the next frame for the primary rasterization
bool renderTemporally = (vis&0x80000000u)==0;// If we are the temporal specialization, only render if marked as render temporally
if (shouldRender) {
SectionMeta section = sectionData[secId];
uint detail = extractDetail(section);
ivec3 ipos = extractPosition(section);
ivec3 relative = ipos-(baseSectionPos>>detail);
#ifdef HAS_STATISTICS
atomicAdd(visibleSectionCounts[detail], 1);
#endif
//TODO: here enqueue the id here for both translucent and temporal (if relevant) (* note technically dont need for temporal as can just check :tm: if we are in temporal render mode)
//TODO: in the temporal phase, extract the sections that are ment to be rendered and are also translucent
// enqueue them into a seperate buffer and increment the bin counters based on distance
// this should allow a massive simplificattion of the raster pipeline by eliminating all command gen shaders + prep shaders
task.baseQuad = extractQuadStart(section);
task.quadCount = fillBins(section.b, relative);
task.cameraOffset = vec3(((ipos<<detail) - baseSectionPos)<<5);
task.lodLvl = detail;
}
gl_TaskCountNV = (task.quadCount+(MESH_SIZE-1))/MESH_SIZE;
}

View File

@@ -0,0 +1,90 @@
#version 460 core
#extension GL_EXT_mesh_shader : require
layout(binding = 0) uniform sampler2D blockModelAtlas;
layout(binding = 2) uniform sampler2D depthTex;
layout(location=1) perprimitiveEXT in PerPrimData {
uvec4 data;
} primIn;
layout(location = 0) out vec4 outColour;
vec4 uint2vec4RGBA(uint colour) {
return vec4((uvec4(colour)>>uvec4(24,16,8,0))&uvec4(0xFF))/255.0;
}
bool useMipmaps() {
return (primIn.data.x&2u)==0u;
}
bool useTinting() {
return (primIn.data.x&4u)!=0u;
}
bool useCutout() {
return (primIn.data.x&1u)==1u;
}
vec4 computeColour(vec4 colour) {
//Conditional tinting, TODO: FIXME: REPLACE WITH MASK OR SOMETHING, like encode data into the top bit of alpha
if (useTinting() && abs(colour.r-colour.g) < 0.02f && abs(colour.g-colour.b) < 0.02f) {
colour *= uint2vec4RGBA(primIn.data.z).yzwx;
}
return (colour * uint2vec4RGBA(primIn.data.y)) + vec4(0,0,0,float(primIn.data.w&0xFFu)/255);
}
uint getFace() {
return (primIn.data.w>>8)&7u;
}
vec2 getBaseUV() {
uint face = getFace();
uint modelId = primIn.data.x>>16;
vec2 modelUV = vec2(modelId&0xFFu, (modelId>>8)&0xFFu)*(1.0/(256.0));
return modelUV + (vec2(face>>1, face&1u) * (1.0/(vec2(3.0, 2.0)*256.0)));
}
void main() {
vec2 uv = vec2(0);
//Tile is the tile we are in
vec2 tile;
vec2 uv2 = modf(uv, tile)*(1.0/(vec2(3.0,2.0)*256.0));
vec4 colour;
vec2 texPos = uv2 + getBaseUV();
if (useMipmaps()) {
vec2 uvSmol = uv*(1.0/(vec2(3.0,2.0)*256.0));
vec2 dx = dFdx(uvSmol);//vec2(lDx, dDx);
vec2 dy = dFdy(uvSmol);//vec2(lDy, dDy);
colour = textureGrad(blockModelAtlas, texPos, dx, dy);
} else {
colour = textureLod(blockModelAtlas, texPos, 0);
}
if (any(notEqual(clamp(tile, vec2(0), vec2((primIn.data.x>>8)&0xFu, (primIn.data.x>>12)&0xFu)), tile))) {
discard;
}
//Check the minimum bounding texture and ensure we are greater than it
if (gl_FragCoord.z < texelFetch(depthTex, ivec2(gl_FragCoord.xy), 0).r) {
discard;
}
//Also, small quad is really fking over the mipping level somehow
if (useCutout() && (textureLod(blockModelAtlas, texPos, 0).a <= 0.1f)) {
//This is stupidly stupidly bad for divergence
//TODO: FIXME, basicly what this do is sample the exact pixel (no lod) for discarding, this stops mipmapping fucking it over
#ifndef DEBUG_RENDER
discard;
#endif
}
colour = computeColour(colour);
outColour = colour;
}

View File

@@ -0,0 +1,327 @@
#version 460 core
#extension GL_EXT_mesh_shader : require
#extension GL_ARB_gpu_shader_int64 : enable
#extension GL_KHR_shader_subgroup_arithmetic: require
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_ballot : require
#extension GL_KHR_shader_subgroup_vote : require
//TODO: finetune the local size and emission size
layout(local_size_x = MESH_SIZE, local_size_y=1, local_size_z=1) in;
layout(triangles, max_vertices=(MESH_SIZE*4), max_primitives=(MESH_SIZE*2)) out;
struct Task {
//Tightly packed, prefix sum + offset
//uvec4 binA;
//uvec4 binB;
uint bins[8];
vec3 cameraOffset;
uint lodLvl;
uint baseQuad;
uint quadCount;
};
taskPayloadSharedEXT Task task;
layout(location=1) perprimitiveEXT out PerPrimData {
uvec4 data;
} primOut[];
uint getQuadId() {
uint mid = gl_GlobalInvocationID.x;
uint cv = (mid<<16)|0xFFFFu;
/*
//Funny method
uvec4 a = mix(uvec4(0), uvec4( 1, 2, 4, 8), lessThanEqual(uvec4(task.bins[0],task.bins[1],task.bins[2],task.bins[3]), uvec4(cv))) +
mix(uvec4(0), uvec4(16,32,64,128), lessThanEqual(uvec4(task.bins[4],task.bins[5],task.bins[6],task.bins[7]), uvec4(cv)));
uint act = a.x+a.y+a.z+a.w;
uint id = findLSB(act^(act>>1));
//uint point = mix(binB, binA, id<4)[id&3u];
uint point = task.bins[id];
return (point&0xFFFFu)+(mid-(point>>16));
*/
#pragma unroll
for (uint i = 0; i<7; i++) {
uint point = task.bins[i];
if (point<=cv&&cv<task.bins[i+1]) {
return (point&0xFFFFu)+(mid-(point>>16));
}
}
return -1;
/*
for (uint i = 0; i<7; i++) {
uint point = task.bins[i];
if (point <= ((mid<<16)|0xFFFFu) && ((mid<<16)|0xFFFFu)<task.bins[i+1]) {
binId = i;
return (point&0xFFFFu)+(mid-(point>>16));
}
}
return -1;
*/
}
#import <voxy:lod/quad_format.glsl>
#import <voxy:lod/block_model.glsl>
layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP;
ivec3 baseSectionPos;
uint frameId;
vec3 cameraSubPos;
uint pad_;
vec2 screenSize;
};
layout(binding = 4, std430) readonly restrict buffer QuadBuffer {
Quad quadData[];
};
layout(binding = 5, std430) readonly restrict buffer ModelBuffer {
BlockModel modelData[];
};
layout(binding = 6, std430) readonly restrict buffer ModelColourBuffer {
uint colourData[];
};
layout(binding = 1) uniform sampler2D lightSampler;
vec4 getLighting(uint index) {
int i2 = int(index);
return texture(lightSampler, clamp((vec2((i2>>4)&0xF, i2&0xF))/16, vec2(8.0f/255), vec2(248.0f/255)));
}
//===============
vec3 swizzelDataAxis(uint axis, vec3 data) {
return mix(mix(data.zxy,data.xzy,bvec3(axis==0)),data,bvec3(axis==1));
}
vec4 getFaceSize(uint faceData) {
float EPSILON = 0.00005f;
vec4 faceOffsetsSizes = extractFaceSizes(faceData);
//Expand the quads by a very small amount (because of the subtraction after this also becomes an implicit add)
faceOffsetsSizes.xz -= vec2(EPSILON);
//Make the end relative to the start
faceOffsetsSizes.yw -= faceOffsetsSizes.xz;
return faceOffsetsSizes;
}
vec3 faceNormal(uint face) {
//TODO: optimize this garbage
return vec3(uint((face>>1)==2), uint((face>>1)==0), uint((face>>1)==1)) * (float(int(face)&1)*2-1);
}
uint packVec4(vec4 vec) {
uvec4 vec_=uvec4(vec*255)<<uvec4(24,16,8,0);
return vec_.x|vec_.y|vec_.z|vec_.w;
}
//===============
vec3 cornerPos;
vec2 axisFaceSize;
uint face;
vec4 faceSize;
uint modelId;
BlockModel model;
uint faceData;
bool isTranslucent;
bool hasAO;
bool isShaded;
void setup(Quad quad) {
face = extractFace(quad);
modelId = extractStateId(quad);
model = modelData[modelId];
faceData = model.faceData[face];
isTranslucent = modelIsTranslucent(model);
hasAO = modelHasMipmaps(model);//TODO: replace with per face AO flag
isShaded = hasAO;//TODO: make this a per face flag
ivec2 quadSize = extractSize(quad);
faceSize = getFaceSize(faceData);
cornerPos = extractPos(quad);
float depthOffset = extractFaceIndentation(faceData);
cornerPos += swizzelDataAxis(face>>1, vec3(faceSize.xz, mix(depthOffset, 1-depthOffset, float(face&1u))));
cornerPos *= (1<<task.lodLvl);
cornerPos += task.cameraOffset;
axisFaceSize = (faceSize.yw + quadSize - 1);
//uv =
}
vec2 getUvCorner(uint corner) {
return faceSize.xz + axisFaceSize*vec2((corner>>1)&1u, corner&1u);;
}
uvec4 createQuadData(Quad quad) {
uint flags = faceHasAlphaCuttout(faceData);
ivec2 quadSize = extractSize(quad);
//We need to have a conditional override based on if the model size is < a full face + quadSize > 1
flags |= uint(any(greaterThan(quadSize, ivec2(1)))) & faceHasAlphaCuttoutOverride(faceData);
flags |= uint(!modelHasMipmaps(model))<<1;
//Compute lighting
vec4 tinting = getLighting(extractLightId(quad));
//Apply model colour tinting
uint tintColour = model.colourTint;
if (modelHasBiomeLUT(model)) {
tintColour = colourData[tintColour + extractBiomeId(quad)];
}
uint conditionalTinting = 0;
if (tintColour != uint(-1)) {
flags |= 1u<<2;
conditionalTinting = tintColour;
}
uint addin = 0;
if (!isTranslucent) {
tinting.w = 0.0;
//Encode the face, the lod level and
uint encodedData = 0;
encodedData |= face;
encodedData |= (task.lodLvl<<3);
encodedData |= uint(hasAO)<<6;
addin = encodedData;
}
//Apply face tint
if (isShaded) {
//TODO: make branchless, infact apply ahead of time to the texture itself in ModelManager since that is
// per face
if ((face>>1) == 1) {//NORTH, SOUTH
tinting.xyz *= 0.8f;
} else if ((face>>1) == 2) {//EAST, WEST
tinting.xyz *= 0.6f;
} else if (face == 0) {//DOWN
tinting.xyz *= 0.5f;
}
}
uvec4 interData;
interData.x = (modelId<<16) | flags | (uint(quadSize.x-1)<<8) | (uint(quadSize.y-1)<<12);
interData.y = packVec4(tinting);
interData.z = conditionalTinting;
interData.w = addin|(face<<8);
return interData;
}
vec4 emitVertexPos(int corner) {
vec3 pointPos = swizzelDataAxis(face>>1,vec3(axisFaceSize*mix(vec2(0),vec2(1<<task.lodLvl),bvec2((corner>>1)&1, corner&1)),0))+cornerPos;
return MVP*vec4(pointPos, 1.0);
}
bvec2 whatRender(vec4 p1, vec4 p2, vec4 p0, vec4 p3) {
vec2 ssmin = ((p1.xy/p1.w)+1)*screenSize;
vec2 ssmax = ssmin;
vec2 point = ((p2.xy/p2.w)+1)*screenSize;
ssmin = min(ssmin, point);
ssmax = max(ssmax, point);
point = ((p0.xy/p0.w)+1)*screenSize;
vec2 t0min = min(ssmin, point);
vec2 t0max = max(ssmax, point);
point = ((p3.xy/p3.w)+1)*screenSize;
vec2 t1min = min(ssmin, point);
vec2 t1max = max(ssmax, point);
//Possibly cull the triangles if they dont cover the center of a pixel on the screen (degen)
float degenBias = 0.01f;
bool t0draw = all(notEqual(round(t0min-degenBias),round(t0max+degenBias)));
bool t1draw = all(notEqual(round(t1min-degenBias),round(t1max+degenBias)));
return bvec2(t0draw, t1draw);
}
#ifdef HAS_STATISTICS
layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBuffer {
uint visibleSectionCounts[5];
uint quadCounts[5];
};
#endif
void main() {
uint qid = uint(-1);
Quad quad;
if (gl_GlobalInvocationID.x<task.quadCount) {
uint qid = getQuadId() + task.baseQuad;
quad = quadData[qid];
setup(quad);
bool render = dot(faceNormal(face), cornerPos-cameraSubPos) <= 0;
qid = render?qid:uint(-1);
}
subgroupBarrier();
uint triId_ = subgroupExclusiveAdd(qid!=uint(-1)?2:0);
uint qc = subgroupMax(triId_+(qid!=uint(-1)?2:0));
SetMeshOutputsEXT(qc*2, qc);
#ifdef HAS_STATISTICS
if (subgroupElect()) {
atomicAdd(quadCounts[task.lodLvl], qc);
}
#endif
if (qid != uint(-1)) {
uint vertId_ = triId_*2;//hack cause we are emitting full quads (2 tris) just to test
uint triId = triId_;
uint vertId = vertId_;
vec4 p1 = emitVertexPos(1);
vec4 p2 = emitVertexPos(2);
vec4 p0 = emitVertexPos(0);
vec4 p3 = emitVertexPos(3);
uvec4 data = createQuadData(quad);
//Emit common
gl_MeshVerticesEXT[vertId++].gl_Position = p1;
gl_MeshVerticesEXT[vertId++].gl_Position = p2;
//Prim 1
gl_PrimitiveTriangleIndicesEXT[triId] = uvec3(vertId_+0, vertId_+1, vertId);
gl_MeshVerticesEXT[vertId++].gl_Position = p0;
primOut[triId++].data = data;
//Prim 2
gl_PrimitiveTriangleIndicesEXT[triId] = uvec3(vertId_+0, vertId, vertId_+1);
gl_MeshVerticesEXT[vertId++].gl_Position = p3;
primOut[triId++].data = data;
}
}

View File

@@ -0,0 +1,125 @@
#version 460 core
#extension GL_EXT_mesh_shader : require
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
#import <voxy:lod/section.glsl>
layout(binding = 0, std140) uniform SceneUniform {
mat4 MVP;
ivec3 baseSectionPos;
uint frameId;
vec3 cameraSubPos;
uint pad_;
vec2 screenSize;
};
layout(binding = 1, std430) restrict readonly buffer IndirectSectionLookupBuffer {
uint sectionCount;
uint indirectLookup[];
};
layout(binding = 2, std430) restrict readonly buffer SectionBuffer {
SectionMeta sectionData[];
};
layout(binding = 3, std430) restrict readonly buffer VisibilityBuffer {
uint visibilityData[];
};
#ifdef HAS_STATISTICS
layout(binding = STATISTICS_BUFFER_BINDING, std430) restrict buffer statisticsBuffer {
uint visibleSectionCounts[5];
uint quadCounts[5];
};
#endif
struct Task {
//Tightly packed, prefix sum + offset
//uvec4 binA;
//uvec4 binB;
uint bins[8];
vec3 cameraOffset;
uint lodLvl;
uint baseQuad;
uint quadCount;
};
taskPayloadSharedEXT Task task;
#define BIN(br, cnt) if (br) { task.bins[i++] = (sum<<16)|off; sum += cnt; } off += cnt;
//#define BIN(br, cnt) if (br) { batch[i++] = (sum<<16)|off; sum += cnt; } off += cnt;
bvec3 and(bvec3 a, bvec3 b) {
return bvec3(a.x&&b.x, a.y&&b.y, a.z&&b.z);
}
uint fillBins(uvec4 counts, ivec3 relative) {//Returns quad count
#pragma unroll
for (uint i = 0; i < 8; i++) task.bins[i] = uint(-1);
uvec3 cA = counts.yzw&0xFFFFu;
uvec3 cB = counts.yzw>>16;
bvec3 a = and(notEqual(cA, uvec3(0)), lessThanEqual(ivec3(0), relative.yzx));
bvec3 b = and(notEqual(cB, uvec3(0)), lessThanEqual(relative.yzx, ivec3(0)));
uint dsc = counts.x>>16;//double sided quads
uint sum = 0;
uint off = counts.x&0xFFFFu;//translucent quads
uint i = 0;
//TODO: might need to move this into shared memory or somethign? so that compiler can reason about it (or make the bin an array in here and mesh)
//uint batch[8] = {uint(-1), uint(-1), uint(-1), uint(-1), uint(-1),uint(-1),uint(-1),uint(-1)};
BIN(dsc!=0, dsc);//Double sided quads
//TODO: compute prefix sums and then jsut batch set into the array (this is an optimization)
BIN(a.x, cA.x);//Down
BIN(b.x, cB.x);//Up
BIN(a.y, cA.y);//North
BIN(b.y, cB.y);//South
BIN(a.z, cA.z);//West
BIN(b.z, cB.z);//East
//task.binA = uvec4(batch[0], batch[1], batch[2], batch[3]);
//task.binB = uvec4(batch[4], batch[5], batch[6], batch[7]);
return sum;
}
void main() {
uint secId = indirectLookup[gl_WorkGroupID.x];
uint vis = visibilityData[secId];
bool shouldRender = (vis&0x7fffffffu) == frameId-1;//-1 since we are technically in the next frame for the primary rasterization
bool renderTemporally = (vis&0x80000000u)==0;
task.quadCount = 0;
if (shouldRender) {
SectionMeta section = sectionData[secId];
uint detail = extractDetail(section);
ivec3 ipos = extractPosition(section);
ivec3 relative = ipos-(baseSectionPos>>detail);
#ifdef HAS_STATISTICS
atomicAdd(visibleSectionCounts[detail], 1);
#endif
//TODO: here enqueue the id here for both translucent and temporal (if relevant) (* note technically dont need for temporal as can just check :tm: if we are in temporal render mode)
task.baseQuad = extractQuadStart(section);
task.quadCount = fillBins(section.b, relative);
task.cameraOffset = vec3(((ipos<<detail) - baseSectionPos)<<5);
task.lodLvl = detail;
}
//It appears to be valid to read from taskPayloadSharedEXT
EmitMeshTasksEXT((task.quadCount+(MESH_SIZE-1))/MESH_SIZE, 1, 1);
}

View File

@@ -1,3 +1,4 @@
/*
struct SectionMeta {
uint posA;
uint posB;
@@ -8,29 +9,38 @@ struct SectionMeta {
uint cntC;
uint cntD;
};
*/
struct SectionMeta {
uvec4 a;
uvec4 b;
};
uvec2 extractRawPos(SectionMeta section) {
return section.a.xy;
}
uint extractDetail(SectionMeta section) {
return section.posA>>28;
return section.a.x>>28;
}
ivec3 extractPosition(SectionMeta section) {
int y = ((int(section.posA)<<4)>>24);
int x = (int(section.posB)<<4)>>8;
int z = int((section.posA&((1u<<20)-1))<<4);
z |= int(section.posB>>28);
int y = ((int(section.a.x)<<4)>>24);
int x = (int(section.a.y)<<4)>>8;
int z = int((section.a.x&((1u<<20)-1))<<4);
z |= int(section.a.y>>28);
z <<= 8;
z >>= 8;
return ivec3(x,y,z);
}
uint extractQuadStart(SectionMeta meta) {
return meta.ptr;
return meta.a.w;
}
ivec3 extractAABBOffset(SectionMeta meta) {
return (ivec3(meta.AABB)>>ivec3(0,5,10))&31;
return (ivec3(meta.a.z)>>ivec3(0,5,10))&31;
}
ivec3 extractAABBSize(SectionMeta meta) {
return ((ivec3(meta.AABB)>>ivec3(15,20,25))&31)+1;//The size is + 1 cause its always at least 1x1x1
return ((ivec3(meta.a.z)>>ivec3(15,20,25))&31)+1;//The size is + 1 cause its always at least 1x1x1
}

View File

@@ -4,6 +4,10 @@ layout(binding = 0) uniform sampler2D colourTex;
layout(binding = 1) uniform sampler2D depthTex;
layout(location = 2) uniform mat4 invProjMat;
layout(location = 3) uniform mat4 projMat;
#ifdef USE_ENV_FOG
layout(location = 4) uniform vec3 endParams;
layout(location = 5) uniform vec3 fogColour;
#endif
out vec4 colour;
in vec2 UV;
@@ -28,7 +32,16 @@ void main() {
discard;
}
depth = projDepth(rev3d(vec3(UV.xy, depth)));
vec3 point = rev3d(vec3(UV.xy, depth));
#ifdef USE_ENV_FOG
{
float fogLerp = max(fma(min(length(point.xyz), endParams.x),endParams.y,endParams.z),0);//512 is 32*16 which is the render distance in blocks
colour.rgb = mix(colour.rgb, fogColour, fogLerp);
}
#endif
depth = projDepth(point);
depth = min(1.0f-(2.0f/((1<<24)-1)), depth);
depth = depth * 0.5f + 0.5f;
depth = gl_DepthRange.diff * depth + gl_DepthRange.near;

View File

@@ -1,7 +1,7 @@
#version 460
#extension GL_KHR_shader_subgroup_arithmetic: require
#extension GL_KHR_shader_subgroup_basic : require
#extension GL_KHR_shader_subgroup_arithmetic: require
#define WORK_SIZE 256
@@ -12,92 +12,113 @@ layout(binding = IO_BUFFER, std430) buffer InputBuffer {
uvec4[] ioCount;
};
shared uint warpPrefixSum[32];//Warps are 32, tricks require full warp
shared uint warpPrefixSum[8];//Warps are 32, tricks require full warp
void main() {
/*
uint subgroupId = gl_LocalInvocationID.x>>5;
warpPrefixSum[gl_SubgroupInvocationID] = 0;
memoryBarrierShared();
if (gl_SubgroupSize == 32) {
#ifdef IS_INTEL
uint subgroupId = gl_LocalInvocationID.x>>5;
#else
uint subgroupId = gl_SubgroupID;
#endif
//todo
//assert(gl_SubgroupSize == 32);
//assert(gl_NumSubgroups == (WORK_SIZE>>5));
//todo
//assert(gl_SubgroupSize == 32);
//assert(gl_NumSubgroups == (WORK_SIZE>>5));
uint gid = gl_GlobalInvocationID.x;
uvec4 count = uvec4(0);
uint sum = 0;
{
uvec4 dat = ioCount[gid];
count.yzw = dat.xyz;
count.z += count.y;
count.w += count.z;
sum = count.w + dat.w;
}
uint gid = gl_GlobalInvocationID.x;
uvec4 count = uvec4(0);
uint sum = 0;
{
uvec4 dat = ioCount[gid];
count.yzw = dat.xyz;
count.z += count.y;
count.w += count.z;
sum = count.w + dat.w;
}
subgroupBarrier();//Wait for all threads in the subgroup to get the buffer
barrier();
count += subgroupExclusiveAdd(sum);
count += subgroupExclusiveAdd(sum);
if (gl_SubgroupInvocationID==31) {
warpPrefixSum[subgroupId] = count.x+sum;
}
memoryBarrierShared();
barrier();
uint val = warpPrefixSum[gl_SubgroupInvocationID];
barrier();
if (subgroupId == 0) {
//Use warp to do entire add in 1 reduction
warpPrefixSum[gl_SubgroupInvocationID] = subgroupExclusiveAdd(val);
}
memoryBarrierShared();
barrier();
count += warpPrefixSum[subgroupId];
ioCount[gid] = count;
*/
if (gl_SubgroupInvocationID==31) {
warpPrefixSum[subgroupId] = count.x+sum;
}
memoryBarrierShared();
barrier();
if (gl_LocalInvocationID.x<8) {
uint val = warpPrefixSum[gl_SubgroupInvocationID];
subgroupBarrier();
//Use warp to do entire add in 1 reduction
warpPrefixSum[gl_SubgroupInvocationID] = subgroupExclusiveAdd(val);
}
memoryBarrierShared();
barrier();
//Add the computed sum across all threads and warps
count += warpPrefixSum[subgroupId];
ioCount[gid] = count;
} else {
#ifdef IS_INTEL
uint subgroupId = gl_LocalInvocationID.x>>6;
#else
uint subgroupId = gl_SubgroupID;
#endif
//todo
//assert(gl_SubgroupSize == 32);
//assert(gl_NumSubgroups == (WORK_SIZE>>5));
uint gid = gl_GlobalInvocationID.x;
uvec4 count = uvec4(0);
uint sum = 0;
{
uvec4 dat = ioCount[gid];
count.yzw = dat.xyz;
count.z += count.y;
count.w += count.z;
sum = count.w + dat.w;
}
subgroupBarrier();//Wait for all threads in the subgroup to get the buffer
count += subgroupExclusiveAdd(sum);
if (gl_SubgroupInvocationID==63) {
warpPrefixSum[subgroupId] = count.x+sum;
}
memoryBarrierShared();
barrier();
#ifdef IS_INTEL
uint subgroupId = gl_LocalInvocationID.x>>5;
#else
uint subgroupId = gl_SubgroupID;
#endif
#ifdef IS_WINDOWS
//hate amd hate amd hate amd hate amd
uint val = warpPrefixSum[gl_LocalInvocationID.x&3u];
//todo
//assert(gl_SubgroupSize == 32);
//assert(gl_NumSubgroups == (WORK_SIZE>>5));
uint gid = gl_GlobalInvocationID.x;
uvec4 count = uvec4(0);
uint sum = 0;
{
uvec4 dat = ioCount[gid];
count.yzw = dat.xyz;
count.z += count.y;
count.w += count.z;
sum = count.w + dat.w;
}
subgroupBarrier();//Wait for all threads in the subgroup to get the buffer
count += subgroupExclusiveAdd(sum);
if (gl_SubgroupInvocationID==31) {
warpPrefixSum[subgroupId] = count.x+sum;
}
memoryBarrierShared();
barrier();
if (subgroupId == 0) {
uint val = warpPrefixSum[gl_SubgroupInvocationID];
subgroupBarrier();
//Use warp to do entire add in 1 reduction
warpPrefixSum[gl_SubgroupInvocationID] = subgroupExclusiveAdd(val);
uint extraJank = subgroupExclusiveAdd(val);
barrier();
if (gl_LocalInvocationID.x<4) {
warpPrefixSum[gl_LocalInvocationID.x] = extraJank;
}
#else
if (gl_LocalInvocationID.x<4) {
uint val = warpPrefixSum[gl_SubgroupInvocationID];
subgroupBarrier();
//Use warp to do entire add in 1 reduction
warpPrefixSum[gl_SubgroupInvocationID] = subgroupExclusiveAdd(val);
}
#endif
memoryBarrierShared();
barrier();
//Add the computed sum across all threads and warps
count += warpPrefixSum[subgroupId];
ioCount[gid] = count;
}
memoryBarrierShared();
barrier();
//Add the computed sum across all threads and warps
count += warpPrefixSum[subgroupId];
ioCount[gid] = count;
}

View File

@@ -3,20 +3,24 @@
"package": "me.cortex.voxy.client.mixin",
"compatibilityLevel": "JAVA_17",
"client": [
"minecraft.MixinBackgroundRenderer",
"minecraft.MixinClientChunkManager",
"minecraft.MixinClientCommonNetworkHandler",
"minecraft.MixinClientLoginNetworkHandler",
"minecraft.MixinDebugHud",
"minecraft.MixinFogRenderer",
"minecraft.MixinGlDebug",
"minecraft.MixinMinecraftClient",
"minecraft.MixinThreadExecutor",
"minecraft.MixinWindow",
"minecraft.MixinWorldRenderer",
"minecraft.MixinGlDebug",
"nvidium.MixinRenderPipeline",
"sodium.MixinDefaultChunkRenderer",
"sodium.MixinRenderSectionManager",
"sodium.MixinSodiumOptionsGUI"
],
"injectors": {
"defaultRequire": 1
}
},
"mixins": [
]
}

View File

@@ -32,7 +32,7 @@
"common.voxy.mixins.json"
],
"depends": {
"minecraft": "1.21.5",
"minecraft": ["1.21.7","1.21.6"],
"fabricloader": ">=0.14.22",
"fabric-api": ">=0.91.1",
"sodium": ">=0.6.13"

View File

@@ -6,9 +6,6 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
accessible field net/minecraft/client/texture/SpriteContents image Lnet/minecraft/client/texture/NativeImage;
accessible field net/minecraft/client/render/Frustum frustumIntersection Lorg/joml/FrustumIntersection;
accessible field net/minecraft/client/color/block/BlockColors providers Lnet/minecraft/util/collection/IdList;
accessible field net/minecraft/client/render/GameRenderer zoomX F
accessible field net/minecraft/client/render/GameRenderer zoomY F
accessible field net/minecraft/client/render/GameRenderer zoom F
accessible field net/minecraft/client/world/ClientWorld worldRenderer Lnet/minecraft/client/render/WorldRenderer;
accessible field net/minecraft/world/biome/source/BiomeAccess seed J
@@ -27,7 +24,11 @@ accessible field net/minecraft/world/chunk/PalettedContainer$Data palette Lnet/m
accessible field net/minecraft/client/gl/GlGpuBuffer id I
accessible field net/minecraft/client/gl/GlResourceManager currentProgram Lnet/minecraft/client/gl/ShaderProgram;
accessible field net/minecraft/client/gl/GlResourceManager currentPipeline Lcom/mojang/blaze3d/pipeline/RenderPipeline;
accessible field net/minecraft/client/gl/GlCommandEncoder currentProgram Lnet/minecraft/client/gl/ShaderProgram;
accessible field net/minecraft/client/gl/GlCommandEncoder currentPipeline Lcom/mojang/blaze3d/pipeline/RenderPipeline;
accessible class net/minecraft/client/gl/GlDebug$DebugMessage
accessible class net/minecraft/client/gl/GlDebug$DebugMessage
accessible class net/minecraft/client/world/ClientChunkManager$ClientChunkMap
accessible method net/minecraft/client/world/ClientChunkManager$ClientChunkMap getChunk (I)Lnet/minecraft/world/chunk/WorldChunk;
accessible method net/minecraft/client/world/ClientChunkManager$ClientChunkMap getIndex (II)I