Files
sasheg dev c0db40f624 Контекст OpenGL и честный счетчик кадров
Пункт ноль плана: клиент теперь просит контекст 3.3 в режиме
совместимости, а не тот, что выпадал по умолчанию.

Зачем: дальний вид будет писаться шейдером GLSL 330, и контекст должен
гарантированно быть не ниже 3.3. В профиле совместимости работают и
шейдеры игры (#version 120, их 34 штуки), и наши 330-е. LWJGL 2.9.4 выше
3.3 контекст не заказывает - ограничение в его конструкторе, а не в
драйвере. Драйвер при этом дает 4.6, и расширение GL_EXT_gpu_shader4,
которое просит FXAA-шейдер игры, в этом контексте на месте.

Замер кадров теперь пишется в лог каждую секунду: средний, медиана,
1% и 0.1% low. Одного среднего мало: клиент, который идет со 60
кадрами и дважды в секунду проваливается в 10, в среднем остается 60 и
ощущается как slideshow. Медленные кадры - это то, что чувствует
игрок, поэтому окно держит все времена кадров и говорит про хвост.

Среднее и процентили видны и в HUD, так что их не надо вылавливать из
скриншотов.

Замер: 307 fps | med 2.9 ms | 1% 6.0 ms | 0.1% 18.1 ms.
2026-10-20 21:21:19 +03:00

238 lines
6.9 KiB
Java

package net.rustnotmc.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiChat;
import net.minecraft.client.gui.GuiIngameMenu;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiOptions;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiWorldSelection;
import net.minecraft.client.gui.achievement.GuiStats;
import net.minecraft.client.gui.advancements.GuiScreenAdvancements;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.settings.KeyBinding;
import net.rustnotmc.api.client.ClientServices;
import net.rustnotmc.api.client.Commands;
import net.rustnotmc.api.client.Input;
import net.rustnotmc.api.client.Screen;
import net.rustnotmc.api.client.ScreenKind;
import org.lwjgl.input.Mouse;
/**
* The client's implementation of the mod API.
*
* Everything here reads presentation state the client already has: what screen
* is open, what the window measures, which key is held. Nothing reaches into
* the world, a player or a packet, because the server owns those and a client
* mod is not allowed to decide them.
*
* The values are recomputed per call rather than cached. A mod may ask once a
* frame, and a stale answer would be worse than a cheap one.
*
* Lives in game-fork/src-overlay; src is regenerated on every build.
*/
public final class RsxClientServices implements ClientServices {
private final Minecraft minecraft;
private final RsxScreen screen = new RsxScreen();
private final RsxInput input = new RsxInput();
private final RsxCommands commands = new RsxCommands();
private long tick;
private long frame;
public RsxClientServices(Minecraft minecraft) {
this.minecraft = minecraft;
}
/** Called once per frame by the bootstrap, before anything is asked. */
public void advanceFrame() {
this.frame++;
}
/** Called once per tick by the bootstrap. */
public void advanceTick() {
this.tick++;
}
boolean dispatchCommand(String line, String sender) {
return this.commands.dispatch(line, sender);
}
@Override
public Screen screen() {
return this.screen;
}
@Override
public Input input() {
return this.input;
}
@Override
public Commands commands() {
return this.commands;
}
@Override
public long tick() {
return this.tick;
}
@Override
public long frame() {
return this.frame;
}
@Override
public net.rustnotmc.api.client.FrameStats frameStats() {
return RsxFrameStats.latest();
}
private final class RsxScreen implements Screen {
@Override
public ScreenKind kind() {
GuiScreen current = minecraft.currentScreen;
if (current == null) {
return minecraft.world == null ? ScreenKind.UNKNOWN : ScreenKind.IN_GAME;
}
if (current instanceof GuiChat) {
return ScreenKind.CHAT;
}
if (current instanceof GuiIngameMenu) {
return ScreenKind.PAUSED;
}
if (current instanceof GuiMainMenu) {
return ScreenKind.MAIN_MENU;
}
if (current instanceof GuiWorldSelection) {
return ScreenKind.WORLD_SELECTION;
}
if (current instanceof GuiOptions) {
return ScreenKind.OPTIONS;
}
if (current instanceof GuiContainer) {
return ScreenKind.INVENTORY;
}
if (current instanceof GuiScreenAdvancements || current instanceof GuiStats) {
return ScreenKind.ADVANCEMENTS;
}
// a screen this build has no name for, over a loaded world or not
return minecraft.world == null ? ScreenKind.UNKNOWN : ScreenKind.PAUSED;
}
@Override
public int width() {
return minecraft.displayWidth / Math.max(1, scaleFactor());
}
@Override
public int height() {
return minecraft.displayHeight / Math.max(1, scaleFactor());
}
@Override
public float scale() {
return (float) scaleFactor();
}
@Override
public int windowWidth() {
return minecraft.displayWidth;
}
@Override
public int windowHeight() {
return minecraft.displayHeight;
}
private int scaleFactor() {
int gui = minecraft.gameSettings == null ? 2 : minecraft.gameSettings.guiScale;
if (gui <= 0) {
// auto: the client picks the largest scale that keeps the
// interface at least this wide
int width = minecraft.displayWidth;
gui = 1;
while (width / (gui + 1) >= 320 && gui < 4) {
gui++;
}
}
return gui;
}
}
private final class RsxInput implements Input {
@Override
public boolean keyDown(String keyId) {
if (keyId == null || minecraft.gameSettings == null) {
return false;
}
KeyBinding[] bindings = minecraft.gameSettings.keyBindings;
if (bindings == null) {
return false;
}
for (int i = 0; i < bindings.length; i++) {
if (keyId.equals(bindings[i].getKeyDescription()) && bindings[i].isKeyDown()) {
return true;
}
}
return false;
}
@Override
public boolean anyKeyDown() {
KeyBinding[] bindings = minecraft.gameSettings == null
? null : minecraft.gameSettings.keyBindings;
if (bindings == null) {
return false;
}
for (int i = 0; i < bindings.length; i++) {
if (bindings[i].isKeyDown()) {
return true;
}
}
return false;
}
@Override
public boolean focused() {
return minecraft.inGameHasFocus;
}
@Override
public int mouseX() {
return scaled(Mouse.getX());
}
@Override
public int mouseY() {
return scaled(Mouse.getY());
}
@Override
public int mouseButtons() {
int buttons = 0;
if (Mouse.isButtonDown(0)) {
buttons |= 1;
}
if (Mouse.isButtonDown(1)) {
buttons |= 2;
}
if (Mouse.isButtonDown(2)) {
buttons |= 4;
}
return buttons;
}
@Override
public boolean mouseDown(int button) {
return button >= 0 && button <= 2 && Mouse.isButtonDown(button);
}
private int scaled(int windowCoord) {
return windowCoord / Math.max(1, screen.scaleFactor());
}
}
}