Skip to content
Closed
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
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
Voxy is an LoD rendering mod for minecraft
Voxy is an LoD rendering mod for minecraft

## Minecraft 26.3 port

Requires Java 25, Fabric Loader 0.19.5+, Fabric API 0.160.7+26.3 and
Sodium mc26.3-0.9.2-fabric. This port uses **OpenGL**. Vulkan LoD rendering
is not implemented; Voxy disables its renderer when another backend is selected.

Build with a Java 25 JDK selected in `JAVA_HOME`:

```powershell
.\gradlew.bat build
```

The mod jar is written to `build/libs`. Optional development dependencies are
pinned in `gradle.properties`. Vivecraft, Flashback and Nvidium remain compile-only
integration targets from earlier Minecraft releases; their 26.3 compatibility is
not claimed. Iris 1.11.6+26.3 can be enabled in the development client with
`-Pruntime.iris_version=1.11.6+26.3-fabric`.

### In-world smoke test

```powershell
.\gradlew.bat -I scripts/smoke.gradle runClient
.\gradlew.bat -I scripts/smoke.gradle runClient -Pruntime.iris_version=1.11.6+26.3-fabric
```

Each invocation opens a development client and creates a new disposable world
under `run/saves/voxy-port-smoke-*`. It checks that Voxy has a renderer after
400 in-world client ticks, prints `VOXY_PORT_SMOKE_OK`, and closes the client.
The smoke-test mod is excluded from the normal build. This checks startup,
world loading and renderer lifetime, not visual correctness or shaderpack compatibility.

See [the Vulkan port assessment](docs/vulkan-port.md) for the remaining rendering work.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ jar {
}
}

project.ext.lwjglVersion = "3.4.1"
project.ext.lwjglVersion = "3.4.3"
project.ext.lwjglNatives = "natives-windows"

repositories {
Expand Down Expand Up @@ -429,4 +429,4 @@ if (!isInGHA) {
exclude group: 'net.fabricmc', module: 'fabric-loader'
}
}
}*/
}*/
46 changes: 46 additions & 0 deletions docs/vulkan-port.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Vulkan port assessment — Minecraft 26.3

Status: **not implemented**. The current 26.3 port renders LoDs with OpenGL.
The backend check in `VoxyClient.initVoxyClient` prevents OpenGL initialization
on Vulkan. Disabling Voxy there is not Vulkan rendering support.

## Findings from the 26.3 game sources

Minecraft's graphics classes now live under `com.mojang.renderpearl`.
The public `api.pipeline.ShaderType` exposes vertex and fragment stages only.
`api.commands.CommandEncoder` has no compute-dispatch API. Using this API alone
cannot preserve Voxy's GPU-driven occlusion traversal and indirect draw generation.

The native Vulkan backend has usable starting points: `VulkanDevice.vkDevice()`,
`vma()`, `graphicsQueue()` and `computeQueue()`. Integrating directly requires
access to the frontend's backend plus careful coordination with Minecraft's
command recording, image layouts, submissions and resource lifetime. It is not
safe to submit independent commands against Minecraft textures without that coordination.

There are 43 production Java files importing LWJGL OpenGL. Key dependencies are:

| Area | Existing implementation | Vulkan work |
| --- | --- | --- |
| Buffers, uploads, readbacks | `core/gl`, `UploadStream`, `DownloadStream` | Vulkan/VMA allocations, mapped staging rings and fence-based retirement |
| Shader compilation | `core/gl/shader` | SPIR-V compilation, descriptor layouts and graphics/compute pipelines |
| Visibility and LoD traversal | `HierarchicalOcclusionTraverser`, `NodeCleaner`, `AsyncNodeManager` | Compute pipelines, storage buffers, indirect dispatch and explicit barriers |
| Geometry drawing | `MDICSectionRenderer` | Indexed indirect-count draws, descriptor binding and feature checks |
| Model atlas and lighting | `SoftwareModelTextureBakery`, `LightMapHelper` | GPU image readback and sampling without OpenGL texture IDs |
| Depth and compositing | `VoxyRenderSystem`, rendering pipelines, SSAO | Shared depth conventions, render attachments, layout transitions and compute passes |
| Optional shaders | Iris integration mixins | Separate compatibility work; OpenGL Iris hooks cannot be reused as-is |

## Implementation sequence

1. Introduce an explicit backend boundary for GPU resources and frame execution,
retaining the working OpenGL implementation as a regression reference.
2. Add a Vulkan device bridge and a minimal offscreen triangle test sharing
Minecraft's device and submission lifecycle. Check with Vulkan validation layers.
3. Port buffer allocation, uploads/readbacks and shader compilation; validate a
compute dispatch with known input/output before porting the traversal algorithms.
4. Port the MDIC path and its dependencies (visibility, indirect draw generation,
model atlas, lighting, depth pyramid and composition).
5. Test actual LoD output, world/dimension changes, resizing, resource reloads,
shutdown, and GPU-memory lifetime. Only then enable Vulkan as a supported backend.

No Vulkan support claim should be made on the basis of a successful Java build
or a game launch where Voxy has disabled itself.
27 changes: 14 additions & 13 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,32 @@ org.gradle.daemon = false

# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=26.2
loader_version=0.19.3
loom_version=1.16-SNAPSHOT
minecraft_version=26.3
loader_version=0.19.5
loom_version=1.17.21

# Fabric API
fabric_api_version=0.152.2+26.2
sodium_version_modrinth=mc26.2-0.9.2-fabric
#sodium_version=0.9.0-beta.3+mc26.2r1
sodium_extra_allows=0.9.1
fabric_api_version=0.160.7+26.3
sodium_version_modrinth=mc26.3-0.9.2-fabric
# Only allow the Sodium version used to build this port.
sodium_extra_allows=


#Following modifiers are accepted: compileOnly, runtime

modmenu_version=20.0.0
lithium_version=mc26.2-0.25.1-fabric
modmenu_version=21.0.0-beta.1
lithium_version=mc26.3-0.26.1-fabric

compileOnly.iris_version=1.11.2+26.2-fabric
compileOnly.iris_version=1.11.6+26.3-fabric

chunky_version=DCgX52a5
chunky_version=4Eotm6ov
# Optional integration compile targets; no 26.3 releases were available when porting.
vivecraft_version=26.2-1.3.12-fabric
flashback_version=VaqZB1DF
nvidium_version=0.4.3-beta13-26.1


# Mod Properties
mod_version = 0.2.20-beta
mod_version = 0.2.20-beta+mc26.3
maven_group = me.cortex
archives_base_name = voxy
archives_base_name = voxy
17 changes: 17 additions & 0 deletions scripts/smoke.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
allprojects {
afterEvaluate {
def smokeCompile = tasks.register('compilePortSmoke', JavaCompile) {
source = fileTree('src/smokeTest/java')
classpath = sourceSets.main.compileClasspath + sourceSets.main.output
destinationDirectory = layout.buildDirectory.dir('smoke-classes')
options.release = 25
dependsOn tasks.named('classes')
}
def smokeResources = tasks.register('copyPortSmoke', Copy) {
from 'src/smokeTest/resources'
into layout.buildDirectory.dir('smoke-classes')
}
dependencies.add('runtimeOnly', files(layout.buildDirectory.dir('smoke-classes')))
tasks.named('runClient') { dependsOn smokeCompile, smokeResources }
}
}
6 changes: 5 additions & 1 deletion src/main/java/me/cortex/voxy/client/VoxyClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public class VoxyClient implements ClientModInitializer {
private static final HashSet<String> FREX = new HashSet<>();
private static FileLock EXCLUSIVE_LOCK;
public static void initVoxyClient() {
if (!"OpenGL".equalsIgnoreCase(com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo().backendName())) {
Logger.error("Voxy requires the OpenGL renderer; Voxy is disabled on this graphics backend.");
return;
}
Capabilities.init();//Ensure clinit is called

if (Capabilities.INSTANCE.hasBrokenDepthSampler) {
Expand Down Expand Up @@ -92,4 +96,4 @@ public static int getOcclusionDebugState() {
public static boolean disableSodiumChunkRender() {
return false;// getOcclusionDebugState() != 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.cortex.voxy.client.core;

import com.mojang.blaze3d.pipeline.DepthStencilState;
import com.mojang.blaze3d.platform.CompareOp;
import com.mojang.renderpearl.api.pipeline.DepthStencilState;
import com.mojang.renderpearl.api.pipeline.CompareOp;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.core.gl.shader.Shader;
import me.cortex.voxy.client.core.util.IrisUtil;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.cortex.voxy.client.core;

import com.mojang.blaze3d.opengl.GlConst;
import com.mojang.blaze3d.opengl.GlStateManager;
import com.mojang.renderpearl.backend.opengl.GlConst;
import com.mojang.renderpearl.backend.opengl.GlStateManager;
import me.cortex.voxy.client.TimingStatistics;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.config.VoxyConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void render(Matrix4f matrix, int texId) {
if (textureId == null) {
Logger.error("ERROR: Empty texture id for layer: " + layer);
} else {
texId = ((com.mojang.blaze3d.opengl.GlTexture)Minecraft.getInstance().getTextureManager().getTexture(textureId).getTexture()).glId();
texId = ((com.mojang.renderpearl.backend.opengl.GlTexture)Minecraft.getInstance().getTextureManager().getTexture(textureId).getTexture()).glId();
}
}
if (texId == 0) continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public ReuseVertexConsumer setUv2(int u, int v) {
return this;
}

@Override
public ReuseVertexConsumer setUv3(float u, float v) {
// Glint coordinates are not used by the block LoD texture baker.
return this;
}

@Override
public ReuseVertexConsumer setNormal(float x, float y, float z) {
return this;
Expand All @@ -104,7 +110,8 @@ public ReuseVertexConsumer quad(BakedQuad quad, boolean forceSolid) {
}

public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
this.anyShaded |= quad.materialInfo().shade();
// 26.3 represents unshaded faces with an upward shade direction override.
this.anyShaded |= quad.materialInfo().shadeDirectionOverride() != net.minecraft.core.Direction.UP;
this.anyDarkendTex |= quad.materialInfo().sprite().contents().mipmapStrategy == MipmapStrategy.DARK_CUTOUT;
this.ensureCanPut();
for (int i = 0; i < 4; i++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.cortex.voxy.client.core.model.bakery;

import com.mojang.blaze3d.GpuFormat;
import com.mojang.blaze3d.opengl.GlTexture;
import com.mojang.renderpearl.api.GpuFormat;
import com.mojang.renderpearl.backend.opengl.GlTexture;
import com.mojang.blaze3d.vertex.PoseStack;
import me.cortex.voxy.client.core.model.ModelFactory;
import me.cortex.voxy.common.util.UnsafeUtil;
Expand Down Expand Up @@ -293,9 +293,9 @@ public int renderToOutput(BlockState state, long outputBuffer, boolean rasterAsU
private static void addView(int i, float pitch, float yaw, float rotation, int flip) {
var stack = new PoseStack();
stack.translate(0.5f,0.5f,0.5f);
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
stack.mulPose(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
stack.mulPose(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
stack.rotate(makeQuatFromAxisExact(new Vector3f(0,0,1), rotation));
stack.rotate(makeQuatFromAxisExact(new Vector3f(1,0,0), pitch));
stack.rotate(makeQuatFromAxisExact(new Vector3f(0,1,0), yaw));
stack.mulPose(new Matrix4f().scale(1-2*(flip&1), 1-(flip&2), 1-((flip>>1)&2)));
stack.translate(-0.5f,-0.5f,-0.5f);
var mat = new Matrix4f(stack.last().pose());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public static void bind(int lightingIndex) {
}

public static int getLightmapTextureId() {
return ((com.mojang.blaze3d.opengl.GlTexture)(Minecraft.getInstance().gameRenderer.levelLightmap().texture())).glId();
return ((com.mojang.renderpearl.backend.opengl.GlTexture)(Minecraft.getInstance().gameRenderer.levelLightmap().texture())).glId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class MixinIrisRenderingPipeline implements IGetVoxyPatchData, IGetIrisVo
}
}

@Inject(method = "beginLevelRendering", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/opengl/GlStateManager;_activeTexture(I)V", shift = At.Shift.BEFORE), remap = false)
@Inject(method = "beginLevelRendering", at = @At(value = "INVOKE", target = "Lcom/mojang/renderpearl/backend/opengl/GlStateManager;_activeTexture(I)V", shift = At.Shift.BEFORE), remap = false)
private void voxy$injectViewportSetup(CallbackInfo ci) {
if (IrisUtil.CAPTURED_VIEWPORT_PARAMETERS != null) {
var renderer = IVoxyRenderSystemHolder.getNullable();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package me.cortex.voxy.client.mixin.iris;

import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
import com.mojang.blaze3d.resource.GraphicsResourceAllocator;
import me.cortex.voxy.client.core.IVoxyRenderSystemHolder;
import me.cortex.voxy.client.core.util.IrisUtil;
Expand Down Expand Up @@ -30,7 +30,7 @@ public class MixinLevelRenderer {
private GameRenderer gameRenderer;

@Inject(method = "render", at = @At("HEAD"), order = 100)
private void voxy$injectIrisCompat(GraphicsResourceAllocator resourceAllocator, DeltaTracker deltaTracker, boolean renderOutline, CameraRenderState cameraState, Matrix4fc modelViewMatrix, GpuBufferSlice terrainFog, Vector4f fogColor, boolean shouldRenderSky, CallbackInfo ci) {
private void voxy$injectIrisCompat(GraphicsResourceAllocator resourceAllocator, boolean renderOutline, CameraRenderState cameraState, GpuBufferSlice terrainFog, Vector4f fogColor, boolean shouldRenderSky, boolean consistentDepthRequired, CallbackInfo ci) {
if (IrisUtil.irisShaderPackEnabled()) {
var renderer = IVoxyRenderSystemHolder.getNullableHolder();
if (renderer != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.cortex.voxy.client.mixin.minecraft;


import com.mojang.blaze3d.systems.GpuDevice;
import com.mojang.renderpearl.api.device.GpuDevice;
import com.mojang.blaze3d.systems.RenderSystem;
import me.cortex.voxy.client.VoxyClient;
import org.spongepowered.asm.mixin.Mixin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.opengl.GlDebug;
import com.mojang.renderpearl.backend.opengl.GlDebug;
import me.cortex.voxy.client.core.gl.Capabilities;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package me.cortex.voxy.client.mixin.nvidium;

import com.mojang.blaze3d.opengl.GlTextureView;
import com.mojang.blaze3d.textures.GpuSampler;
import com.mojang.renderpearl.backend.opengl.GlTextureView;
import com.mojang.renderpearl.api.textures.GpuSampler;
import me.cortex.nvidium.RenderPipeline;
import me.cortex.voxy.client.core.IVoxyRenderSystemHolder;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkRenderMatrices;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package me.cortex.voxy.client.mixin.sodium;

import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.opengl.GlTextureView;
import com.mojang.blaze3d.textures.GpuSampler;
import com.mojang.renderpearl.api.buffers.GpuBuffer;
import com.mojang.renderpearl.api.commands.RenderPass;
import net.minecraft.client.renderer.oit.OitStage;
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
import com.mojang.renderpearl.backend.opengl.GlTextureView;
import com.mojang.renderpearl.api.textures.GpuSampler;
import me.cortex.voxy.client.VoxyClient;
import me.cortex.voxy.client.core.IVoxyRenderSystemHolder;
import me.cortex.voxy.client.core.rendering.Viewport;
Expand Down Expand Up @@ -31,17 +33,17 @@ public MixinDefaultChunkRenderer(ChunkVertexType vertexType) {
}

@Inject(method = "render", at = @At(value = "HEAD"), cancellable = true)
private void voxy$cancelThingie(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, FogParameters parameters, boolean indexedRenderingEnabled, GpuSampler terrainSampler, GpuBufferSlice uniformData, GpuBuffer sectionTimeInfo, CallbackInfo ci) {
private void voxy$cancelThingie(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, FogParameters parameters, boolean indexedRenderingEnabled, RenderPass pass, GpuSampler terrainSampler, GpuBufferSlice uniformData, GpuBuffer sectionTimeInfo, OitStage oitStage, CallbackInfo ci) {
if (VoxyClient.disableSodiumChunkRender()) {
super.begin(renderPass, parameters, terrainSampler);
super.begin(renderPass, parameters, terrainSampler, oitStage);
this.doRender(matrices, renderPass, camera, parameters);
super.end(renderPass);
ci.cancel();
}
}

@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 voxy$injectRender(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, FogParameters parameters, boolean indexedRenderingEnabled, GpuSampler terrainSampler, GpuBufferSlice uniformData, GpuBuffer sectionTimeInfo, CallbackInfo ci) {
private void voxy$injectRender(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform camera, FogParameters parameters, boolean indexedRenderingEnabled, RenderPass pass, GpuSampler terrainSampler, GpuBufferSlice uniformData, GpuBuffer sectionTimeInfo, OitStage oitStage, CallbackInfo ci) {
this.doRender(matrices, renderPass, camera, parameters);
}

Expand Down
11 changes: 9 additions & 2 deletions src/main/java/me/cortex/voxy/client/taskbar/WindowsTaskbar.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
import com.sun.jna.platform.win32.COM.COMInvoker;
import com.sun.jna.platform.win32.*;
import com.sun.jna.ptr.PointerByReference;
import org.lwjgl.glfw.GLFWNativeWin32;
import static org.lwjgl.sdl.SDLProperties.SDL_GetPointerProperty;
import static org.lwjgl.sdl.SDLVideo.SDL_GetWindowProperties;
import static org.lwjgl.sdl.SDLVideo.SDL_PROP_WINDOW_WIN32_HWND_POINTER;

public class WindowsTaskbar extends COMInvoker implements Taskbar.ITaskbar {
private final WinDef.HWND hwnd;
WindowsTaskbar(long windowId) {
long nativeWindow = SDL_GetPointerProperty(SDL_GetWindowProperties(windowId),
SDL_PROP_WINDOW_WIN32_HWND_POINTER, 0);
if (nativeWindow == 0) {
throw new IllegalStateException("SDL window has no Win32 handle");
}
var itaskbar3res = new PointerByReference();

if (W32Errors.FAILED(Ole32.INSTANCE.CoCreateInstance(new Guid.GUID("56FDF344-FD6D-11d0-958A-006097C9A090"),
Expand All @@ -20,7 +27,7 @@ public class WindowsTaskbar extends COMInvoker implements Taskbar.ITaskbar {
}

this.setPointer(itaskbar3res.getValue());
this.hwnd = new WinDef.HWND(new Pointer(GLFWNativeWin32.glfwGetWin32Window(windowId)));
this.hwnd = new WinDef.HWND(new Pointer(nativeWindow));

this.invokeNative(3); // HrInit
}
Expand Down
Loading