Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд Пишем Motion Blur | Fabric 1.21.4

Нужные миксины:
MixinLevelRenderer.java:
Expand Collapse Copy
package ru.motionreblur.mixin;

import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.client.render.*;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import ru.motionreblur.MotionBlurModule;

@Mixin(WorldRenderer.class)
public class MixinLevelRenderer {
    @Unique
    private Matrix4f prevModelView = new Matrix4f();
    @Unique
    private Matrix4f prevProjection = new Matrix4f();
    @Unique
    private Vector3f prevCameraPos = new Vector3f();

    @Inject(method = "render", at = @At("HEAD"))
    private void setMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
        float tickDelta = tickCounter.getTickDelta(true);
        float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
        MotionBlurModule.getInstance().shader.setFrameMotionBlur(positionMatrix, prevModelView,
                gameRenderer.getBasicProjectionMatrix(fov),
                prevProjection,
                new Vector3f(
                        (float) (camera.getPos().x % 30000f),
                        (float) (camera.getPos().y % 30000f),
                        (float) (camera.getPos().z % 30000f)
                ),
                prevCameraPos
        );
    }

    @Inject(method = "render", at = @At("RETURN"))
    private void setOldMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
        prevModelView = new Matrix4f(positionMatrix);
        float tickDelta = tickCounter.getTickDelta(true);
        float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
        prevProjection = new Matrix4f(gameRenderer.getBasicProjectionMatrix(fov));
        prevCameraPos = new Vector3f(
                (float) (camera.getPos().x % 30000f),
                (float) (camera.getPos().y % 30000f),
                (float) (camera.getPos().z % 30000f)
        );
    }
}

GameRendererAccessor.java:
Expand Collapse Copy
package ru.motionreblur.mixin;

import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(GameRenderer.class)
public interface GameRendererAccessor {
    @Invoker("getFov")
    float invokeGetFov(Camera camera, float tickDelta, boolean changingFov);
}

Сам блюр:
ShaderMotionBlur.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.ladysnake.satin.api.managed.ManagedShaderEffect;
import org.ladysnake.satin.api.managed.ShaderEffectManager;

public class ShaderMotionBlur {
    private final MotionBlurModule config;
    private final ManagedShaderEffect motionBlurShader;

    public ShaderMotionBlur(MotionBlurModule config) {
        this.config = config;
        motionBlurShader = ShaderEffectManager.getInstance().manage(
                Identifier.of(MotionReBlur.MOD_ID, "motion_blur"),
                shader -> shader.setUniformValue("BlendFactor", config.getStrength())
        );
    }

    private long lastNano = System.nanoTime();
    private float currentBlur = 0.0f;
    private float currentFPS = 0.0f;

    public void registerShaderCallbacks() {
        WorldRenderEvents.END.register(context -> {
            long now = System.nanoTime();
            float deltaTime = (now - lastNano) / 1_000_000_000.0f;
            float deltaTick = deltaTime * 20.0f;
            lastNano = now;

            if (deltaTime > 0 && deltaTime < 1.0f) {
                currentFPS = 1.0f / deltaTime;
            } else {
                currentFPS = 0.0f;
            }

            if (shouldRenderMotionBlur()) {
                applyMotionBlur(deltaTick);
            }
        });
    }

    private boolean shouldRenderMotionBlur() {
        if (config.getStrength() == 0 || !config.isEnabled()) {
            return false;
        }
        if (FabricLoader.getInstance().isModLoaded("iris")) {
            MotionReBlur.LOGGER.warn("Motion Blur cannot work with Iris Shaders!");
            config.setEnabled(false);
            return false;
        }
        return true;
    }

    private void applyMotionBlur(float deltaTick) {
        MinecraftClient client = MinecraftClient.getInstance();

        MonitorInfoProvider.updateDisplayInfo();
        int displayRefreshRate = MonitorInfoProvider.getRefreshRate();

        float baseStrength = config.getStrength();
        float scaledStrength = baseStrength;
        if (config.isUseRRC()) {
            float fpsOverRefresh = (displayRefreshRate > 0) ? currentFPS / displayRefreshRate : 1.0f;
            if (fpsOverRefresh < 1.0f) fpsOverRefresh = 1.0f;
            scaledStrength = baseStrength * fpsOverRefresh;
        }

        if (currentBlur != scaledStrength) {
            motionBlurShader.setUniformValue("BlendFactor", scaledStrength);
            currentBlur = scaledStrength;
        }

        int sampleAmount = getSampleAmountForFPS(currentFPS);
        int halfSampleAmount = sampleAmount / 2;
        float invSamples = 1.0f / sampleAmount;

        motionBlurShader.setUniformValue("view_res", (float) client.getFramebuffer().viewportWidth, (float) client.getFramebuffer().viewportHeight);
        motionBlurShader.setUniformValue("view_pixel_size", 1.0f / client.getFramebuffer().viewportWidth, 1.0f / client.getFramebuffer().viewportHeight);
        motionBlurShader.setUniformValue("motionBlurSamples", sampleAmount);
        motionBlurShader.setUniformValue("halfSamples", halfSampleAmount);
        motionBlurShader.setUniformValue("inverseSamples", invSamples);
        motionBlurShader.setUniformValue("blurAlgorithm", MotionBlurModule.BlurAlgorithm.CENTERED.ordinal());

        motionBlurShader.render(deltaTick);
    }

    private int getSampleAmountForFPS(float fps) {
        int quality = config.getQuality();

        int baseSamples = switch (quality) {
            case 0 -> 8;
            case 1 -> 12;
            case 2 -> 16;
            case 3 -> 24;
            default -> 12;
        };

        if (fps < 30) {
            return Math.max(6, baseSamples / 2);
        } else if (fps < 60) {
            return Math.max(8, (int) (baseSamples * 0.75f));
        } else if (fps > 144) {
            return Math.min(32, (int) (baseSamples * 1.25f));
        }

        return baseSamples;
    }

    private final Matrix4f tempPrevModelView = new Matrix4f();
    private final Matrix4f tempPrevProjection = new Matrix4f();
    private final Matrix4f tempProjInverse = new Matrix4f();
    private final Matrix4f tempMvInverse = new Matrix4f();

    public void setFrameMotionBlur(Matrix4f modelView, Matrix4f prevModelView,
                                   Matrix4f projection, Matrix4f prevProjection,
                                   Vector3f cameraPos, Vector3f prevCameraPos) {
        motionBlurShader.setUniformValue("mvInverse", tempMvInverse.set(modelView).invert());
        motionBlurShader.setUniformValue("projInverse", tempProjInverse.set(projection).invert());
        motionBlurShader.setUniformValue("prevModelView", tempPrevModelView.set(prevModelView));
        motionBlurShader.setUniformValue("prevProjection", tempPrevProjection.set(prevProjection));
        motionBlurShader.setUniformValue("cameraPos", cameraPos.x, cameraPos.y, cameraPos.z);
        motionBlurShader.setUniformValue("prevCameraPos", prevCameraPos.x, prevCameraPos.y, prevCameraPos.z);
    }

    public void updateBlurStrength(float strength) {
        motionBlurShader.setUniformValue("BlendFactor", strength);
        currentBlur = strength;
    }
}
MotionReBlur.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.api.ClientModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MotionReBlur implements ClientModInitializer {
    public static final String MOD_ID = "motionreblur";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);

    @Override
    public void onInitializeClient() {
        LOGGER.info("Motion ReBlur initialized!");

        MotionBlurModule.getInstance();

        MotionBlurCommand.register();

        MotionBlurKeyBinding.register();
    }
}
MotionBlurModule.java:
Expand Collapse Copy
package ru.motionreblur;

public class MotionBlurModule {
    private static final MotionBlurModule instance = new MotionBlurModule();
    public final ShaderMotionBlur shader;

    private boolean enabled = false;
    private float strength = -0.8f;
    private boolean useRRC = true;
    private int quality = 2;

    private MotionBlurModule() {
        shader = new ShaderMotionBlur(this);
        shader.registerShaderCallbacks();
    }

    public static MotionBlurModule getInstance() {
        return instance;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        MotionReBlur.LOGGER.info("Motion Blur " + (enabled ? "enabled" : "disabled"));
    }

    public float getStrength() {
        return strength;
    }

    public void setStrength(float strength) {
        this.strength = Math.max(-2.0f, Math.min(2.0f, strength));
        shader.updateBlurStrength(this.strength);
        MotionReBlur.LOGGER.info("Motion Blur strength set to " + this.strength);
    }

    public boolean isUseRRC() {
        return useRRC;
    }

    public void setUseRRC(boolean useRRC) {
        this.useRRC = useRRC;
        MotionReBlur.LOGGER.info("Refresh Rate Scaling " + (useRRC ? "enabled" : "disabled"));
    }

    public int getQuality() {
        return quality;
    }

    public void setQuality(int quality) {
        this.quality = Math.max(0, Math.min(3, quality));
        MotionReBlur.LOGGER.info("Motion Blur quality set to " + getQualityName());
    }

    public String getQualityName() {
        return switch (quality) {
            case 0 -> "Низкое";
            case 1 -> "Среднее";
            case 2 -> "Высокое";
            case 3 -> "Ультра";
            default -> "Среднее";
        };
    }

    public enum BlurAlgorithm {BACKWARDS, CENTERED}

    public static BlurAlgorithm blurAlgorithm = BlurAlgorithm.CENTERED;
}
MotionBlurKeyBinding.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;

public class MotionBlurKeyBinding {
    private static KeyBinding openGuiKey;

    public static void register() {
        openGuiKey = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                "key.motionreblur.open_gui",
                InputUtil.Type.KEYSYM,
                GLFW.GLFW_KEY_M,
                "category.motionreblur"
        ));

        ClientTickEvents.END_CLIENT_TICK.register(client -> {
            while (openGuiKey.wasPressed()) {
                client.setScreen(new MotionBlurConfigScreen(client.currentScreen));
            }
        });
    }
}
MotionBlurConfigScreen.java:
Expand Collapse Copy
package ru.motionreblur;

import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.text.Text;

public class MotionBlurConfigScreen extends Screen {
    private final Screen parent;
    private SliderWidget strengthSlider;
    private ButtonWidget toggleButton;
    private ButtonWidget rrcButton;
    private ButtonWidget qualityButton;

    public MotionBlurConfigScreen(Screen parent) {
        super(Text.literal("Motion ReBlur Settings"));
        this.parent = parent;
    }

    @Override
    protected void init() {
        MotionBlurModule mb = MotionBlurModule.getInstance();

        int centerX = this.width / 2;
        int startY = this.height / 2 - 60;

        toggleButton = ButtonWidget.builder(
                Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")),
                button -> {
                    mb.setEnabled(!mb.isEnabled());
                    button.setMessage(Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
                }
        ).dimensions(centerX - 100, startY, 200, 20).build();

        strengthSlider = new SliderWidget(
                centerX - 100, startY + 30, 200, 20,
                Text.literal("Сила: " + String.format("%.1f", mb.getStrength())),
                (mb.getStrength() + 2.0) / 4.0
        ) {
            @Override
            protected void updateMessage() {
                double value = this.value * 4.0 - 2.0;
                this.setMessage(Text.literal("Сила: " + String.format("%.1f", value)));
            }

            @Override
            protected void applyValue() {
                float value = (float) (this.value * 4.0 - 2.0);
                mb.setStrength(value);
            }
        };

        rrcButton = ButtonWidget.builder(
                Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")),
                button -> {
                    mb.setUseRRC(!mb.isUseRRC());
                    button.setMessage(Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
                }
        ).dimensions(centerX - 100, startY + 60, 200, 20).build();

        qualityButton = ButtonWidget.builder(
                Text.literal("Качество: " + mb.getQualityName()),
                button -> {
                    int newQuality = (mb.getQuality() + 1) % 4;
                    mb.setQuality(newQuality);
                    button.setMessage(Text.literal("Качество: " + mb.getQualityName()));
                }
        ).dimensions(centerX - 100, startY + 90, 200, 20).build();

        ButtonWidget doneButton = ButtonWidget.builder(
                Text.translatable("gui.done"),
                button -> this.close()
        ).dimensions(centerX - 100, startY + 120, 200, 20).build();

        addDrawableChild(toggleButton);
        addDrawableChild(strengthSlider);
        addDrawableChild(rrcButton);
        addDrawableChild(qualityButton);
        addDrawableChild(doneButton);
    }

    @Override
    public void render(DrawContext context, int mouseX, int mouseY, float delta) {
        this.renderBackground(context, mouseX, mouseY, delta);
        super.render(context, mouseX, mouseY, delta);

        context.drawCenteredTextWithShadow(
                this.textRenderer,
                this.title,
                this.width / 2,
                20,
                0xFFFFFF
        );

        MotionBlurModule mb = MotionBlurModule.getInstance();
        int refreshRate = MonitorInfoProvider.getRefreshRate();
        String info = "Refresh Rate: " + refreshRate + " Hz";
        context.drawCenteredTextWithShadow(
                this.textRenderer,
                Text.literal(info),
                this.width / 2,
                this.height / 2 + 80,
                0x808080
        );

        if (mb.isEnabled()) {
            String hint = "Двигайте камеру, чтобы увидеть эффект";
            context.drawCenteredTextWithShadow(
                    this.textRenderer,
                    Text.literal(hint),
                    this.width / 2,
                    this.height / 2 + 95,
                    0x808080
            );
        }
    }

    @Override
    public void close() {
        if (this.client != null) {
            this.client.setScreen(parent);
        }
    }
}
MotionBlurCommand.java:
Expand Collapse Copy
package ru.motionreblur;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;

public class MotionBlurCommand {

    public static void register() {
        ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
            registerCommand(dispatcher);
        });
    }

    private static void registerCommand(CommandDispatcher<FabricClientCommandSource> dispatcher) {
        dispatcher.register(ClientCommandManager.literal("motionreblur")
                .executes(ctx -> openGui(ctx))
                .then(ClientCommandManager.literal("on")
                        .executes(ctx -> enable(ctx)))
                .then(ClientCommandManager.literal("off")
                        .executes(ctx -> disable(ctx)))
                .then(ClientCommandManager.literal("toggle")
                        .executes(ctx -> toggle(ctx)))
                .then(ClientCommandManager.literal("strength")
                        .then(ClientCommandManager.argument("value", FloatArgumentType.floatArg(-2.0f, 2.0f))
                                .executes(ctx -> setStrength(ctx, FloatArgumentType.getFloat(ctx, "value")))))
                .then(ClientCommandManager.literal("rrc")
                        .executes(ctx -> toggleRRC(ctx)))
                .then(ClientCommandManager.literal("status")
                        .executes(ctx -> showStatus(ctx)))
                .then(ClientCommandManager.literal("help")
                        .executes(ctx -> showHelp(ctx)))
        );
    }

    private static int openGui(CommandContext<FabricClientCommandSource> ctx) {
        MinecraftClient client = MinecraftClient.getInstance();
        client.execute(() -> {
            client.setScreen(new MotionBlurConfigScreen(null));
        });
        ctx.getSource().sendFeedback(Text.literal("§aОткрываю меню настроек..."));
        return 1;
    }

    private static int showStatus(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        ctx.getSource().sendFeedback(Text.literal("§7§m                    "));
        ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur"));
        ctx.getSource().sendFeedback(Text.literal("§7Состояние: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
        ctx.getSource().sendFeedback(Text.literal("§7Сила: §f" + String.format("%.1f", mb.getStrength())));
        ctx.getSource().sendFeedback(Text.literal("§7RRC: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
        ctx.getSource().sendFeedback(Text.literal("§7Refresh Rate: §f" + MonitorInfoProvider.getRefreshRate() + " Hz"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                    "));
        return 1;
    }

    private static int enable(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule.getInstance().setEnabled(true);
        ctx.getSource().sendFeedback(Text.literal("§aMotion Blur включен"));
        return 1;
    }

    private static int disable(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule.getInstance().setEnabled(false);
        ctx.getSource().sendFeedback(Text.literal("§cMotion Blur выключен"));
        return 1;
    }

    private static int toggle(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        boolean newState = !mb.isEnabled();
        mb.setEnabled(newState);
        ctx.getSource().sendFeedback(Text.literal("§7Motion Blur: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
        return 1;
    }

    private static int setStrength(CommandContext<FabricClientCommandSource> ctx, float value) {
        MotionBlurModule.getInstance().setStrength(value);
        ctx.getSource().sendFeedback(Text.literal("§aСила установлена на §f" + String.format("%.1f", value)));
        return 1;
    }

    private static int toggleRRC(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        boolean newState = !mb.isUseRRC();
        mb.setUseRRC(newState);
        ctx.getSource().sendFeedback(Text.literal("§7Адаптация к частоте монитора: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
        return 1;
    }

    private static int showHelp(CommandContext<FabricClientCommandSource> ctx) {
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur - Команды"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur §7- открыть GUI"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur on §7- включить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur off §7- выключить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur toggle §7- переключить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur strength <значение> §7- установить силу"));
        ctx.getSource().sendFeedback(Text.literal("§7  Диапазон: от -2.0 до 2.0"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur rrc §7- переключить адаптацию к частоте"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur status §7- показать статус"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        return 1;
    }
}
MonitorInfoProvider.java:
Expand Collapse Copy
package ru.motionreblur;

import net.minecraft.client.MinecraftClient;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWVidMode;

public class MonitorInfoProvider {
    private static long lastMonitorHandle = 0;
    private static int lastRefreshRate = 60;
    private static long lastCheckTime = 0;
    private static final long CHECK_INTERVAL_NS = 1_000_000_000L;

    public static void updateDisplayInfo() {
        long now = System.nanoTime();
        if (now - lastCheckTime < CHECK_INTERVAL_NS) {
            return;
        }
        lastCheckTime = now;

        MinecraftClient client = MinecraftClient.getInstance();
        if (client == null || client.getWindow() == null) return;

        long window = client.getWindow().getHandle();
        long monitor = GLFW.glfwGetWindowMonitor(window);

        if (monitor == 0) {
            monitor = getMonitorFromWindowPosition(window, client.getWindow().getWidth(), client.getWindow().getHeight());
        }

        if (monitor != lastMonitorHandle) {
            lastRefreshRate = detectRefreshRateFromMonitor(monitor);
            lastMonitorHandle = monitor;
        }
    }

    public static int getRefreshRate() {
        return lastRefreshRate;
    }

    private static long getMonitorFromWindowPosition(long window, int windowWidth, int windowHeight) {
        int[] winX = new int[1];
        int[] winY = new int[1];
        GLFW.glfwGetWindowPos(window, winX, winY);

        int windowCenterX = winX[0] + windowWidth / 2;
        int windowCenterY = winY[0] + windowHeight / 2;

        long monitorResult = GLFW.glfwGetPrimaryMonitor();
        PointerBuffer monitors = GLFW.glfwGetMonitors();
        if (monitors != null) {
            for (int i = 0; i < monitors.limit(); i++) {
                long m = monitors.get(i);
                int[] mx = new int[1];
                int[] my = new int[1];
                GLFW.glfwGetMonitorPos(m, mx, my);
                GLFWVidMode mode = GLFW.glfwGetVideoMode(m);
                if (mode == null) continue;

                int mw = mode.width();
                int mh = mode.height();

                if (windowCenterX >= mx[0] && windowCenterX < mx[0] + mw &&
                        windowCenterY >= my[0] && windowCenterY < my[0] + mh) {
                    monitorResult = m;
                    break;
                }
            }
        }
        return monitorResult;
    }

    private static int detectRefreshRateFromMonitor(long monitor) {
        GLFWVidMode vidMode = GLFW.glfwGetVideoMode(monitor);
        return (vidMode != null) ? vidMode.refreshRate() : 60;
    }
}
Ты по моему разделом ошибся
 
крутая, но не актуальная
у меня есть версия без сатина под 1.21.8
1775144668633.png
 
Нужные миксины:
MixinLevelRenderer.java:
Expand Collapse Copy
package ru.motionreblur.mixin;

import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.client.render.*;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import ru.motionreblur.MotionBlurModule;

@Mixin(WorldRenderer.class)
public class MixinLevelRenderer {
    @Unique
    private Matrix4f prevModelView = new Matrix4f();
    @Unique
    private Matrix4f prevProjection = new Matrix4f();
    @Unique
    private Vector3f prevCameraPos = new Vector3f();

    @Inject(method = "render", at = @At("HEAD"))
    private void setMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
        float tickDelta = tickCounter.getTickDelta(true);
        float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
        MotionBlurModule.getInstance().shader.setFrameMotionBlur(positionMatrix, prevModelView,
                gameRenderer.getBasicProjectionMatrix(fov),
                prevProjection,
                new Vector3f(
                        (float) (camera.getPos().x % 30000f),
                        (float) (camera.getPos().y % 30000f),
                        (float) (camera.getPos().z % 30000f)
                ),
                prevCameraPos
        );
    }

    @Inject(method = "render", at = @At("RETURN"))
    private void setOldMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
        prevModelView = new Matrix4f(positionMatrix);
        float tickDelta = tickCounter.getTickDelta(true);
        float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
        prevProjection = new Matrix4f(gameRenderer.getBasicProjectionMatrix(fov));
        prevCameraPos = new Vector3f(
                (float) (camera.getPos().x % 30000f),
                (float) (camera.getPos().y % 30000f),
                (float) (camera.getPos().z % 30000f)
        );
    }
}

GameRendererAccessor.java:
Expand Collapse Copy
package ru.motionreblur.mixin;

import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(GameRenderer.class)
public interface GameRendererAccessor {
    @Invoker("getFov")
    float invokeGetFov(Camera camera, float tickDelta, boolean changingFov);
}

Сам блюр:
ShaderMotionBlur.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.ladysnake.satin.api.managed.ManagedShaderEffect;
import org.ladysnake.satin.api.managed.ShaderEffectManager;

public class ShaderMotionBlur {
    private final MotionBlurModule config;
    private final ManagedShaderEffect motionBlurShader;

    public ShaderMotionBlur(MotionBlurModule config) {
        this.config = config;
        motionBlurShader = ShaderEffectManager.getInstance().manage(
                Identifier.of(MotionReBlur.MOD_ID, "motion_blur"),
                shader -> shader.setUniformValue("BlendFactor", config.getStrength())
        );
    }

    private long lastNano = System.nanoTime();
    private float currentBlur = 0.0f;
    private float currentFPS = 0.0f;

    public void registerShaderCallbacks() {
        WorldRenderEvents.END.register(context -> {
            long now = System.nanoTime();
            float deltaTime = (now - lastNano) / 1_000_000_000.0f;
            float deltaTick = deltaTime * 20.0f;
            lastNano = now;

            if (deltaTime > 0 && deltaTime < 1.0f) {
                currentFPS = 1.0f / deltaTime;
            } else {
                currentFPS = 0.0f;
            }

            if (shouldRenderMotionBlur()) {
                applyMotionBlur(deltaTick);
            }
        });
    }

    private boolean shouldRenderMotionBlur() {
        if (config.getStrength() == 0 || !config.isEnabled()) {
            return false;
        }
        if (FabricLoader.getInstance().isModLoaded("iris")) {
            MotionReBlur.LOGGER.warn("Motion Blur cannot work with Iris Shaders!");
            config.setEnabled(false);
            return false;
        }
        return true;
    }

    private void applyMotionBlur(float deltaTick) {
        MinecraftClient client = MinecraftClient.getInstance();

        MonitorInfoProvider.updateDisplayInfo();
        int displayRefreshRate = MonitorInfoProvider.getRefreshRate();

        float baseStrength = config.getStrength();
        float scaledStrength = baseStrength;
        if (config.isUseRRC()) {
            float fpsOverRefresh = (displayRefreshRate > 0) ? currentFPS / displayRefreshRate : 1.0f;
            if (fpsOverRefresh < 1.0f) fpsOverRefresh = 1.0f;
            scaledStrength = baseStrength * fpsOverRefresh;
        }

        if (currentBlur != scaledStrength) {
            motionBlurShader.setUniformValue("BlendFactor", scaledStrength);
            currentBlur = scaledStrength;
        }

        int sampleAmount = getSampleAmountForFPS(currentFPS);
        int halfSampleAmount = sampleAmount / 2;
        float invSamples = 1.0f / sampleAmount;

        motionBlurShader.setUniformValue("view_res", (float) client.getFramebuffer().viewportWidth, (float) client.getFramebuffer().viewportHeight);
        motionBlurShader.setUniformValue("view_pixel_size", 1.0f / client.getFramebuffer().viewportWidth, 1.0f / client.getFramebuffer().viewportHeight);
        motionBlurShader.setUniformValue("motionBlurSamples", sampleAmount);
        motionBlurShader.setUniformValue("halfSamples", halfSampleAmount);
        motionBlurShader.setUniformValue("inverseSamples", invSamples);
        motionBlurShader.setUniformValue("blurAlgorithm", MotionBlurModule.BlurAlgorithm.CENTERED.ordinal());

        motionBlurShader.render(deltaTick);
    }

    private int getSampleAmountForFPS(float fps) {
        int quality = config.getQuality();

        int baseSamples = switch (quality) {
            case 0 -> 8;
            case 1 -> 12;
            case 2 -> 16;
            case 3 -> 24;
            default -> 12;
        };

        if (fps < 30) {
            return Math.max(6, baseSamples / 2);
        } else if (fps < 60) {
            return Math.max(8, (int) (baseSamples * 0.75f));
        } else if (fps > 144) {
            return Math.min(32, (int) (baseSamples * 1.25f));
        }

        return baseSamples;
    }

    private final Matrix4f tempPrevModelView = new Matrix4f();
    private final Matrix4f tempPrevProjection = new Matrix4f();
    private final Matrix4f tempProjInverse = new Matrix4f();
    private final Matrix4f tempMvInverse = new Matrix4f();

    public void setFrameMotionBlur(Matrix4f modelView, Matrix4f prevModelView,
                                   Matrix4f projection, Matrix4f prevProjection,
                                   Vector3f cameraPos, Vector3f prevCameraPos) {
        motionBlurShader.setUniformValue("mvInverse", tempMvInverse.set(modelView).invert());
        motionBlurShader.setUniformValue("projInverse", tempProjInverse.set(projection).invert());
        motionBlurShader.setUniformValue("prevModelView", tempPrevModelView.set(prevModelView));
        motionBlurShader.setUniformValue("prevProjection", tempPrevProjection.set(prevProjection));
        motionBlurShader.setUniformValue("cameraPos", cameraPos.x, cameraPos.y, cameraPos.z);
        motionBlurShader.setUniformValue("prevCameraPos", prevCameraPos.x, prevCameraPos.y, prevCameraPos.z);
    }

    public void updateBlurStrength(float strength) {
        motionBlurShader.setUniformValue("BlendFactor", strength);
        currentBlur = strength;
    }
}
MotionReBlur.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.api.ClientModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MotionReBlur implements ClientModInitializer {
    public static final String MOD_ID = "motionreblur";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);

    @Override
    public void onInitializeClient() {
        LOGGER.info("Motion ReBlur initialized!");

        MotionBlurModule.getInstance();

        MotionBlurCommand.register();

        MotionBlurKeyBinding.register();
    }
}
MotionBlurModule.java:
Expand Collapse Copy
package ru.motionreblur;

public class MotionBlurModule {
    private static final MotionBlurModule instance = new MotionBlurModule();
    public final ShaderMotionBlur shader;

    private boolean enabled = false;
    private float strength = -0.8f;
    private boolean useRRC = true;
    private int quality = 2;

    private MotionBlurModule() {
        shader = new ShaderMotionBlur(this);
        shader.registerShaderCallbacks();
    }

    public static MotionBlurModule getInstance() {
        return instance;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        MotionReBlur.LOGGER.info("Motion Blur " + (enabled ? "enabled" : "disabled"));
    }

    public float getStrength() {
        return strength;
    }

    public void setStrength(float strength) {
        this.strength = Math.max(-2.0f, Math.min(2.0f, strength));
        shader.updateBlurStrength(this.strength);
        MotionReBlur.LOGGER.info("Motion Blur strength set to " + this.strength);
    }

    public boolean isUseRRC() {
        return useRRC;
    }

    public void setUseRRC(boolean useRRC) {
        this.useRRC = useRRC;
        MotionReBlur.LOGGER.info("Refresh Rate Scaling " + (useRRC ? "enabled" : "disabled"));
    }

    public int getQuality() {
        return quality;
    }

    public void setQuality(int quality) {
        this.quality = Math.max(0, Math.min(3, quality));
        MotionReBlur.LOGGER.info("Motion Blur quality set to " + getQualityName());
    }

    public String getQualityName() {
        return switch (quality) {
            case 0 -> "Низкое";
            case 1 -> "Среднее";
            case 2 -> "Высокое";
            case 3 -> "Ультра";
            default -> "Среднее";
        };
    }

    public enum BlurAlgorithm {BACKWARDS, CENTERED}

    public static BlurAlgorithm blurAlgorithm = BlurAlgorithm.CENTERED;
}
MotionBlurKeyBinding.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;

public class MotionBlurKeyBinding {
    private static KeyBinding openGuiKey;

    public static void register() {
        openGuiKey = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                "key.motionreblur.open_gui",
                InputUtil.Type.KEYSYM,
                GLFW.GLFW_KEY_M,
                "category.motionreblur"
        ));

        ClientTickEvents.END_CLIENT_TICK.register(client -> {
            while (openGuiKey.wasPressed()) {
                client.setScreen(new MotionBlurConfigScreen(client.currentScreen));
            }
        });
    }
}
MotionBlurConfigScreen.java:
Expand Collapse Copy
package ru.motionreblur;

import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.text.Text;

public class MotionBlurConfigScreen extends Screen {
    private final Screen parent;
    private SliderWidget strengthSlider;
    private ButtonWidget toggleButton;
    private ButtonWidget rrcButton;
    private ButtonWidget qualityButton;

    public MotionBlurConfigScreen(Screen parent) {
        super(Text.literal("Motion ReBlur Settings"));
        this.parent = parent;
    }

    @Override
    protected void init() {
        MotionBlurModule mb = MotionBlurModule.getInstance();

        int centerX = this.width / 2;
        int startY = this.height / 2 - 60;

        toggleButton = ButtonWidget.builder(
                Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")),
                button -> {
                    mb.setEnabled(!mb.isEnabled());
                    button.setMessage(Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
                }
        ).dimensions(centerX - 100, startY, 200, 20).build();

        strengthSlider = new SliderWidget(
                centerX - 100, startY + 30, 200, 20,
                Text.literal("Сила: " + String.format("%.1f", mb.getStrength())),
                (mb.getStrength() + 2.0) / 4.0
        ) {
            @Override
            protected void updateMessage() {
                double value = this.value * 4.0 - 2.0;
                this.setMessage(Text.literal("Сила: " + String.format("%.1f", value)));
            }

            @Override
            protected void applyValue() {
                float value = (float) (this.value * 4.0 - 2.0);
                mb.setStrength(value);
            }
        };

        rrcButton = ButtonWidget.builder(
                Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")),
                button -> {
                    mb.setUseRRC(!mb.isUseRRC());
                    button.setMessage(Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
                }
        ).dimensions(centerX - 100, startY + 60, 200, 20).build();

        qualityButton = ButtonWidget.builder(
                Text.literal("Качество: " + mb.getQualityName()),
                button -> {
                    int newQuality = (mb.getQuality() + 1) % 4;
                    mb.setQuality(newQuality);
                    button.setMessage(Text.literal("Качество: " + mb.getQualityName()));
                }
        ).dimensions(centerX - 100, startY + 90, 200, 20).build();

        ButtonWidget doneButton = ButtonWidget.builder(
                Text.translatable("gui.done"),
                button -> this.close()
        ).dimensions(centerX - 100, startY + 120, 200, 20).build();

        addDrawableChild(toggleButton);
        addDrawableChild(strengthSlider);
        addDrawableChild(rrcButton);
        addDrawableChild(qualityButton);
        addDrawableChild(doneButton);
    }

    @Override
    public void render(DrawContext context, int mouseX, int mouseY, float delta) {
        this.renderBackground(context, mouseX, mouseY, delta);
        super.render(context, mouseX, mouseY, delta);

        context.drawCenteredTextWithShadow(
                this.textRenderer,
                this.title,
                this.width / 2,
                20,
                0xFFFFFF
        );

        MotionBlurModule mb = MotionBlurModule.getInstance();
        int refreshRate = MonitorInfoProvider.getRefreshRate();
        String info = "Refresh Rate: " + refreshRate + " Hz";
        context.drawCenteredTextWithShadow(
                this.textRenderer,
                Text.literal(info),
                this.width / 2,
                this.height / 2 + 80,
                0x808080
        );

        if (mb.isEnabled()) {
            String hint = "Двигайте камеру, чтобы увидеть эффект";
            context.drawCenteredTextWithShadow(
                    this.textRenderer,
                    Text.literal(hint),
                    this.width / 2,
                    this.height / 2 + 95,
                    0x808080
            );
        }
    }

    @Override
    public void close() {
        if (this.client != null) {
            this.client.setScreen(parent);
        }
    }
}
MotionBlurCommand.java:
Expand Collapse Copy
package ru.motionreblur;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;

public class MotionBlurCommand {

    public static void register() {
        ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
            registerCommand(dispatcher);
        });
    }

    private static void registerCommand(CommandDispatcher<FabricClientCommandSource> dispatcher) {
        dispatcher.register(ClientCommandManager.literal("motionreblur")
                .executes(ctx -> openGui(ctx))
                .then(ClientCommandManager.literal("on")
                        .executes(ctx -> enable(ctx)))
                .then(ClientCommandManager.literal("off")
                        .executes(ctx -> disable(ctx)))
                .then(ClientCommandManager.literal("toggle")
                        .executes(ctx -> toggle(ctx)))
                .then(ClientCommandManager.literal("strength")
                        .then(ClientCommandManager.argument("value", FloatArgumentType.floatArg(-2.0f, 2.0f))
                                .executes(ctx -> setStrength(ctx, FloatArgumentType.getFloat(ctx, "value")))))
                .then(ClientCommandManager.literal("rrc")
                        .executes(ctx -> toggleRRC(ctx)))
                .then(ClientCommandManager.literal("status")
                        .executes(ctx -> showStatus(ctx)))
                .then(ClientCommandManager.literal("help")
                        .executes(ctx -> showHelp(ctx)))
        );
    }

    private static int openGui(CommandContext<FabricClientCommandSource> ctx) {
        MinecraftClient client = MinecraftClient.getInstance();
        client.execute(() -> {
            client.setScreen(new MotionBlurConfigScreen(null));
        });
        ctx.getSource().sendFeedback(Text.literal("§aОткрываю меню настроек..."));
        return 1;
    }

    private static int showStatus(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        ctx.getSource().sendFeedback(Text.literal("§7§m                    "));
        ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur"));
        ctx.getSource().sendFeedback(Text.literal("§7Состояние: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
        ctx.getSource().sendFeedback(Text.literal("§7Сила: §f" + String.format("%.1f", mb.getStrength())));
        ctx.getSource().sendFeedback(Text.literal("§7RRC: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
        ctx.getSource().sendFeedback(Text.literal("§7Refresh Rate: §f" + MonitorInfoProvider.getRefreshRate() + " Hz"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                    "));
        return 1;
    }

    private static int enable(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule.getInstance().setEnabled(true);
        ctx.getSource().sendFeedback(Text.literal("§aMotion Blur включен"));
        return 1;
    }

    private static int disable(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule.getInstance().setEnabled(false);
        ctx.getSource().sendFeedback(Text.literal("§cMotion Blur выключен"));
        return 1;
    }

    private static int toggle(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        boolean newState = !mb.isEnabled();
        mb.setEnabled(newState);
        ctx.getSource().sendFeedback(Text.literal("§7Motion Blur: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
        return 1;
    }

    private static int setStrength(CommandContext<FabricClientCommandSource> ctx, float value) {
        MotionBlurModule.getInstance().setStrength(value);
        ctx.getSource().sendFeedback(Text.literal("§aСила установлена на §f" + String.format("%.1f", value)));
        return 1;
    }

    private static int toggleRRC(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        boolean newState = !mb.isUseRRC();
        mb.setUseRRC(newState);
        ctx.getSource().sendFeedback(Text.literal("§7Адаптация к частоте монитора: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
        return 1;
    }

    private static int showHelp(CommandContext<FabricClientCommandSource> ctx) {
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur - Команды"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur §7- открыть GUI"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur on §7- включить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur off §7- выключить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur toggle §7- переключить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur strength <значение> §7- установить силу"));
        ctx.getSource().sendFeedback(Text.literal("§7  Диапазон: от -2.0 до 2.0"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur rrc §7- переключить адаптацию к частоте"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur status §7- показать статус"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        return 1;
    }
}
MonitorInfoProvider.java:
Expand Collapse Copy
package ru.motionreblur;

import net.minecraft.client.MinecraftClient;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWVidMode;

public class MonitorInfoProvider {
    private static long lastMonitorHandle = 0;
    private static int lastRefreshRate = 60;
    private static long lastCheckTime = 0;
    private static final long CHECK_INTERVAL_NS = 1_000_000_000L;

    public static void updateDisplayInfo() {
        long now = System.nanoTime();
        if (now - lastCheckTime < CHECK_INTERVAL_NS) {
            return;
        }
        lastCheckTime = now;

        MinecraftClient client = MinecraftClient.getInstance();
        if (client == null || client.getWindow() == null) return;

        long window = client.getWindow().getHandle();
        long monitor = GLFW.glfwGetWindowMonitor(window);

        if (monitor == 0) {
            monitor = getMonitorFromWindowPosition(window, client.getWindow().getWidth(), client.getWindow().getHeight());
        }

        if (monitor != lastMonitorHandle) {
            lastRefreshRate = detectRefreshRateFromMonitor(monitor);
            lastMonitorHandle = monitor;
        }
    }

    public static int getRefreshRate() {
        return lastRefreshRate;
    }

    private static long getMonitorFromWindowPosition(long window, int windowWidth, int windowHeight) {
        int[] winX = new int[1];
        int[] winY = new int[1];
        GLFW.glfwGetWindowPos(window, winX, winY);

        int windowCenterX = winX[0] + windowWidth / 2;
        int windowCenterY = winY[0] + windowHeight / 2;

        long monitorResult = GLFW.glfwGetPrimaryMonitor();
        PointerBuffer monitors = GLFW.glfwGetMonitors();
        if (monitors != null) {
            for (int i = 0; i < monitors.limit(); i++) {
                long m = monitors.get(i);
                int[] mx = new int[1];
                int[] my = new int[1];
                GLFW.glfwGetMonitorPos(m, mx, my);
                GLFWVidMode mode = GLFW.glfwGetVideoMode(m);
                if (mode == null) continue;

                int mw = mode.width();
                int mh = mode.height();

                if (windowCenterX >= mx[0] && windowCenterX < mx[0] + mw &&
                        windowCenterY >= my[0] && windowCenterY < my[0] + mh) {
                    monitorResult = m;
                    break;
                }
            }
        }
        return monitorResult;
    }

    private static int detectRefreshRateFromMonitor(long monitor) {
        GLFWVidMode vidMode = GLFW.glfwGetVideoMode(monitor);
        return (vidMode != null) ? vidMode.refreshRate() : 60;
    }
}
/up
 
Нужные миксины:
MixinLevelRenderer.java:
Expand Collapse Copy
package ru.motionreblur.mixin;

import net.minecraft.client.util.ObjectAllocator;
import net.minecraft.client.render.*;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import ru.motionreblur.MotionBlurModule;

@Mixin(WorldRenderer.class)
public class MixinLevelRenderer {
    @Unique
    private Matrix4f prevModelView = new Matrix4f();
    @Unique
    private Matrix4f prevProjection = new Matrix4f();
    @Unique
    private Vector3f prevCameraPos = new Vector3f();

    @Inject(method = "render", at = @At("HEAD"))
    private void setMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
        float tickDelta = tickCounter.getTickDelta(true);
        float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
        MotionBlurModule.getInstance().shader.setFrameMotionBlur(positionMatrix, prevModelView,
                gameRenderer.getBasicProjectionMatrix(fov),
                prevProjection,
                new Vector3f(
                        (float) (camera.getPos().x % 30000f),
                        (float) (camera.getPos().y % 30000f),
                        (float) (camera.getPos().z % 30000f)
                ),
                prevCameraPos
        );
    }

    @Inject(method = "render", at = @At("RETURN"))
    private void setOldMatrices(ObjectAllocator allocator, RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, Matrix4f positionMatrix, Matrix4f projectionMatrix, CallbackInfo ci) {
        prevModelView = new Matrix4f(positionMatrix);
        float tickDelta = tickCounter.getTickDelta(true);
        float fov = ((GameRendererAccessor) gameRenderer).invokeGetFov(camera, tickDelta, true);
        prevProjection = new Matrix4f(gameRenderer.getBasicProjectionMatrix(fov));
        prevCameraPos = new Vector3f(
                (float) (camera.getPos().x % 30000f),
                (float) (camera.getPos().y % 30000f),
                (float) (camera.getPos().z % 30000f)
        );
    }
}

GameRendererAccessor.java:
Expand Collapse Copy
package ru.motionreblur.mixin;

import net.minecraft.client.render.Camera;
import net.minecraft.client.render.GameRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

@Mixin(GameRenderer.class)
public interface GameRendererAccessor {
    @Invoker("getFov")
    float invokeGetFov(Camera camera, float tickDelta, boolean changingFov);
}

Сам блюр:
ShaderMotionBlur.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.ladysnake.satin.api.managed.ManagedShaderEffect;
import org.ladysnake.satin.api.managed.ShaderEffectManager;

public class ShaderMotionBlur {
    private final MotionBlurModule config;
    private final ManagedShaderEffect motionBlurShader;

    public ShaderMotionBlur(MotionBlurModule config) {
        this.config = config;
        motionBlurShader = ShaderEffectManager.getInstance().manage(
                Identifier.of(MotionReBlur.MOD_ID, "motion_blur"),
                shader -> shader.setUniformValue("BlendFactor", config.getStrength())
        );
    }

    private long lastNano = System.nanoTime();
    private float currentBlur = 0.0f;
    private float currentFPS = 0.0f;

    public void registerShaderCallbacks() {
        WorldRenderEvents.END.register(context -> {
            long now = System.nanoTime();
            float deltaTime = (now - lastNano) / 1_000_000_000.0f;
            float deltaTick = deltaTime * 20.0f;
            lastNano = now;

            if (deltaTime > 0 && deltaTime < 1.0f) {
                currentFPS = 1.0f / deltaTime;
            } else {
                currentFPS = 0.0f;
            }

            if (shouldRenderMotionBlur()) {
                applyMotionBlur(deltaTick);
            }
        });
    }

    private boolean shouldRenderMotionBlur() {
        if (config.getStrength() == 0 || !config.isEnabled()) {
            return false;
        }
        if (FabricLoader.getInstance().isModLoaded("iris")) {
            MotionReBlur.LOGGER.warn("Motion Blur cannot work with Iris Shaders!");
            config.setEnabled(false);
            return false;
        }
        return true;
    }

    private void applyMotionBlur(float deltaTick) {
        MinecraftClient client = MinecraftClient.getInstance();

        MonitorInfoProvider.updateDisplayInfo();
        int displayRefreshRate = MonitorInfoProvider.getRefreshRate();

        float baseStrength = config.getStrength();
        float scaledStrength = baseStrength;
        if (config.isUseRRC()) {
            float fpsOverRefresh = (displayRefreshRate > 0) ? currentFPS / displayRefreshRate : 1.0f;
            if (fpsOverRefresh < 1.0f) fpsOverRefresh = 1.0f;
            scaledStrength = baseStrength * fpsOverRefresh;
        }

        if (currentBlur != scaledStrength) {
            motionBlurShader.setUniformValue("BlendFactor", scaledStrength);
            currentBlur = scaledStrength;
        }

        int sampleAmount = getSampleAmountForFPS(currentFPS);
        int halfSampleAmount = sampleAmount / 2;
        float invSamples = 1.0f / sampleAmount;

        motionBlurShader.setUniformValue("view_res", (float) client.getFramebuffer().viewportWidth, (float) client.getFramebuffer().viewportHeight);
        motionBlurShader.setUniformValue("view_pixel_size", 1.0f / client.getFramebuffer().viewportWidth, 1.0f / client.getFramebuffer().viewportHeight);
        motionBlurShader.setUniformValue("motionBlurSamples", sampleAmount);
        motionBlurShader.setUniformValue("halfSamples", halfSampleAmount);
        motionBlurShader.setUniformValue("inverseSamples", invSamples);
        motionBlurShader.setUniformValue("blurAlgorithm", MotionBlurModule.BlurAlgorithm.CENTERED.ordinal());

        motionBlurShader.render(deltaTick);
    }

    private int getSampleAmountForFPS(float fps) {
        int quality = config.getQuality();

        int baseSamples = switch (quality) {
            case 0 -> 8;
            case 1 -> 12;
            case 2 -> 16;
            case 3 -> 24;
            default -> 12;
        };

        if (fps < 30) {
            return Math.max(6, baseSamples / 2);
        } else if (fps < 60) {
            return Math.max(8, (int) (baseSamples * 0.75f));
        } else if (fps > 144) {
            return Math.min(32, (int) (baseSamples * 1.25f));
        }

        return baseSamples;
    }

    private final Matrix4f tempPrevModelView = new Matrix4f();
    private final Matrix4f tempPrevProjection = new Matrix4f();
    private final Matrix4f tempProjInverse = new Matrix4f();
    private final Matrix4f tempMvInverse = new Matrix4f();

    public void setFrameMotionBlur(Matrix4f modelView, Matrix4f prevModelView,
                                   Matrix4f projection, Matrix4f prevProjection,
                                   Vector3f cameraPos, Vector3f prevCameraPos) {
        motionBlurShader.setUniformValue("mvInverse", tempMvInverse.set(modelView).invert());
        motionBlurShader.setUniformValue("projInverse", tempProjInverse.set(projection).invert());
        motionBlurShader.setUniformValue("prevModelView", tempPrevModelView.set(prevModelView));
        motionBlurShader.setUniformValue("prevProjection", tempPrevProjection.set(prevProjection));
        motionBlurShader.setUniformValue("cameraPos", cameraPos.x, cameraPos.y, cameraPos.z);
        motionBlurShader.setUniformValue("prevCameraPos", prevCameraPos.x, prevCameraPos.y, prevCameraPos.z);
    }

    public void updateBlurStrength(float strength) {
        motionBlurShader.setUniformValue("BlendFactor", strength);
        currentBlur = strength;
    }
}
MotionReBlur.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.api.ClientModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MotionReBlur implements ClientModInitializer {
    public static final String MOD_ID = "motionreblur";
    public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);

    @Override
    public void onInitializeClient() {
        LOGGER.info("Motion ReBlur initialized!");

        MotionBlurModule.getInstance();

        MotionBlurCommand.register();

        MotionBlurKeyBinding.register();
    }
}
MotionBlurModule.java:
Expand Collapse Copy
package ru.motionreblur;

public class MotionBlurModule {
    private static final MotionBlurModule instance = new MotionBlurModule();
    public final ShaderMotionBlur shader;

    private boolean enabled = false;
    private float strength = -0.8f;
    private boolean useRRC = true;
    private int quality = 2;

    private MotionBlurModule() {
        shader = new ShaderMotionBlur(this);
        shader.registerShaderCallbacks();
    }

    public static MotionBlurModule getInstance() {
        return instance;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        MotionReBlur.LOGGER.info("Motion Blur " + (enabled ? "enabled" : "disabled"));
    }

    public float getStrength() {
        return strength;
    }

    public void setStrength(float strength) {
        this.strength = Math.max(-2.0f, Math.min(2.0f, strength));
        shader.updateBlurStrength(this.strength);
        MotionReBlur.LOGGER.info("Motion Blur strength set to " + this.strength);
    }

    public boolean isUseRRC() {
        return useRRC;
    }

    public void setUseRRC(boolean useRRC) {
        this.useRRC = useRRC;
        MotionReBlur.LOGGER.info("Refresh Rate Scaling " + (useRRC ? "enabled" : "disabled"));
    }

    public int getQuality() {
        return quality;
    }

    public void setQuality(int quality) {
        this.quality = Math.max(0, Math.min(3, quality));
        MotionReBlur.LOGGER.info("Motion Blur quality set to " + getQualityName());
    }

    public String getQualityName() {
        return switch (quality) {
            case 0 -> "Низкое";
            case 1 -> "Среднее";
            case 2 -> "Высокое";
            case 3 -> "Ультра";
            default -> "Среднее";
        };
    }

    public enum BlurAlgorithm {BACKWARDS, CENTERED}

    public static BlurAlgorithm blurAlgorithm = BlurAlgorithm.CENTERED;
}
MotionBlurKeyBinding.java:
Expand Collapse Copy
package ru.motionreblur;

import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.lwjgl.glfw.GLFW;

public class MotionBlurKeyBinding {
    private static KeyBinding openGuiKey;

    public static void register() {
        openGuiKey = KeyBindingHelper.registerKeyBinding(new KeyBinding(
                "key.motionreblur.open_gui",
                InputUtil.Type.KEYSYM,
                GLFW.GLFW_KEY_M,
                "category.motionreblur"
        ));

        ClientTickEvents.END_CLIENT_TICK.register(client -> {
            while (openGuiKey.wasPressed()) {
                client.setScreen(new MotionBlurConfigScreen(client.currentScreen));
            }
        });
    }
}
MotionBlurConfigScreen.java:
Expand Collapse Copy
package ru.motionreblur;

import net.minecraft.client.gui.DrawContext;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.SliderWidget;
import net.minecraft.text.Text;

public class MotionBlurConfigScreen extends Screen {
    private final Screen parent;
    private SliderWidget strengthSlider;
    private ButtonWidget toggleButton;
    private ButtonWidget rrcButton;
    private ButtonWidget qualityButton;

    public MotionBlurConfigScreen(Screen parent) {
        super(Text.literal("Motion ReBlur Settings"));
        this.parent = parent;
    }

    @Override
    protected void init() {
        MotionBlurModule mb = MotionBlurModule.getInstance();

        int centerX = this.width / 2;
        int startY = this.height / 2 - 60;

        toggleButton = ButtonWidget.builder(
                Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")),
                button -> {
                    mb.setEnabled(!mb.isEnabled());
                    button.setMessage(Text.literal("Motion Blur: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
                }
        ).dimensions(centerX - 100, startY, 200, 20).build();

        strengthSlider = new SliderWidget(
                centerX - 100, startY + 30, 200, 20,
                Text.literal("Сила: " + String.format("%.1f", mb.getStrength())),
                (mb.getStrength() + 2.0) / 4.0
        ) {
            @Override
            protected void updateMessage() {
                double value = this.value * 4.0 - 2.0;
                this.setMessage(Text.literal("Сила: " + String.format("%.1f", value)));
            }

            @Override
            protected void applyValue() {
                float value = (float) (this.value * 4.0 - 2.0);
                mb.setStrength(value);
            }
        };

        rrcButton = ButtonWidget.builder(
                Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")),
                button -> {
                    mb.setUseRRC(!mb.isUseRRC());
                    button.setMessage(Text.literal("Адаптация к частоте монитора: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
                }
        ).dimensions(centerX - 100, startY + 60, 200, 20).build();

        qualityButton = ButtonWidget.builder(
                Text.literal("Качество: " + mb.getQualityName()),
                button -> {
                    int newQuality = (mb.getQuality() + 1) % 4;
                    mb.setQuality(newQuality);
                    button.setMessage(Text.literal("Качество: " + mb.getQualityName()));
                }
        ).dimensions(centerX - 100, startY + 90, 200, 20).build();

        ButtonWidget doneButton = ButtonWidget.builder(
                Text.translatable("gui.done"),
                button -> this.close()
        ).dimensions(centerX - 100, startY + 120, 200, 20).build();

        addDrawableChild(toggleButton);
        addDrawableChild(strengthSlider);
        addDrawableChild(rrcButton);
        addDrawableChild(qualityButton);
        addDrawableChild(doneButton);
    }

    @Override
    public void render(DrawContext context, int mouseX, int mouseY, float delta) {
        this.renderBackground(context, mouseX, mouseY, delta);
        super.render(context, mouseX, mouseY, delta);

        context.drawCenteredTextWithShadow(
                this.textRenderer,
                this.title,
                this.width / 2,
                20,
                0xFFFFFF
        );

        MotionBlurModule mb = MotionBlurModule.getInstance();
        int refreshRate = MonitorInfoProvider.getRefreshRate();
        String info = "Refresh Rate: " + refreshRate + " Hz";
        context.drawCenteredTextWithShadow(
                this.textRenderer,
                Text.literal(info),
                this.width / 2,
                this.height / 2 + 80,
                0x808080
        );

        if (mb.isEnabled()) {
            String hint = "Двигайте камеру, чтобы увидеть эффект";
            context.drawCenteredTextWithShadow(
                    this.textRenderer,
                    Text.literal(hint),
                    this.width / 2,
                    this.height / 2 + 95,
                    0x808080
            );
        }
    }

    @Override
    public void close() {
        if (this.client != null) {
            this.client.setScreen(parent);
        }
    }
}
MotionBlurCommand.java:
Expand Collapse Copy
package ru.motionreblur;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.context.CommandContext;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;

public class MotionBlurCommand {

    public static void register() {
        ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> {
            registerCommand(dispatcher);
        });
    }

    private static void registerCommand(CommandDispatcher<FabricClientCommandSource> dispatcher) {
        dispatcher.register(ClientCommandManager.literal("motionreblur")
                .executes(ctx -> openGui(ctx))
                .then(ClientCommandManager.literal("on")
                        .executes(ctx -> enable(ctx)))
                .then(ClientCommandManager.literal("off")
                        .executes(ctx -> disable(ctx)))
                .then(ClientCommandManager.literal("toggle")
                        .executes(ctx -> toggle(ctx)))
                .then(ClientCommandManager.literal("strength")
                        .then(ClientCommandManager.argument("value", FloatArgumentType.floatArg(-2.0f, 2.0f))
                                .executes(ctx -> setStrength(ctx, FloatArgumentType.getFloat(ctx, "value")))))
                .then(ClientCommandManager.literal("rrc")
                        .executes(ctx -> toggleRRC(ctx)))
                .then(ClientCommandManager.literal("status")
                        .executes(ctx -> showStatus(ctx)))
                .then(ClientCommandManager.literal("help")
                        .executes(ctx -> showHelp(ctx)))
        );
    }

    private static int openGui(CommandContext<FabricClientCommandSource> ctx) {
        MinecraftClient client = MinecraftClient.getInstance();
        client.execute(() -> {
            client.setScreen(new MotionBlurConfigScreen(null));
        });
        ctx.getSource().sendFeedback(Text.literal("§aОткрываю меню настроек..."));
        return 1;
    }

    private static int showStatus(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        ctx.getSource().sendFeedback(Text.literal("§7§m                    "));
        ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur"));
        ctx.getSource().sendFeedback(Text.literal("§7Состояние: " + (mb.isEnabled() ? "§aВКЛ" : "§cВЫКЛ")));
        ctx.getSource().sendFeedback(Text.literal("§7Сила: §f" + String.format("%.1f", mb.getStrength())));
        ctx.getSource().sendFeedback(Text.literal("§7RRC: " + (mb.isUseRRC() ? "§aВКЛ" : "§cВЫКЛ")));
        ctx.getSource().sendFeedback(Text.literal("§7Refresh Rate: §f" + MonitorInfoProvider.getRefreshRate() + " Hz"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                    "));
        return 1;
    }

    private static int enable(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule.getInstance().setEnabled(true);
        ctx.getSource().sendFeedback(Text.literal("§aMotion Blur включен"));
        return 1;
    }

    private static int disable(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule.getInstance().setEnabled(false);
        ctx.getSource().sendFeedback(Text.literal("§cMotion Blur выключен"));
        return 1;
    }

    private static int toggle(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        boolean newState = !mb.isEnabled();
        mb.setEnabled(newState);
        ctx.getSource().sendFeedback(Text.literal("§7Motion Blur: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
        return 1;
    }

    private static int setStrength(CommandContext<FabricClientCommandSource> ctx, float value) {
        MotionBlurModule.getInstance().setStrength(value);
        ctx.getSource().sendFeedback(Text.literal("§aСила установлена на §f" + String.format("%.1f", value)));
        return 1;
    }

    private static int toggleRRC(CommandContext<FabricClientCommandSource> ctx) {
        MotionBlurModule mb = MotionBlurModule.getInstance();
        boolean newState = !mb.isUseRRC();
        mb.setUseRRC(newState);
        ctx.getSource().sendFeedback(Text.literal("§7Адаптация к частоте монитора: " + (newState ? "§aВКЛ" : "§cВЫКЛ")));
        return 1;
    }

    private static int showHelp(CommandContext<FabricClientCommandSource> ctx) {
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        ctx.getSource().sendFeedback(Text.literal("§6Motion ReBlur - Команды"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur §7- открыть GUI"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur on §7- включить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur off §7- выключить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur toggle §7- переключить"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur strength <значение> §7- установить силу"));
        ctx.getSource().sendFeedback(Text.literal("§7  Диапазон: от -2.0 до 2.0"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur rrc §7- переключить адаптацию к частоте"));
        ctx.getSource().sendFeedback(Text.literal("§e/motionreblur status §7- показать статус"));
        ctx.getSource().sendFeedback(Text.literal("§7§m                              "));
        return 1;
    }
}
MonitorInfoProvider.java:
Expand Collapse Copy
package ru.motionreblur;

import net.minecraft.client.MinecraftClient;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWVidMode;

public class MonitorInfoProvider {
    private static long lastMonitorHandle = 0;
    private static int lastRefreshRate = 60;
    private static long lastCheckTime = 0;
    private static final long CHECK_INTERVAL_NS = 1_000_000_000L;

    public static void updateDisplayInfo() {
        long now = System.nanoTime();
        if (now - lastCheckTime < CHECK_INTERVAL_NS) {
            return;
        }
        lastCheckTime = now;

        MinecraftClient client = MinecraftClient.getInstance();
        if (client == null || client.getWindow() == null) return;

        long window = client.getWindow().getHandle();
        long monitor = GLFW.glfwGetWindowMonitor(window);

        if (monitor == 0) {
            monitor = getMonitorFromWindowPosition(window, client.getWindow().getWidth(), client.getWindow().getHeight());
        }

        if (monitor != lastMonitorHandle) {
            lastRefreshRate = detectRefreshRateFromMonitor(monitor);
            lastMonitorHandle = monitor;
        }
    }

    public static int getRefreshRate() {
        return lastRefreshRate;
    }

    private static long getMonitorFromWindowPosition(long window, int windowWidth, int windowHeight) {
        int[] winX = new int[1];
        int[] winY = new int[1];
        GLFW.glfwGetWindowPos(window, winX, winY);

        int windowCenterX = winX[0] + windowWidth / 2;
        int windowCenterY = winY[0] + windowHeight / 2;

        long monitorResult = GLFW.glfwGetPrimaryMonitor();
        PointerBuffer monitors = GLFW.glfwGetMonitors();
        if (monitors != null) {
            for (int i = 0; i < monitors.limit(); i++) {
                long m = monitors.get(i);
                int[] mx = new int[1];
                int[] my = new int[1];
                GLFW.glfwGetMonitorPos(m, mx, my);
                GLFWVidMode mode = GLFW.glfwGetVideoMode(m);
                if (mode == null) continue;

                int mw = mode.width();
                int mh = mode.height();

                if (windowCenterX >= mx[0] && windowCenterX < mx[0] + mw &&
                        windowCenterY >= my[0] && windowCenterY < my[0] + mh) {
                    monitorResult = m;
                    break;
                }
            }
        }
        return monitorResult;
    }

    private static int detectRefreshRateFromMonitor(long monitor) {
        GLFWVidMode vidMode = GLFW.glfwGetVideoMode(monitor);
        return (vidMode != null) ? vidMode.refreshRate() : 60;
    }
}
лучший
 
Назад
Сверху Снизу