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 -[![Demostration Video](http://img.youtube.com/vi/sbr7UxcAmOE/0.jpg)](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. -- [![CurseForge](https://cf.way2muchnoise.eu/full_635429_downloads.svg?badge_style=flat)](https://www.curseforge.com/minecraft/mc-mods/vulkanmod) +### Demonstration (Original Mod): +[![Demonstration Video](http://img.youtube.com/vi/sbr7UxcAmOE/0.jpg)](https://youtu.be/sbr7UxcAmOE) -- [![Modrinth Downloads](https://img.shields.io/modrinth/dt/JYQhtZtO?logo=modrinth&label=Modrinth%20Downloads)](https://modrinth.com/mod/vulkanmod/versions) +## Installation -- [![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/xCollateral/VulkanMod/total?style=flat-square&logo=github&label=Github%20Downloads)](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
- - Discord - - - - Static Badge - -
- +>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