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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,10 @@ tasks.register('makeExcludedRocksDB', Zip) {
exclude {
def file = it.name
if (file.endsWith(".jnilib")) {
return true
//Keep macOS arm64 native so RocksDB works on Apple Silicon (VK/MoltenVK
// target); drop x86_64. Without this, world load crashes on M-series Macs
// with "librocksdbjni-osx-arm64.jnilib was not found inside JAR".
return !file.contains("osx-arm64")
}
if (!file.endsWith(".so")) {
return false
Expand Down Expand Up @@ -361,6 +364,18 @@ jar {
}

project.ext.lwjglVersion = "3.4.1"

// --- Voxy Vulkan backend deps (optional at runtime; probed via capability detection) ---
dependencies {
include(implementation("org.lwjgl:lwjgl-vulkan:${project.ext.lwjglVersion}"))
include(implementation("org.lwjgl:lwjgl-shaderc:${project.ext.lwjglVersion}"))
for (n in ["natives-windows", "natives-linux", "natives-macos", "natives-macos-arm64"]) {
include(runtimeOnly("org.lwjgl:lwjgl-shaderc:${project.ext.lwjglVersion}:${n}"))
}
// MoltenVK translation layer path on macOS is provided by lwjgl-vulkan natives there:
include(runtimeOnly("org.lwjgl:lwjgl-vulkan:${project.ext.lwjglVersion}:natives-macos"))
include(runtimeOnly("org.lwjgl:lwjgl-vulkan:${project.ext.lwjglVersion}:natives-macos-arm64"))
}
project.ext.lwjglNatives = "natives-windows"

repositories {
Expand All @@ -380,6 +395,12 @@ dependencies {
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-windows")
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-linux")
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-linux")
//macOS natives: required for the MoltenVK/Vulkan path so Voxy's section
// storage (zstd compression + lmdb index) can load on Apple Silicon.
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-macos")
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-macos")
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-macos-arm64")
include(runtimeOnly "org.lwjgl:lwjgl-zstd:$lwjglVersion:natives-macos-arm64")
if (INCLUDE_OTHER_ARCHS) {
runtimeOnly "org.lwjgl:lwjgl:$lwjglVersion:natives-linux-arm64"
include(runtimeOnly "org.lwjgl:lwjgl-lmdb:$lwjglVersion:natives-linux-arm64")
Expand Down
65 changes: 52 additions & 13 deletions src/main/java/me/cortex/voxy/client/VoxyClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import me.cortex.voxy.client.core.gl.Capabilities;
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
import me.cortex.voxy.client.core.vk.MinecraftVkHost;
import me.cortex.voxy.client.core.vk.VulkanBackend;
import me.cortex.voxy.common.Logger;
import me.cortex.voxy.commonImpl.VoxyCommon;
import net.fabricmc.api.ClientModInitializer;
Expand All @@ -21,6 +23,16 @@ public class VoxyClient implements ClientModInitializer {
private static final HashSet<String> FREX = new HashSet<>();
private static FileLock EXCLUSIVE_LOCK;
public static void initVoxyClient() {
//When MC itself presents through its 26.2 Vulkan backend there is no GL
// context on the render thread. Nothing GL-backed may be touched here —
// above all not Capabilities, whose <clinit> calls GL.getCapabilities()
// and throws. Everything below this branch classloads or queries GL and
// is only valid when MC is on OpenGL.
if (MinecraftVkHost.isMinecraftOnVulkan()) {
initVoxyClientVulkan();
return;
}

Capabilities.init();//Ensure clinit is called

if (Capabilities.INSTANCE.hasBrokenDepthSampler) {
Expand All @@ -33,25 +45,14 @@ public static void initVoxyClient() {
}

if (systemSupported && System.getProperty("voxy.exclusiveLock", "false").equalsIgnoreCase("true")) {
//Try acquire the lock file
var vf = Minecraft.getInstance().gameDirectory.toPath().resolve(".voxy");
if (!vf.toFile().isDirectory()) {
vf.toFile().mkdir();
}
try {
FileOutputStream fis = new FileOutputStream(vf.resolve("voxy.lock").toFile());
EXCLUSIVE_LOCK = fis.getChannel().lock(0, Long.MAX_VALUE, false);
} catch (NonWritableChannelException | IOException e) {
//If some error write to log and unsupport
Logger.error("Failed to acquire exclusive voxy lock file, mod will be disabled");
if (!acquireExclusiveLock()) {
systemSupported = false;
}

}

if (systemSupported) {

SharedIndexBuffer.INSTANCE.id();
SharedIndexBuffer.INSTANCE().id();

VoxyCommon.setInstanceFactory(VoxyClientInstance::new);

Expand All @@ -62,6 +63,44 @@ public static void initVoxyClient() {
}
}

//Vulkan init: MC is presenting through its own Vulkan backend, so Voxy adopts
// that device (VulkanBackend) and never runs the GL capability probes. This
// method — and everything it reaches — must stay free of any GL classload.
// If VK cannot be adopted (host adapter not registered, missing bindings)
// Voxy disables itself: falling back to GL is impossible (no GL context).
private static void initVoxyClientVulkan() {
if (!VulkanBackend.shouldUseVulkan()) {
Logger.error("Voxy is unsupported on your system. Minecraft is on Vulkan but the Voxy Vulkan backend could not be used (" + VulkanBackend.statusLine() + ")");
return;
}

if (System.getProperty("voxy.exclusiveLock", "false").equalsIgnoreCase("true")) {
if (!acquireExclusiveLock()) {
return;
}
}

VoxyCommon.setInstanceFactory(VoxyClientInstance::new);
Logger.info("Voxy initialised on the Vulkan backend (" + VulkanBackend.statusLine() + ")");
}

//Acquire the cross-process exclusive lock file. Backend-agnostic (pure file
// IO, no GPU work). Returns false and logs on failure so callers can disable Voxy.
private static boolean acquireExclusiveLock() {
var vf = Minecraft.getInstance().gameDirectory.toPath().resolve(".voxy");
if (!vf.toFile().isDirectory()) {
vf.toFile().mkdir();
}
try {
FileOutputStream fis = new FileOutputStream(vf.resolve("voxy.lock").toFile());
EXCLUSIVE_LOCK = fis.getChannel().lock(0, Long.MAX_VALUE, false);
return true;
} catch (NonWritableChannelException | IOException e) {
Logger.error("Failed to acquire exclusive voxy lock file, mod will be disabled");
return false;
}
}

@Override
public void onInitializeClient() {
DebugEntries.init();
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/me/cortex/voxy/client/config/VoxyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ public class VoxyConfig {
public float subDivisionSize = 64;
public boolean useEnvironmentalFog = true;
public boolean dontUseSodiumBuilderThreads = false;
//Voxy's rendering API is not user-selectable: it follows whichever graphics
// API MC itself is running on. When MC resolves to Vulkan, Voxy renders
// through Vulkan (host mode, adopting MC's own device); otherwise OpenGL.
// A manual override is deliberately absent: Voxy cannot run OpenGL while MC
// runs Vulkan (no GL context exists in that case), and forcing a cross-API
// split is incoherent with the goal of an identical experience.
public boolean wantsVulkanBackend() {
return me.cortex.voxy.client.core.vk.MinecraftVkHost.isMinecraftOnVulkan();
}

public String ssaoMode;

public SSAO.SSAOMode getSSAOMode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.AbstractDownloadStream;
import me.cortex.voxy.client.core.util.GPUTiming;
import me.cortex.voxy.common.util.TrackedObject;
import org.joml.Matrix4f;
Expand Down Expand Up @@ -208,7 +208,7 @@ protected void innerPrimaryWork(Viewport<?> viewport, int depthBuffer) {

TimingStatistics.D.start();
//Tick download stream
DownloadStream.INSTANCE.tick();
AbstractDownloadStream.INSTANCE().tick();
TimingStatistics.D.stop();

this.nodeManager.tick(this.traversal.getNodeBuffer(), this.nodeCleaner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import me.cortex.voxy.client.core.rendering.post.FullscreenBlit;
import me.cortex.voxy.client.core.rendering.section.backend.AbstractSectionRenderer;
import me.cortex.voxy.client.core.rendering.util.DepthFramebuffer;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.core.rendering.util.AbstractUploadStream;
import me.cortex.voxy.client.iris.IrisVoxyRenderPipelineData;
import net.irisshaders.iris.shaderpack.materialmap.WorldRenderingSettings;
import org.joml.Matrix4f;
Expand Down Expand Up @@ -110,9 +110,9 @@ public void preSetup(Viewport<?> viewport) {
super.preSetup(viewport);
if (this.shaderUniforms != null) {
//Update the uniforms
long ptr = UploadStream.INSTANCE.uploadTo(this.shaderUniforms);
long ptr = AbstractUploadStream.INSTANCE().uploadTo(this.shaderUniforms);
this.data.getUniforms().updater().accept(ptr);
UploadStream.INSTANCE.commit();
AbstractUploadStream.INSTANCE().commit();
}
}

Expand Down
67 changes: 58 additions & 9 deletions src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
import me.cortex.voxy.client.core.rendering.section.backend.mdic.MDICSectionRenderer;
import me.cortex.voxy.client.core.rendering.section.geometry.BasicSectionGeometryData;
import me.cortex.voxy.client.core.rendering.section.geometry.IGeometryData;
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
import me.cortex.voxy.client.core.rendering.util.AbstractDownloadStream;
import me.cortex.voxy.client.core.rendering.util.PrintfDebugUtil;
import me.cortex.voxy.client.core.rendering.util.UploadStream;
import me.cortex.voxy.client.core.rendering.util.AbstractUploadStream;
import me.cortex.voxy.client.core.util.GPUTiming;
import me.cortex.voxy.client.core.util.IrisUtil;
import me.cortex.voxy.common.Logger;
Expand Down Expand Up @@ -54,6 +54,10 @@
public class VoxyRenderSystem {
private final WorldEngine worldIn;

//Non-null exactly when MC runs on Vulkan: the whole render path is the
// pure-VK core and every GL member below stays null.
public final @Nullable me.cortex.voxy.client.core.vk.render.VkRenderCore vkCore;

private final ModelBakerySubsystem modelService;
private final RenderGenerationService renderGen;
private final IGeometryData geometryData;
Expand All @@ -64,7 +68,7 @@ public class VoxyRenderSystem {

private final RenderDistanceTracker renderDistanceTracker;
private final BoundRenderer boundOutlineRenderer;
public final StreamedBoundStore visbleSectionStream = new StreamedBoundStore();
public StreamedBoundStore visbleSectionStream;//Sodium mixin fed; backend-neutral (GL creates here, VK core supplies its own)
private @Nullable ColumnStreamedBoundStore columnStreamedBoundStore;//Only used when FREX is enabled

private final ViewportSelector<?> viewportSelector;
Expand All @@ -80,6 +84,28 @@ private static AbstractSectionRenderer.Factory<?,? extends IGeometryData> getRen
public VoxyRenderSystem(WorldEngine world, ServiceManager sm) {
//Keep the world loaded, NOTE: this is done FIRST, to keep and ensure that even if the rest of loading takes more
// than timeout, we keep the world acquired
//When MC itself renders through Vulkan there is no GL context at all; the
// entire renderer is the VkRenderCore and nothing below may run.
if (me.cortex.voxy.client.core.vk.VulkanBackend.shouldUseVulkan()) {
this.worldIn = world;
this.vkCore = new me.cortex.voxy.client.core.vk.render.VkRenderCore(world, sm);
this.visbleSectionStream = this.vkCore.getVisibleSectionStream();//Sodium visibility mixins feed it on VK too
this.modelService = null;
this.renderGen = null;
this.geometryData = null;
this.nodeManager = null;
this.nodeCleaner = null;
this.traversal = null;
this.renderDistanceTracker = null;
this.boundOutlineRenderer = null;
this.viewportSelector = null;
this.pipeline = null;
this.properties = null;
return;
}
this.vkCore = null;
this.visbleSectionStream = new StreamedBoundStore(GlBuffer::new);
//OpenGL path (unchanged)
world.acquireRef();
Logger.info("Creating Voxy render system");

Expand Down Expand Up @@ -112,7 +138,7 @@ public VoxyRenderSystem(WorldEngine world, ServiceManager sm) {

this.geometryData = new BasicSectionGeometryData(1<<20, RenderResourceReuse.getOrCreateGeometryBuffer());

this.nodeManager = new AsyncNodeManager(1 << 21, this.geometryData, this.renderGen);
this.nodeManager = new AsyncNodeManager(1 << 21, this.geometryData, this.renderGen, new me.cortex.voxy.client.core.rendering.hierachical.GlNodeGpuOps());
this.nodeCleaner = new NodeCleaner(this.nodeManager);
this.traversal = new HierarchicalOcclusionTraverser(this.nodeManager, this.nodeCleaner, this.renderGen);

Expand Down Expand Up @@ -174,7 +200,16 @@ public VoxyRenderSystem(WorldEngine world, ServiceManager sm) {
}


//True when MC — and therefore Voxy — is on the Vulkan backend. On VK Voxy
// renders through its own frame hook (MixinSodiumOpaqueVkFrame), so the
// GL/Sodium-interop hooks stay inert (Sodium 0.9.1 also renders through MC's
// Vulkan device, so its texture views are VulkanGpuTextureView).
public boolean isVulkanBackend() {
return this.vkCore != null;
}

public Viewport<?> setupViewport(Matrix4fc vanillaProjection, Matrix4fc modelView, FogParameters fogParameters, int width, int height, double cameraX, double cameraY, double cameraZ) {
if (this.vkCore != null) return null;//VK path renders via its own hook
var viewport = this.getViewport();
if (viewport == null) {
return null;
Expand Down Expand Up @@ -228,6 +263,7 @@ public Viewport<?> setupViewport(Matrix4fc vanillaProjection, Matrix4fc modelVie


public void renderOpaque(Viewport<?> viewport, int sourceDepthTexture, int sourceColourTexture) {
if (this.vkCore != null) return;//VK path renders via its own hook
if (viewport == null) {
return;
}
Expand Down Expand Up @@ -306,7 +342,7 @@ public void renderOpaque(Viewport<?> viewport, int sourceDepthTexture, int sourc
//As much dynamic runtime stuff here
{
//Tick upload stream (this is ok to do here as upload ticking is just memory management)
UploadStream.INSTANCE.tick();
AbstractUploadStream.INSTANCE().tick();

while (this.renderDistanceTracker.setCenterAndProcess(viewport.cameraX, viewport.cameraZ) && VoxyClient.isFrexActive());//While FF is active, run until everything is processed
TimingStatistics.H.start();
Expand Down Expand Up @@ -451,7 +487,7 @@ private static Matrix4f computeProjectionMat(Matrix4fc base) {
).mulLocal(makeProjectionMatrix(nearVoxy, 16*3000));
}*/

private static Matrix4f computeProjectionMat(RenderProperties properties, Matrix4fc base) {
public static Matrix4f computeProjectionMat(RenderProperties properties, Matrix4fc base) {

//this jank is to capture the extra crap they inject like viewbobbing
var rawMCProj = Minecraft.getInstance().gameRenderer.gameRenderState().levelRenderState.cameraRenderState.projectionMatrix;
Expand Down Expand Up @@ -488,25 +524,34 @@ private boolean frexStillHasWork() {
return false;
}
//If frex is running we must tick everything to ensure correctness
UploadStream.INSTANCE.tick();
AbstractUploadStream.INSTANCE().tick();
//Done here as is allows less gl state resetup
this.modelService.tick(100_000_000);
GL11.glFinish();
return this.nodeManager.hasWork() || this.renderGen.getTaskCount()!=0 || !this.modelService.areQueuesEmpty();
}

public void setRenderDistance(float renderDistance) {
if (this.vkCore != null) {
this.vkCore.setRenderDistance(renderDistance);
return;
}
this.renderDistanceTracker.setRenderDistance((int) Math.ceil(renderDistance+1));//the +1 is to cover the outer ring of chunks when rendering a circle
}

public Viewport<?> getViewport() {
if (this.vkCore != null) return null;
if (IrisUtil.irisShadowActive()) {
return null;
}
return this.viewportSelector.getViewport();
}

public void addDebugInfo(List<String> debug) {
if (this.vkCore != null) {
this.vkCore.addDebugInfo(debug);
return;
}
debug.add("Buf/Tex [#/Mb]: [" + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000) + "],[" + GlTexture.getCount() + "/" + (GlTexture.getEstimatedTotalSize()/1_000_000)+"]");
{
this.modelService.addDebugData(debug);
Expand All @@ -525,8 +570,12 @@ public void addDebugInfo(List<String> debug) {
}

public void shutdown() {
if (this.vkCore != null) {
this.vkCore.shutdown();
return;
}
Logger.info("Flushing download stream");
DownloadStream.INSTANCE.flushWaitClear();
AbstractDownloadStream.INSTANCE().flushWaitClear();
Logger.info("Shutting down rendering");
try {
//Cleanup callbacks
Expand Down Expand Up @@ -560,7 +609,7 @@ public void shutdown() {


Logger.info("Flushing download stream");
DownloadStream.INSTANCE.flushWaitClear();
AbstractDownloadStream.INSTANCE().flushWaitClear();

//Release hold on the world
this.worldIn.releaseRef();
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/me/cortex/voxy/client/core/gl/GlBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
import static org.lwjgl.opengl.GL15.glDeleteBuffers;
import static org.lwjgl.opengl.GL45C.*;

public class GlBuffer extends TrackedObject {
public class GlBuffer extends TrackedObject implements me.cortex.voxy.client.core.rendering.IRenderList, me.cortex.voxy.client.core.rendering.util.IDeviceBuffer {
public final int id;
private final long size;
private final int flags;

@Override public int glId() { return this.id; }
@Override public long sizeBytes() { return this.size; }

private static int COUNT;
private static long TOTAL_SIZE;

Expand Down
Loading