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

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

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
29 Мар 2026
Сообщения
43
Реакции
1
Нужные миксины:
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;
    }
}
 
Нужные миксины:
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 молодец ты лучший
Создай на 1.16.5 MCP плиз
 
Нужные миксины:
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;
    }
}
дай ss
 
Нужные миксины:
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;
    }
}
красава, взял с евавара и перекинул в тред, легенда нахуй
 
ХАХАХАХАХХАХАХА ГДЕ ТУТ ЕВАВАР ПОКАЖИ ХАХАХААХАХАХА
Т.е, ты хочешь сказать что человек в здравом уме будет использовать satin api обрекая себя на пожизненую не совместимость с 1.21.4+ ? Не неси хуйню
 
Т.е, ты хочешь сказать что человек в здравом уме будет использовать satin api обрекая себя на пожизненую совместимость с 1.21.4+ ? Не неси хуйню
тупее фантаймера я не видел это пиздец
 
тупее фантаймера я не видел это пиздец
Я не играю на фантайме, так что вальни свою тупорылую ебасосину и обьясни нахуя ты тогда использовал satin?
 
Я не играю на фантайме, так что вальни свою тупорылую ебасосину и обьясни нахуя ты тогда использовал satin?
я использовал satin api потому что мой чит на базе alien v4 где мой китайский друг является кодером и использует satin api
 
Нужные миксины:
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 Я увидел по сатин апи что это говно 2 Твоя тема в целом говно ебное без обьяснения чего либо, мне больше даже не надо доказывать ничего
 
1 Я увидел по сатин апи что это говно 2 Твоя тема в целом говно ебное без обьяснения чего либо, мне больше даже не надо доказывать ничего
нашелся пастерок который не смог осилить satin api
 
Нашелся человек который не смог написать без сатина, рофланебало момент
чувак без знаний хейтит человека который сам написал моушн блюр на крутой библиотеке))))
 
Назад
Сверху Снизу