diff --git a/README.md b/README.md
index 917d9bdb6..74be2826e 100644
--- a/README.md
+++ b/README.md
@@ -1,77 +1,86 @@
-#
VulkanMod
+#
VulkanMod (Multi-Screen Fix Fork)
-This is a fabric mod that introduces a brand new **Vulkan** based voxel rendering engine to **Minecraft java** in order to both replace the default OpenGL renderer and bring performance improvements.
+[English](#english) | [Українська](#ukrainian)
-### Why?
-- Highly experimental project that overhauls and modernizes the internal renderer for Minecraft.
-- Updates the renderer from OpenGL 3.2 to Vulkan 1.2.
-- Provides a potential reference for a future-proof Vulkan codebase for Minecraft Java.
-- Utilizes the VulkanAPI to allow for capabilities not always possible with OpenGL.
-- Including reduced CPU Overhead and use of newer, modern hardware capabilities.
+---
-### Demonstration Video:
+
+## 🇬🇧 English Description
-[](https://youtu.be/sbr7UxcAmOE)
+This is a fork of the **VulkanMod** fabric mod, which introduces a brand new **Vulkan** based voxel rendering engine to **Minecraft Java**.
-## FAQ
-- Remember to check the [Wiki](https://github.com/xCollateral/VulkanMod/wiki) we wrote before asking for support!
+**The main purpose of this fork is to fix critical bugs and ensure stability on multi-screen systems (multi-monitor setups).**
-## Installation
-
-### Download Links:
+### Why this fork?
+- **Multi-Screen Fix:** The original mod may have issues with initialization or rendering when using multiple displays. This version is designed to solve these specific problems.
+- **Retains original benefits:** Replaces the default OpenGL renderer with Vulkan 1.2 and brings performance improvements.
+- **Performance:** Reduced CPU overhead and utilization of modern hardware capabilities.
-- [](https://www.curseforge.com/minecraft/mc-mods/vulkanmod)
+### Demonstration (Original Mod):
+[](https://youtu.be/sbr7UxcAmOE)
-- [](https://modrinth.com/mod/vulkanmod/versions)
+## Installation
-- [](https://github.com/xCollateral/VulkanMod/releases)
+### Download:
+Please download the releases **from this repository** to get the multi-screen fixes.
### Install guide:
>1) Install the [fabric modloader](https://fabricmc.net).
->1) Download and put the `Vulkanmod.jar` file into `.minecraft/mods`
->1) Enjoy !
-
-## Useful links
-
-
- | Discord server |
- Ko-Fi |
-
-
-
-
-
-
- |
-
-
-
-
- |
-
-
-
+>1) Download the `Vulkanmod.jar` file (from this fork) and put it into `.minecraft/mods`.
+>1) Enjoy playing on multiple monitors!
## Features
-### Optimizations:
+### Fixes in this fork:
+>- [x] **Correct behavior on multi-monitor systems**
+>- [x] Window initialization fixes
+
+### Optimizations (from original mod):
>- [x] Multiple chunk culling algorithms
->- [x] Reduced CPU overhead
->- [x] Improved GPU performance
->- [x] Indirect Draw mode (reduces CPU overhead)
+>- [x] Reduced CPU overhead & Improved GPU performance
+>- [x] Indirect Draw mode
>- [x] Chunk rendering optimizations
-### New changes:
->- [x] Native Wayland support
->- [x] GPU selector
->- [x] Windowed fullscreen mode
->- [x] Revamped graphic settings menu
->- [x] Resizable render frame queue
->- [ ] Shader support
->- [ ] Removed Herobrine
+## Notes
+- This fork is based on the work by [xCollateral](https://github.com/xCollateral/VulkanMod).
+- Please report multi-screen specific issues in the Issues tab of this repository.
+
+---
+
+## 🇺🇦 Український опис
-## Notes
-- This mod is still in development, please report issues in the [issue tab](https://github.com/xCollateral/VulkanMod/issues) with logs attached!
-- This mode isn't just "minecraft on vulkan" (e.g: [zink](https://docs.mesa3d.org/drivers/zink.html) ), it is a full rewrite of the minecraft renderer.
+Це форк Fabric-моду **VulkanMod**, який впроваджує новий воксельний рушій рендерингу на базі **Vulkan** для **Minecraft Java**.
+
+**Головна мета цього форку — виправлення критичних помилок та забезпечення стабільної роботи на системах із кількома моніторами (мультиекранних конфігураціях).**
+
+### Чому цей форк?
+- **Виправлення мультиекранності:** Оригінальний мод може мати проблеми з ініціалізацією або рендерингом при використанні кількох дисплеїв. Ця версія створена, щоб вирішити ці проблеми.
+- **Зберігає всі переваги оригінального VulkanMod:** Заміна стандартного OpenGL рендерера на Vulkan 1.2.
+- **Продуктивність:** Зменшення навантаження на процесор та використання сучасних можливостей відеокарт.
+
+### Встановлення
+
+### Завантаження:
+Завантажуйте релізи **саме з цього репозиторію (форку)**, щоб отримати виправлення для мультиекранних систем.
+
+### Інструкція:
+>1) Встановіть [Fabric modloader](https://fabricmc.net).
+>1) Завантажте файл `Vulkanmod.jar` (з цього форку) та помістіть його в папку `.minecraft/mods`.
+>1) Насолоджуйтесь грою на кількох моніторах!
+
+## Особливості
+
+### Виправлення у цьому форку:
+>- [x] **Коректна робота на системах з кількома моніторами**
+>- [x] Виправлення помилок ініціалізації вікна
+
+### Оптимізації (з оригінального моду):
+>- [x] Різноманітні алгоритми відсікання чанків (culling)
+>- [x] Зменшене навантаження на CPU та покращена продуктивність GPU
+>- [x] Режим Indirect Draw (зменшує оверхед CPU)
+>- [x] Оптимізація рендерингу чанків
+## Примітки
+- Цей форк базується на чудовій роботі [xCollateral](https://github.com/xCollateral/VulkanMod).
+- Будь ласка, повідомляйте про проблеми, специфічні для мультиекранної роботи, у вкладку Issues цього репозиторію.
diff --git a/src/main/java/net/vulkanmod/config/Config.java b/src/main/java/net/vulkanmod/config/Config.java
index e2bebdb7f..dd5bd2aa7 100644
--- a/src/main/java/net/vulkanmod/config/Config.java
+++ b/src/main/java/net/vulkanmod/config/Config.java
@@ -15,7 +15,7 @@
public class Config {
public VideoModeSet.VideoMode videoMode = VideoModeManager.getFirstAvailable().getVideoMode();
public int windowMode = 0;
-
+ public int targetMonitor = 0;
public int advCulling = 2;
public boolean indirectDraw = true;
diff --git a/src/main/java/net/vulkanmod/config/option/Options.java b/src/main/java/net/vulkanmod/config/option/Options.java
index 560e6d2c9..db5821995 100644
--- a/src/main/java/net/vulkanmod/config/option/Options.java
+++ b/src/main/java/net/vulkanmod/config/option/Options.java
@@ -5,7 +5,6 @@
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ParticleStatus;
import net.vulkanmod.Initializer;
-import net.vulkanmod.config.Config;
import net.vulkanmod.config.gui.OptionBlock;
import net.vulkanmod.config.video.VideoModeManager;
import net.vulkanmod.config.video.VideoModeSet;
@@ -15,17 +14,23 @@
import net.vulkanmod.render.vertex.TerrainRenderType;
import net.vulkanmod.vulkan.Renderer;
import net.vulkanmod.vulkan.device.DeviceManager;
+import org.lwjgl.PointerBuffer;
+import org.lwjgl.glfw.GLFW;
+import java.util.ArrayList; // Додано
+import java.util.List;
import java.util.stream.IntStream;
public abstract class Options {
public static boolean fullscreenDirty = false;
- static Config config = Initializer.CONFIG;
static Minecraft minecraft = Minecraft.getInstance();
static Window window = minecraft.getWindow();
static net.minecraft.client.Options minecraftOptions = minecraft.options;
public static OptionBlock[] getVideoOpts() {
+ var config = Initializer.CONFIG;
+ if (config == null) return new OptionBlock[0];
+
var videoMode = config.videoMode;
var videoModeSet = VideoModeManager.getFromVideoMode(videoMode);
@@ -37,6 +42,54 @@ public static OptionBlock[] getVideoOpts() {
VideoModeManager.selectedVideoMode = videoMode;
var refreshRates = videoModeSet.getRefreshRates();
+ // --- ВИБІР МОНІТОРА З НАЗВАМИ ---
+ PointerBuffer monitors = GLFW.glfwGetMonitors();
+ int monitorCount = (monitors != null) ? monitors.limit() : 1;
+ Integer[] monitorIndices = new Integer[monitorCount];
+ List monitorNames = new ArrayList<>(); // Список для збереження назв
+
+ for (int i = 0; i < monitorCount; i++) {
+ monitorIndices[i] = i;
+ String name = "Monitor " + (i + 1); // Дефолтна назва
+
+ if (monitors != null) {
+ long handle = monitors.get(i);
+ // Отримуємо реальну назву монітора від драйвера
+ String fetchedName = GLFW.glfwGetMonitorName(handle);
+ if (fetchedName != null && !fetchedName.isEmpty()) {
+ // Додаємо індекс у дужках, щоб розрізняти однакові моделі
+ name = fetchedName + " (" + (i + 1) + ")";
+ }
+ }
+ monitorNames.add(name);
+ }
+
+ if (config.targetMonitor >= monitorCount) {
+ config.targetMonitor = 0;
+ }
+
+ CyclingOption monitorOption = (CyclingOption) new CyclingOption<>(
+ Component.literal("Monitor"),
+ monitorIndices,
+ (value) -> {
+ if (Initializer.CONFIG != null) {
+ Initializer.CONFIG.targetMonitor = value;
+ VideoModeManager.init();
+ }
+
+ if (minecraftOptions.fullscreen().get())
+ fullscreenDirty = true;
+ },
+ () -> (Initializer.CONFIG != null) ? Initializer.CONFIG.targetMonitor : 0)
+ .setTranslator(value -> {
+ // Використовуємо збережену назву зі списку
+ if (value >= 0 && value < monitorNames.size()) {
+ return Component.literal(monitorNames.get(value));
+ }
+ return Component.literal("Monitor " + (value + 1));
+ });
+ // ----------------------
+
CyclingOption RefreshRate = (CyclingOption) new CyclingOption<>(
Component.translatable("vulkanmod.options.refreshRate"),
refreshRates.toArray(new Integer[0]),
@@ -78,6 +131,7 @@ public static OptionBlock[] getVideoOpts() {
return new OptionBlock[]{
new OptionBlock("", new Option>[]{
+ monitorOption,
resolutionOption,
RefreshRate,
new CyclingOption<>(Component.translatable("vulkanmod.options.windowMode"),
@@ -85,125 +139,127 @@ public static OptionBlock[] getVideoOpts() {
value -> {
boolean exclusiveFullscreen = value == WindowMode.EXCLUSIVE_FULLSCREEN;
minecraftOptions.fullscreen()
- .set(exclusiveFullscreen);
+ .set(exclusiveFullscreen);
- config.windowMode = value.mode;
+ if (Initializer.CONFIG != null) Initializer.CONFIG.windowMode = value.mode;
fullscreenDirty = true;
},
- () -> WindowMode.fromValue(config.windowMode))
+ () -> (Initializer.CONFIG != null) ? WindowMode.fromValue(Initializer.CONFIG.windowMode) : WindowMode.WINDOWED)
.setTranslator(value -> Component.translatable(WindowMode.getComponentName(value))),
new RangeOption(Component.translatable("options.framerateLimit"),
- 10, 260, 10,
- value -> Component.nullToEmpty(value == 260 ?
- Component.translatable(
- "options.framerateLimit.max")
- .getString() :
- String.valueOf(value)),
- value -> {
- minecraftOptions.framerateLimit().set(value);
- minecraft.getFramerateLimitTracker().setFramerateLimit(value);
- },
- () -> minecraftOptions.framerateLimit().get()),
+ 10, 260, 10,
+ value -> Component.nullToEmpty(value == 260 ?
+ Component.translatable(
+ "options.framerateLimit.max")
+ .getString() :
+ String.valueOf(value)),
+ value -> {
+ minecraftOptions.framerateLimit().set(value);
+ minecraft.getFramerateLimitTracker().setFramerateLimit(value);
+ },
+ () -> minecraftOptions.framerateLimit().get()),
new SwitchOption(Component.translatable("options.vsync"),
- value -> {
- minecraftOptions.enableVsync().set(value);
- window.updateVsync(value);
- },
- () -> minecraftOptions.enableVsync().get()),
+ value -> {
+ minecraftOptions.enableVsync().set(value);
+ window.updateVsync(value);
+ },
+ () -> minecraftOptions.enableVsync().get()),
new CyclingOption<>(Component.translatable("options.inactivityFpsLimit"),
- InactivityFpsLimit.values(),
- value -> minecraftOptions.inactivityFpsLimit().set(value),
- () -> minecraftOptions.inactivityFpsLimit().get())
+ InactivityFpsLimit.values(),
+ value -> minecraftOptions.inactivityFpsLimit().set(value),
+ () -> minecraftOptions.inactivityFpsLimit().get())
.setTranslator(inactivityFpsLimit -> Component.translatable(inactivityFpsLimit.getKey()))
}),
new OptionBlock("", new Option>[]{
new RangeOption(Component.translatable("options.guiScale"),
- 0, window.calculateScale(0, minecraft.isEnforceUnicode()), 1,
- value -> Component.translatable((value == 0)
- ? "options.guiScale.auto"
- : String.valueOf(value)),
- value -> {
- minecraftOptions.guiScale().set(value);
- minecraft.resizeDisplay();
- },
- () -> (minecraftOptions.guiScale().get())),
+ 0, window.calculateScale(0, minecraft.isEnforceUnicode()), 1,
+ value -> Component.translatable((value == 0)
+ ? "options.guiScale.auto"
+ : String.valueOf(value)),
+ value -> {
+ minecraftOptions.guiScale().set(value);
+ minecraft.resizeDisplay();
+ },
+ () -> (minecraftOptions.guiScale().get())),
new RangeOption(Component.translatable("options.gamma"),
- 0, 100, 1,
- value -> Component.translatable(switch (value) {
- case 0 -> "options.gamma.min";
- case 50 -> "options.gamma.default";
- case 100 -> "options.gamma.max";
- default -> String.valueOf(value);
- }),
- value -> minecraftOptions.gamma().set(value * 0.01),
- () -> (int) (minecraftOptions.gamma().get() * 100.0)),
+ 0, 100, 1,
+ value -> Component.translatable(switch (value) {
+ case 0 -> "options.gamma.min";
+ case 50 -> "options.gamma.default";
+ case 100 -> "options.gamma.max";
+ default -> String.valueOf(value);
+ }),
+ value -> minecraftOptions.gamma().set(value * 0.01),
+ () -> (int) (minecraftOptions.gamma().get() * 100.0)),
}),
new OptionBlock("", new Option>[]{
new SwitchOption(Component.translatable("options.viewBobbing"),
- (value) -> minecraftOptions.bobView().set(value),
- () -> minecraftOptions.bobView().get()),
+ (value) -> minecraftOptions.bobView().set(value),
+ () -> minecraftOptions.bobView().get()),
new CyclingOption<>(Component.translatable("options.attackIndicator"),
- AttackIndicatorStatus.values(),
- value -> minecraftOptions.attackIndicator().set(value),
- () -> minecraftOptions.attackIndicator().get())
+ AttackIndicatorStatus.values(),
+ value -> minecraftOptions.attackIndicator().set(value),
+ () -> minecraftOptions.attackIndicator().get())
.setTranslator(value -> Component.translatable(value.getKey())),
new SwitchOption(Component.translatable("options.autosaveIndicator"),
- value -> minecraftOptions.showAutosaveIndicator().set(value),
- () -> minecraftOptions.showAutosaveIndicator().get()),
+ value -> minecraftOptions.showAutosaveIndicator().set(value),
+ () -> minecraftOptions.showAutosaveIndicator().get()),
})
};
}
public static OptionBlock[] getGraphicsOpts() {
+ var config = Initializer.CONFIG;
+
return new OptionBlock[]{
new OptionBlock("", new Option>[]{
new RangeOption(Component.translatable("options.renderDistance"),
- 2, 32, 1,
- (value) -> minecraftOptions.renderDistance().set(value),
- () -> minecraftOptions.renderDistance().get()),
+ 2, 32, 1,
+ (value) -> minecraftOptions.renderDistance().set(value),
+ () -> minecraftOptions.renderDistance().get()),
new RangeOption(Component.translatable("options.simulationDistance"),
- 5, 32, 1,
- (value) -> minecraftOptions.simulationDistance().set(value),
- () -> minecraftOptions.simulationDistance().get()),
+ 5, 32, 1,
+ (value) -> minecraftOptions.simulationDistance().set(value),
+ () -> minecraftOptions.simulationDistance().get()),
new CyclingOption<>(Component.translatable("options.prioritizeChunkUpdates"),
- PrioritizeChunkUpdates.values(),
- value -> minecraftOptions.prioritizeChunkUpdates().set(value),
- () -> minecraftOptions.prioritizeChunkUpdates().get())
+ PrioritizeChunkUpdates.values(),
+ value -> minecraftOptions.prioritizeChunkUpdates().set(value),
+ () -> minecraftOptions.prioritizeChunkUpdates().get())
.setTranslator(value -> Component.translatable(value.getKey())),
}),
new OptionBlock("", new Option>[]{
new CyclingOption<>(Component.translatable("options.graphics"),
- new GraphicsStatus[]{GraphicsStatus.FAST, GraphicsStatus.FANCY},
- value -> minecraftOptions.graphicsMode().set(value),
- () -> minecraftOptions.graphicsMode().get())
+ new GraphicsStatus[]{GraphicsStatus.FAST, GraphicsStatus.FANCY},
+ value -> minecraftOptions.graphicsMode().set(value),
+ () -> minecraftOptions.graphicsMode().get())
.setTranslator(graphicsMode -> Component.translatable(graphicsMode.getKey())),
new CyclingOption<>(Component.translatable("options.particles"),
- new ParticleStatus[]{ParticleStatus.MINIMAL, ParticleStatus.DECREASED, ParticleStatus.ALL},
- value -> minecraftOptions.particles().set(value),
- () -> minecraftOptions.particles().get())
+ new ParticleStatus[]{ParticleStatus.MINIMAL, ParticleStatus.DECREASED, ParticleStatus.ALL},
+ value -> minecraftOptions.particles().set(value),
+ () -> minecraftOptions.particles().get())
.setTranslator(particlesMode -> Component.translatable(particlesMode.getKey())),
new CyclingOption<>(Component.translatable("options.renderClouds"),
- CloudStatus.values(),
- value -> minecraftOptions.cloudStatus().set(value),
- () -> minecraftOptions.cloudStatus().get())
+ CloudStatus.values(),
+ value -> minecraftOptions.cloudStatus().set(value),
+ () -> minecraftOptions.cloudStatus().get())
.setTranslator(value -> Component.translatable(value.getKey())),
new RangeOption(Component.translatable("options.renderCloudsDistance"),
- 2, 128, 1,
- (value) -> minecraftOptions.cloudRange().set(value),
- () -> minecraftOptions.cloudRange().get()),
+ 2, 128, 1,
+ (value) -> minecraftOptions.cloudRange().set(value),
+ () -> minecraftOptions.cloudRange().get()),
new CyclingOption<>(Component.translatable("options.ao"),
- new Integer[]{LightMode.FLAT, LightMode.SMOOTH, LightMode.SUB_BLOCK},
- (value) -> {
- if (value > LightMode.FLAT)
- minecraftOptions.ambientOcclusion().set(true);
- else
- minecraftOptions.ambientOcclusion().set(false);
-
- config.ambientOcclusion = value;
-
- minecraft.levelRenderer.allChanged();
- },
- () -> config.ambientOcclusion)
+ new Integer[]{LightMode.FLAT, LightMode.SMOOTH, LightMode.SUB_BLOCK},
+ (value) -> {
+ if (value > LightMode.FLAT)
+ minecraftOptions.ambientOcclusion().set(true);
+ else
+ minecraftOptions.ambientOcclusion().set(false);
+
+ if (Initializer.CONFIG != null) Initializer.CONFIG.ambientOcclusion = value;
+
+ minecraft.levelRenderer.allChanged();
+ },
+ () -> (Initializer.CONFIG != null) ? Initializer.CONFIG.ambientOcclusion : LightMode.SMOOTH)
.setTranslator(value -> Component.translatable(switch (value) {
case LightMode.FLAT -> "options.off";
case LightMode.SMOOTH -> "options.on";
@@ -212,45 +268,47 @@ public static OptionBlock[] getGraphicsOpts() {
}))
.setTooltip(Component.translatable("vulkanmod.options.ao.subBlock.tooltip")),
new RangeOption(Component.translatable("options.biomeBlendRadius"),
- 0, 7, 1,
- value -> {
- int v = value * 2 + 1;
- return Component.nullToEmpty("%d x %d".formatted(v, v));
- },
- (value) -> {
- minecraftOptions.biomeBlendRadius().set(value);
- minecraft.levelRenderer.allChanged();
- },
- () -> minecraftOptions.biomeBlendRadius().get()),
+ 0, 7, 1,
+ value -> {
+ int v = value * 2 + 1;
+ return Component.nullToEmpty("%d x %d".formatted(v, v));
+ },
+ (value) -> {
+ minecraftOptions.biomeBlendRadius().set(value);
+ minecraft.levelRenderer.allChanged();
+ },
+ () -> minecraftOptions.biomeBlendRadius().get()),
}),
new OptionBlock("", new Option>[]{
new SwitchOption(Component.translatable("options.entityShadows"),
- value -> minecraftOptions.entityShadows().set(value),
- () -> minecraftOptions.entityShadows().get()),
+ value -> minecraftOptions.entityShadows().set(value),
+ () -> minecraftOptions.entityShadows().get()),
new RangeOption(Component.translatable("options.entityDistanceScaling"),
- 50, 500, 25,
- value -> minecraftOptions.entityDistanceScaling().set(value * 0.01),
- () -> minecraftOptions.entityDistanceScaling().get().intValue() * 100),
+ 50, 500, 25,
+ value -> minecraftOptions.entityDistanceScaling().set(value * 0.01),
+ () -> minecraftOptions.entityDistanceScaling().get().intValue() * 100),
new CyclingOption<>(Component.translatable("options.mipmapLevels"),
- new Integer[]{0, 1, 2, 3, 4},
- value -> {
- minecraftOptions.mipmapLevels().set(value);
- minecraft.updateMaxMipLevel(value);
- minecraft.delayTextureReload();
- },
- () -> minecraftOptions.mipmapLevels().get())
+ new Integer[]{0, 1, 2, 3, 4},
+ value -> {
+ minecraftOptions.mipmapLevels().set(value);
+ minecraft.updateMaxMipLevel(value);
+ minecraft.delayTextureReload();
+ },
+ () -> minecraftOptions.mipmapLevels().get())
.setTranslator(value -> Component.nullToEmpty(value.toString()))
})
};
}
public static OptionBlock[] getOptimizationOpts() {
+ var config = Initializer.CONFIG;
+
return new OptionBlock[]{
new OptionBlock("", new Option[]{
new CyclingOption<>(Component.translatable("vulkanmod.options.advCulling"),
- new Integer[]{1, 2, 3, 10},
- value -> config.advCulling = value,
- () -> config.advCulling)
+ new Integer[]{1, 2, 3, 10},
+ value -> { if(Initializer.CONFIG != null) Initializer.CONFIG.advCulling = value; },
+ () -> (Initializer.CONFIG != null) ? Initializer.CONFIG.advCulling : 2)
.setTranslator(value -> Component.translatable(switch (value) {
case 1 -> "vulkanmod.options.advCulling.aggressive";
case 2 -> "vulkanmod.options.advCulling.normal";
@@ -260,27 +318,27 @@ public static OptionBlock[] getOptimizationOpts() {
}))
.setTooltip(Component.translatable("vulkanmod.options.advCulling.tooltip")),
new SwitchOption(Component.translatable("vulkanmod.options.entityCulling"),
- value -> config.entityCulling = value,
- () -> config.entityCulling)
+ value -> { if(Initializer.CONFIG != null) Initializer.CONFIG.entityCulling = value; },
+ () -> (Initializer.CONFIG != null) && Initializer.CONFIG.entityCulling)
.setTooltip(Component.translatable("vulkanmod.options.entityCulling.tooltip")),
new SwitchOption(Component.translatable("vulkanmod.options.uniqueOpaqueLayer"),
- value -> {
- config.uniqueOpaqueLayer = value;
- TerrainRenderType.updateMapping();
- minecraft.levelRenderer.allChanged();
- },
- () -> config.uniqueOpaqueLayer)
+ value -> {
+ if(Initializer.CONFIG != null) Initializer.CONFIG.uniqueOpaqueLayer = value;
+ TerrainRenderType.updateMapping();
+ minecraft.levelRenderer.allChanged();
+ },
+ () -> (Initializer.CONFIG != null) && Initializer.CONFIG.uniqueOpaqueLayer)
.setTooltip(Component.translatable("vulkanmod.options.uniqueOpaqueLayer.tooltip")),
new SwitchOption(Component.translatable("vulkanmod.options.backfaceCulling"),
- value -> {
- config.backFaceCulling = value;
- Minecraft.getInstance().levelRenderer.allChanged();
- },
- () -> config.backFaceCulling)
+ value -> {
+ if(Initializer.CONFIG != null) Initializer.CONFIG.backFaceCulling = value;
+ Minecraft.getInstance().levelRenderer.allChanged();
+ },
+ () -> (Initializer.CONFIG != null) && Initializer.CONFIG.backFaceCulling)
.setTooltip(Component.translatable("vulkanmod.options.backfaceCulling.tooltip")),
new SwitchOption(Component.translatable("vulkanmod.options.indirectDraw"),
- value -> config.indirectDraw = value,
- () -> config.indirectDraw)
+ value -> { if(Initializer.CONFIG != null) Initializer.CONFIG.indirectDraw = value; },
+ () -> (Initializer.CONFIG != null) && Initializer.CONFIG.indirectDraw)
.setTooltip(Component.translatable("vulkanmod.options.indirectDraw.tooltip")),
})
};
@@ -288,15 +346,17 @@ public static OptionBlock[] getOptimizationOpts() {
}
public static OptionBlock[] getOtherOpts() {
+ var config = Initializer.CONFIG;
+
return new OptionBlock[]{
new OptionBlock("", new Option[]{
new RangeOption(Component.translatable("vulkanmod.options.builderThreads"),
- 0, (Runtime.getRuntime().availableProcessors() - 1), 1,
- value -> {
- config.builderThreads = value;
- WorldRenderer.getInstance().getTaskDispatcher().createThreads(value);
- },
- () -> config.builderThreads)
+ 0, (Runtime.getRuntime().availableProcessors() - 1), 1,
+ value -> {
+ if(Initializer.CONFIG != null) Initializer.CONFIG.builderThreads = value;
+ WorldRenderer.getInstance().getTaskDispatcher().createThreads(value);
+ },
+ () -> (Initializer.CONFIG != null) ? Initializer.CONFIG.builderThreads : 1)
.setTranslator(value -> {
if (value == 0)
return Component.translatable("vulkanmod.options.builderThreads.auto");
@@ -304,27 +364,27 @@ public static OptionBlock[] getOtherOpts() {
return Component.nullToEmpty(String.valueOf(value));
}),
new RangeOption(Component.translatable("vulkanmod.options.frameQueue"),
- 2, 5, 1,
- value -> {
- config.frameQueueSize = value;
- Renderer.scheduleSwapChainUpdate();
- }, () -> config.frameQueueSize)
+ 2, 5, 1,
+ value -> {
+ if(Initializer.CONFIG != null) Initializer.CONFIG.frameQueueSize = value;
+ Renderer.scheduleSwapChainUpdate();
+ }, () -> (Initializer.CONFIG != null) ? Initializer.CONFIG.frameQueueSize : 2)
.setTooltip(Component.translatable("vulkanmod.options.frameQueue.tooltip")),
new SwitchOption(Component.translatable("vulkanmod.options.textureAnimations"),
- value -> {
- config.textureAnimations = value;
- },
- () -> config.textureAnimations),
+ value -> {
+ if(Initializer.CONFIG != null) Initializer.CONFIG.textureAnimations = value;
+ },
+ () -> (Initializer.CONFIG != null) && Initializer.CONFIG.textureAnimations),
}),
new OptionBlock("", new Option[]{
new CyclingOption<>(Component.translatable("vulkanmod.options.deviceSelector"),
- IntStream.range(-1, DeviceManager.suitableDevices.size()).boxed()
- .toArray(Integer[]::new),
- value -> config.device = value,
- () -> config.device)
+ IntStream.range(-1, DeviceManager.suitableDevices.size()).boxed()
+ .toArray(Integer[]::new),
+ value -> { if(Initializer.CONFIG != null) Initializer.CONFIG.device = value; },
+ () -> (Initializer.CONFIG != null) ? Initializer.CONFIG.device : -1)
.setTranslator(value -> Component.translatable((value == -1)
- ? "vulkanmod.options.deviceSelector.auto"
- : DeviceManager.suitableDevices.get(
+ ? "vulkanmod.options.deviceSelector.auto"
+ : DeviceManager.suitableDevices.get(
value).deviceName)
)
.setTooltip(Component.nullToEmpty("%s: %s".formatted(
@@ -334,4 +394,4 @@ public static OptionBlock[] getOtherOpts() {
};
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/net/vulkanmod/config/video/VideoModeManager.java b/src/main/java/net/vulkanmod/config/video/VideoModeManager.java
index 985455a63..6d2045745 100644
--- a/src/main/java/net/vulkanmod/config/video/VideoModeManager.java
+++ b/src/main/java/net/vulkanmod/config/video/VideoModeManager.java
@@ -1,6 +1,8 @@
package net.vulkanmod.config.video;
import net.vulkanmod.Initializer;
+import net.vulkanmod.config.Config;
+import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWVidMode;
@@ -16,13 +18,43 @@ public abstract class VideoModeManager {
public static VideoModeSet.VideoMode selectedVideoMode;
public static void init() {
- long monitor = glfwGetPrimaryMonitor();
+ // Отримуємо доступ до конфігурації
+ Config config = Initializer.CONFIG;
+
+ PointerBuffer monitors = GLFW.glfwGetMonitors();
+ long monitor;
+
+ // БЕЗПЕЧНА ПЕРЕВІРКА: Якщо конфіг ще не ініціалізовано, використовуємо дефолтний монітор
+ if (config == null) {
+ monitor = glfwGetPrimaryMonitor();
+ } else {
+ int targetIndex = config.targetMonitor;
+ if (monitors != null && targetIndex >= 0 && targetIndex < monitors.limit()) {
+ monitor = monitors.get(targetIndex);
+ } else {
+ monitor = glfwGetPrimaryMonitor();
+ if (monitors != null && targetIndex >= monitors.limit()) {
+ config.targetMonitor = 0;
+ }
+ }
+ }
+
+ updateMonitor(monitor);
+ }
+
+ /**
+ * Оновлює кешовані відеорежими для вказаного монітора.
+ * Викликається при ініціалізації або зміні монітора (наприклад, з WindowMixin).
+ */
+ public static void updateMonitor(long monitor) {
osVideoMode = getCurrentVideoMode(monitor);
- videoModeSets = populateVideoResolutions(GLFW.glfwGetPrimaryMonitor());
+ videoModeSets = populateVideoResolutions(monitor);
}
public static void applySelectedVideoMode() {
- Initializer.CONFIG.videoMode = selectedVideoMode;
+ if (Initializer.CONFIG != null) {
+ Initializer.CONFIG.videoMode = selectedVideoMode;
+ }
}
public static VideoModeSet[] getVideoResolutions() {
@@ -30,7 +62,7 @@ public static VideoModeSet[] getVideoResolutions() {
}
public static VideoModeSet getFirstAvailable() {
- if(videoModeSets != null)
+ if(videoModeSets != null && videoModeSets.length > 0)
return videoModeSets[videoModeSets.length - 1];
else
return VideoModeSet.getDummy();
@@ -43,6 +75,9 @@ public static VideoModeSet.VideoMode getOsVideoMode() {
public static VideoModeSet.VideoMode getCurrentVideoMode(long monitor){
GLFWVidMode vidMode = GLFW.glfwGetVideoMode(monitor);
+ if (vidMode == null)
+ vidMode = GLFW.glfwGetVideoMode(GLFW.glfwGetPrimaryMonitor());
+
if (vidMode == null)
throw new NullPointerException("Unable to get current video mode");
@@ -52,6 +87,8 @@ public static VideoModeSet.VideoMode getCurrentVideoMode(long monitor){
public static VideoModeSet[] populateVideoResolutions(long monitor) {
GLFWVidMode.Buffer buffer = GLFW.glfwGetVideoModes(monitor);
+ if (buffer == null) return new VideoModeSet[0];
+
List videoModeSets = new ArrayList<>();
int currWidth = 0, currHeight = 0, currBitDepth = 0;
@@ -76,7 +113,9 @@ public static VideoModeSet[] populateVideoResolutions(long monitor) {
videoModeSets.add(videoModeSet);
}
- videoModeSet.addRefreshRate(refreshRate);
+ if (videoModeSet != null) {
+ videoModeSet.addRefreshRate(refreshRate);
+ }
}
VideoModeSet[] arr = new VideoModeSet[videoModeSets.size()];
@@ -86,6 +125,8 @@ public static VideoModeSet[] populateVideoResolutions(long monitor) {
}
public static VideoModeSet getFromVideoMode(VideoModeSet.VideoMode videoMode) {
+ if (videoModeSets == null) return null;
+
for (var set : videoModeSets) {
if (set.width == videoMode.width && set.height == videoMode.height)
return set;
@@ -93,4 +134,4 @@ public static VideoModeSet getFromVideoMode(VideoModeSet.VideoMode videoMode) {
return null;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/net/vulkanmod/mixin/window/WindowMixin.java b/src/main/java/net/vulkanmod/mixin/window/WindowMixin.java
index 4827f9043..2516a1910 100644
--- a/src/main/java/net/vulkanmod/mixin/window/WindowMixin.java
+++ b/src/main/java/net/vulkanmod/mixin/window/WindowMixin.java
@@ -14,6 +14,7 @@
import net.vulkanmod.vulkan.VRenderSystem;
import net.vulkanmod.vulkan.Vulkan;
import org.jetbrains.annotations.Nullable;
+import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
@@ -61,7 +62,7 @@ private void redirect(int hint, int value) { }
private void vulkanHint(WindowEventHandler windowEventHandler, ScreenManager screenManager, DisplayData displayData, String string, String string2, CallbackInfo ci) {
GLFW.glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
- //Fix Gnome Client-Side Decorators
+ //Fix Gnome/Wayland Client-Side Decorators
boolean b = (Platform.isGnome() | Platform.isWeston() | Platform.isGeneric()) && Platform.isWayLand();
GLFW.glfwWindowHint(GLFW_DECORATED, (b ? GLFW_FALSE : GLFW_TRUE));
}
@@ -104,51 +105,61 @@ public void updateDisplay(@Nullable TracyFrameCapture tracyFrameCapture) {
private boolean wasOnFullscreen = false;
+ // --- Helper to get the correct monitor ---
+ private long getMonitor() {
+ if (this.wasOnFullscreen && this.fullscreen) {
+ long m = GLFW.glfwGetWindowMonitor(this.handle);
+ if (m != 0L) return m;
+ }
+
+ if (Initializer.CONFIG == null) {
+ return GLFW.glfwGetPrimaryMonitor();
+ }
+
+ PointerBuffer monitors = GLFW.glfwGetMonitors();
+ if (monitors == null || monitors.limit() == 0) {
+ return GLFW.glfwGetPrimaryMonitor();
+ }
+
+ int targetIndex = Initializer.CONFIG.targetMonitor;
+
+ if (targetIndex < 0 || targetIndex >= monitors.limit()) {
+ targetIndex = 0;
+ Initializer.CONFIG.targetMonitor = 0;
+ }
+
+ return monitors.get(targetIndex);
+ }
+
/**
* @author
*/
@Overwrite
private void setMode() {
Config config = Initializer.CONFIG;
+ long monitor = this.getMonitor();
+
+ // Update modes for current monitor
+ VideoModeManager.updateMonitor(monitor);
- long monitor = GLFW.glfwGetPrimaryMonitor();
+ // --- 1. EXCLUSIVE FULLSCREEN ---
if (this.fullscreen) {
- {
- VideoModeSet.VideoMode videoMode = config.videoMode;
-
- boolean supported;
- VideoModeSet set = VideoModeManager.getFromVideoMode(videoMode);
-
- if (set != null) {
- supported = set.hasRefreshRate(videoMode.refreshRate);
- }
- else {
- supported = false;
- }
-
- if(!supported) {
- LOGGER.error("Resolution not supported, using first available as fallback");
- videoMode = VideoModeManager.getFirstAvailable().getVideoMode();
- }
-
- if (!this.wasOnFullscreen) {
- this.windowedX = this.x;
- this.windowedY = this.y;
- this.windowedWidth = this.width;
- this.windowedHeight = this.height;
- }
-
- this.x = 0;
- this.y = 0;
- this.width = videoMode.width;
- this.height = videoMode.height;
- GLFW.glfwSetWindowMonitor(this.handle, monitor, this.x, this.y, this.width, this.height, videoMode.refreshRate);
-
- this.wasOnFullscreen = true;
+ VideoModeSet.VideoMode videoMode = (config != null) ? config.videoMode : VideoModeManager.getFirstAvailable().getVideoMode();
+
+ boolean supported;
+ VideoModeSet set = VideoModeManager.getFromVideoMode(videoMode);
+
+ if (set != null) {
+ supported = set.hasRefreshRate(videoMode.refreshRate);
+ }
+ else {
+ supported = false;
+ }
+
+ if(!supported) {
+ LOGGER.error("Resolution not supported, using first available as fallback");
+ videoMode = VideoModeManager.getFirstAvailable().getVideoMode();
}
- }
- else if (config.windowMode == WindowMode.WINDOWED_FULLSCREEN.mode) {
- VideoModeSet.VideoMode videoMode = VideoModeManager.getOsVideoMode();
if (!this.wasOnFullscreen) {
this.windowedX = this.x;
@@ -157,21 +168,81 @@ else if (config.windowMode == WindowMode.WINDOWED_FULLSCREEN.mode) {
this.windowedHeight = this.height;
}
- int width = videoMode.width;
- int height = videoMode.height;
+ this.x = 0;
+ this.y = 0;
+ this.width = videoMode.width;
+ this.height = videoMode.height;
+
+ GLFW.glfwSetWindowMonitor(this.handle, monitor, this.x, this.y, this.width, this.height, videoMode.refreshRate);
+
+ this.wasOnFullscreen = true;
+ }
+ // --- 2. WINDOWED FULLSCREEN (BORDERLESS) - ADAPTIVE WAYLAND FIX ---
+ else if (config != null && config.windowMode == WindowMode.WINDOWED_FULLSCREEN.mode) {
+
+ // Get monitor position
+ int[] monitorX = new int[1];
+ int[] monitorY = new int[1];
+ GLFW.glfwGetMonitorPos(monitor, monitorX, monitorY);
+ if (!this.wasOnFullscreen) {
+ this.windowedX = this.x;
+ this.windowedY = this.y;
+ this.windowedWidth = this.width;
+ this.windowedHeight = this.height;
+ }
+
+ // Remove window decorations (borders)
GLFW.glfwSetWindowAttrib(this.handle, GLFW_DECORATED, GLFW_FALSE);
- GLFW.glfwSetWindowMonitor(this.handle, 0L, 0, 0, width, height, -1);
- this.width = width;
- this.height = height;
+ // Move window to the target monitor at (x, y).
+ // We set a dummy size (800x600) initially because we will maximize it immediately.
+ // Using 0L as monitor handle means "Windowed mode", which is correct for Borderless.
+ GLFW.glfwSetWindowMonitor(this.handle, 0L, monitorX[0], monitorY[0], 800, 600, -1);
+
+ // Force Maximize. This lets the Window Manager (KWin/Wayland) handle the scaling logic.
+ // It will fill the logical screen area perfectly, ignoring incorrect GLFW scaling reports.
+ GLFW.glfwMaximizeWindow(this.handle);
+
+ // Now read back the Logic Size assigned by the OS
+ int[] realWidth = new int[1];
+ int[] realHeight = new int[1];
+ GLFW.glfwGetWindowSize(this.handle, realWidth, realHeight);
+
+ this.width = realWidth[0];
+ this.height = realHeight[0];
+ this.x = monitorX[0];
+ this.y = monitorY[0];
+
+ LOGGER.info("Adaptive Borderless: System assigned logical size {}x{}", this.width, this.height);
+
+ int[] fbW = new int[1];
+ int[] fbH = new int[1];
+ GLFW.glfwGetFramebufferSize(this.handle, fbW, fbH);
+
+ this.framebufferWidth = fbW[0];
+ this.framebufferHeight = fbH[0];
+
+ LOGGER.info("Adaptive Borderless: Physical framebuffer size {}x{}", this.framebufferWidth, this.framebufferHeight);
+
+ // Force update if framebuffer size changed significantly
+ if (this.framebufferWidth > 0 && this.framebufferHeight > 0) {
+ Renderer.scheduleSwapChainUpdate();
+ }
+
this.wasOnFullscreen = true;
- } else {
+ }
+ // --- 3. WINDOWED MODE ---
+ else {
this.x = this.windowedX;
this.y = this.windowedY;
this.width = this.windowedWidth;
this.height = this.windowedHeight;
+ // Important: If we were maximized/borderless, we must restore the window
+ // so it can be resized and decorated again.
+ GLFW.glfwRestoreWindow(this.handle);
+
GLFW.glfwSetWindowMonitor(this.handle, 0L, this.x, this.y, this.width, this.height, -1);
GLFW.glfwSetWindowAttrib(this.handle, GLFW_DECORATED, GLFW_TRUE);
@@ -186,19 +257,14 @@ else if (config.windowMode == WindowMode.WINDOWED_FULLSCREEN.mode) {
@Overwrite
private void onFramebufferResize(long window, int width, int height) {
if (window == this.handle) {
- int prevWidth = this.getWidth();
- int prevHeight = this.getHeight();
-
if(width > 0 && height > 0) {
+ // Update internal framebuffer state
this.framebufferWidth = width;
this.framebufferHeight = height;
-// if (this.framebufferWidth != prevWidth || this.framebufferHeight != prevHeight) {
-// this.eventHandler.resizeDisplay();
-// }
+ // Trigger Vulkan SwapChain rebuild
Renderer.scheduleSwapChainUpdate();
}
-
}
}
@@ -214,5 +280,4 @@ private void onResize(long window, int width, int height) {
if(width > 0 && height > 0)
Renderer.scheduleSwapChainUpdate();
}
-
-}
+}
\ No newline at end of file