505 lines
15 KiB
Java
505 lines
15 KiB
Java
package com.mojang.blaze3d.platform;
|
|
|
|
import com.mojang.blaze3d.TracyFrameCapture;
|
|
import com.mojang.blaze3d.systems.RenderSystem;
|
|
import com.mojang.logging.LogUtils;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Optional;
|
|
import java.util.function.BiConsumer;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.CrashReport;
|
|
import net.minecraft.CrashReportCategory;
|
|
import net.minecraft.ReportedException;
|
|
import net.minecraft.client.main.SilentInitException;
|
|
import net.minecraft.server.packs.PackResources;
|
|
import net.minecraft.server.packs.resources.IoSupplier;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.lwjgl.PointerBuffer;
|
|
import org.lwjgl.glfw.Callbacks;
|
|
import org.lwjgl.glfw.GLFW;
|
|
import org.lwjgl.glfw.GLFWErrorCallback;
|
|
import org.lwjgl.glfw.GLFWImage;
|
|
import org.lwjgl.glfw.GLFWWindowCloseCallback;
|
|
import org.lwjgl.glfw.GLFWImage.Buffer;
|
|
import org.lwjgl.system.MemoryStack;
|
|
import org.lwjgl.system.MemoryUtil;
|
|
import org.lwjgl.util.tinyfd.TinyFileDialogs;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public final class Window implements AutoCloseable {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
public static final int BASE_WIDTH = 320;
|
|
public static final int BASE_HEIGHT = 240;
|
|
private final GLFWErrorCallback defaultErrorCallback = GLFWErrorCallback.create(this::defaultErrorCallback);
|
|
private final WindowEventHandler eventHandler;
|
|
private final ScreenManager screenManager;
|
|
private final long window;
|
|
private int windowedX;
|
|
private int windowedY;
|
|
private int windowedWidth;
|
|
private int windowedHeight;
|
|
private Optional<VideoMode> preferredFullscreenVideoMode;
|
|
private boolean fullscreen;
|
|
private boolean actuallyFullscreen;
|
|
private int x;
|
|
private int y;
|
|
private int width;
|
|
private int height;
|
|
private int framebufferWidth;
|
|
private int framebufferHeight;
|
|
private int guiScaledWidth;
|
|
private int guiScaledHeight;
|
|
private double guiScale;
|
|
private String errorSection = "";
|
|
private boolean dirty;
|
|
private boolean vsync;
|
|
private boolean iconified;
|
|
private boolean minimized;
|
|
|
|
public Window(
|
|
WindowEventHandler eventHandler, ScreenManager screenManager, DisplayData displayData, @Nullable String preferredFullscreenVideoMode, String title
|
|
) {
|
|
this.screenManager = screenManager;
|
|
this.setBootErrorCallback();
|
|
this.setErrorSection("Pre startup");
|
|
this.eventHandler = eventHandler;
|
|
Optional<VideoMode> optional = VideoMode.read(preferredFullscreenVideoMode);
|
|
if (optional.isPresent()) {
|
|
this.preferredFullscreenVideoMode = optional;
|
|
} else if (displayData.fullscreenWidth().isPresent() && displayData.fullscreenHeight().isPresent()) {
|
|
this.preferredFullscreenVideoMode = Optional.of(
|
|
new VideoMode(displayData.fullscreenWidth().getAsInt(), displayData.fullscreenHeight().getAsInt(), 8, 8, 8, 60)
|
|
);
|
|
} else {
|
|
this.preferredFullscreenVideoMode = Optional.empty();
|
|
}
|
|
|
|
this.actuallyFullscreen = this.fullscreen = displayData.isFullscreen();
|
|
Monitor monitor = screenManager.getMonitor(GLFW.glfwGetPrimaryMonitor());
|
|
this.windowedWidth = this.width = Math.max(displayData.width(), 1);
|
|
this.windowedHeight = this.height = Math.max(displayData.height(), 1);
|
|
GLFW.glfwDefaultWindowHints();
|
|
GLFW.glfwWindowHint(139265, 196609);
|
|
GLFW.glfwWindowHint(139275, 221185);
|
|
GLFW.glfwWindowHint(139266, 3);
|
|
GLFW.glfwWindowHint(139267, 2);
|
|
GLFW.glfwWindowHint(139272, 204801);
|
|
GLFW.glfwWindowHint(139270, 1);
|
|
this.window = GLFW.glfwCreateWindow(this.width, this.height, title, this.fullscreen && monitor != null ? monitor.getMonitor() : 0L, 0L);
|
|
if (monitor != null) {
|
|
VideoMode videoMode = monitor.getPreferredVidMode(this.fullscreen ? this.preferredFullscreenVideoMode : Optional.empty());
|
|
this.windowedX = this.x = monitor.getX() + videoMode.getWidth() / 2 - this.width / 2;
|
|
this.windowedY = this.y = monitor.getY() + videoMode.getHeight() / 2 - this.height / 2;
|
|
} else {
|
|
int[] is = new int[1];
|
|
int[] js = new int[1];
|
|
GLFW.glfwGetWindowPos(this.window, is, js);
|
|
this.windowedX = this.x = is[0];
|
|
this.windowedY = this.y = js[0];
|
|
}
|
|
|
|
this.setMode();
|
|
this.refreshFramebufferSize();
|
|
GLFW.glfwSetFramebufferSizeCallback(this.window, this::onFramebufferResize);
|
|
GLFW.glfwSetWindowPosCallback(this.window, this::onMove);
|
|
GLFW.glfwSetWindowSizeCallback(this.window, this::onResize);
|
|
GLFW.glfwSetWindowFocusCallback(this.window, this::onFocus);
|
|
GLFW.glfwSetCursorEnterCallback(this.window, this::onEnter);
|
|
GLFW.glfwSetWindowIconifyCallback(this.window, this::onIconify);
|
|
}
|
|
|
|
public static String getPlatform() {
|
|
int i = GLFW.glfwGetPlatform();
|
|
|
|
return switch (i) {
|
|
case 0 -> "<error>";
|
|
case 393217 -> "win32";
|
|
case 393218 -> "cocoa";
|
|
case 393219 -> "wayland";
|
|
case 393220 -> "x11";
|
|
case 393221 -> "null";
|
|
default -> String.format(Locale.ROOT, "unknown (%08X)", i);
|
|
};
|
|
}
|
|
|
|
public int getRefreshRate() {
|
|
RenderSystem.assertOnRenderThread();
|
|
return GLX._getRefreshRate(this);
|
|
}
|
|
|
|
public boolean shouldClose() {
|
|
return GLX._shouldClose(this);
|
|
}
|
|
|
|
public static void checkGlfwError(BiConsumer<Integer, String> errorConsumer) {
|
|
try (MemoryStack memoryStack = MemoryStack.stackPush()) {
|
|
PointerBuffer pointerBuffer = memoryStack.mallocPointer(1);
|
|
int i = GLFW.glfwGetError(pointerBuffer);
|
|
if (i != 0) {
|
|
long l = pointerBuffer.get();
|
|
String string = l == 0L ? "" : MemoryUtil.memUTF8(l);
|
|
errorConsumer.accept(i, string);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setIcon(PackResources packResources, IconSet iconSet) throws IOException {
|
|
int i = GLFW.glfwGetPlatform();
|
|
switch (i) {
|
|
case 393217:
|
|
case 393220:
|
|
List<IoSupplier<InputStream>> list = iconSet.getStandardIcons(packResources);
|
|
List<ByteBuffer> list2 = new ArrayList(list.size());
|
|
|
|
try (MemoryStack memoryStack = MemoryStack.stackPush()) {
|
|
Buffer buffer = GLFWImage.malloc(list.size(), memoryStack);
|
|
|
|
for (int j = 0; j < list.size(); j++) {
|
|
try (NativeImage nativeImage = NativeImage.read((InputStream)((IoSupplier)list.get(j)).get())) {
|
|
ByteBuffer byteBuffer = MemoryUtil.memAlloc(nativeImage.getWidth() * nativeImage.getHeight() * 4);
|
|
list2.add(byteBuffer);
|
|
byteBuffer.asIntBuffer().put(nativeImage.getPixelsABGR());
|
|
buffer.position(j);
|
|
buffer.width(nativeImage.getWidth());
|
|
buffer.height(nativeImage.getHeight());
|
|
buffer.pixels(byteBuffer);
|
|
}
|
|
}
|
|
|
|
GLFW.glfwSetWindowIcon(this.window, buffer.position(0));
|
|
break;
|
|
} finally {
|
|
list2.forEach(MemoryUtil::memFree);
|
|
}
|
|
case 393218:
|
|
MacosUtil.loadIcon(iconSet.getMacIcon(packResources));
|
|
case 393219:
|
|
case 393221:
|
|
break;
|
|
default:
|
|
LOGGER.warn("Not setting icon for unrecognized platform: {}", i);
|
|
}
|
|
}
|
|
|
|
public void setErrorSection(String errorSection) {
|
|
this.errorSection = errorSection;
|
|
}
|
|
|
|
private void setBootErrorCallback() {
|
|
GLFW.glfwSetErrorCallback(Window::bootCrash);
|
|
}
|
|
|
|
private static void bootCrash(int error, long description) {
|
|
String string = "GLFW error " + error + ": " + MemoryUtil.memUTF8(description);
|
|
TinyFileDialogs.tinyfd_messageBox(
|
|
"Minecraft", string + ".\n\nPlease make sure you have up-to-date drivers (see aka.ms/mcdriver for instructions).", "ok", "error", false
|
|
);
|
|
throw new Window.WindowInitFailed(string);
|
|
}
|
|
|
|
public void defaultErrorCallback(int error, long description) {
|
|
RenderSystem.assertOnRenderThread();
|
|
String string = MemoryUtil.memUTF8(description);
|
|
LOGGER.error("########## GL ERROR ##########");
|
|
LOGGER.error("@ {}", this.errorSection);
|
|
LOGGER.error("{}: {}", error, string);
|
|
}
|
|
|
|
public void setDefaultErrorCallback() {
|
|
GLFWErrorCallback gLFWErrorCallback = GLFW.glfwSetErrorCallback(this.defaultErrorCallback);
|
|
if (gLFWErrorCallback != null) {
|
|
gLFWErrorCallback.free();
|
|
}
|
|
}
|
|
|
|
public void updateVsync(boolean vsync) {
|
|
RenderSystem.assertOnRenderThread();
|
|
this.vsync = vsync;
|
|
GLFW.glfwSwapInterval(vsync ? 1 : 0);
|
|
}
|
|
|
|
public void close() {
|
|
RenderSystem.assertOnRenderThread();
|
|
Callbacks.glfwFreeCallbacks(this.window);
|
|
this.defaultErrorCallback.close();
|
|
GLFW.glfwDestroyWindow(this.window);
|
|
GLFW.glfwTerminate();
|
|
}
|
|
|
|
private void onMove(long window, int x, int y) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
|
|
private void onFramebufferResize(long window, int framebufferWidth, int framebufferHeight) {
|
|
if (window == this.window) {
|
|
int i = this.getWidth();
|
|
int j = this.getHeight();
|
|
if (framebufferWidth != 0 && framebufferHeight != 0) {
|
|
this.minimized = false;
|
|
this.framebufferWidth = framebufferWidth;
|
|
this.framebufferHeight = framebufferHeight;
|
|
if (this.getWidth() != i || this.getHeight() != j) {
|
|
try {
|
|
this.eventHandler.resizeDisplay();
|
|
} catch (Exception var10) {
|
|
CrashReport crashReport = CrashReport.forThrowable(var10, "Window resize");
|
|
CrashReportCategory crashReportCategory = crashReport.addCategory("Window Dimensions");
|
|
crashReportCategory.setDetail("Old", i + "x" + j);
|
|
crashReportCategory.setDetail("New", framebufferWidth + "x" + framebufferHeight);
|
|
throw new ReportedException(crashReport);
|
|
}
|
|
}
|
|
} else {
|
|
this.minimized = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void refreshFramebufferSize() {
|
|
int[] is = new int[1];
|
|
int[] js = new int[1];
|
|
GLFW.glfwGetFramebufferSize(this.window, is, js);
|
|
this.framebufferWidth = is[0] > 0 ? is[0] : 1;
|
|
this.framebufferHeight = js[0] > 0 ? js[0] : 1;
|
|
}
|
|
|
|
private void onResize(long window, int width, int height) {
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
private void onFocus(long window, boolean hasFocus) {
|
|
if (window == this.window) {
|
|
this.eventHandler.setWindowActive(hasFocus);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param cursorEntered {@code true} if the cursor entered the window, {@code false} if the cursor left
|
|
*/
|
|
private void onEnter(long window, boolean cursorEntered) {
|
|
if (cursorEntered) {
|
|
this.eventHandler.cursorEntered();
|
|
}
|
|
}
|
|
|
|
private void onIconify(long window, boolean iconified) {
|
|
this.iconified = iconified;
|
|
}
|
|
|
|
public void updateDisplay(@Nullable TracyFrameCapture tracyFrameCapture) {
|
|
RenderSystem.flipFrame(this.window, tracyFrameCapture);
|
|
if (this.fullscreen != this.actuallyFullscreen) {
|
|
this.actuallyFullscreen = this.fullscreen;
|
|
this.updateFullscreen(this.vsync, tracyFrameCapture);
|
|
}
|
|
}
|
|
|
|
public Optional<VideoMode> getPreferredFullscreenVideoMode() {
|
|
return this.preferredFullscreenVideoMode;
|
|
}
|
|
|
|
public void setPreferredFullscreenVideoMode(Optional<VideoMode> preferredFullscreenVideoMode) {
|
|
boolean bl = !preferredFullscreenVideoMode.equals(this.preferredFullscreenVideoMode);
|
|
this.preferredFullscreenVideoMode = preferredFullscreenVideoMode;
|
|
if (bl) {
|
|
this.dirty = true;
|
|
}
|
|
}
|
|
|
|
public void changeFullscreenVideoMode() {
|
|
if (this.fullscreen && this.dirty) {
|
|
this.dirty = false;
|
|
this.setMode();
|
|
this.eventHandler.resizeDisplay();
|
|
}
|
|
}
|
|
|
|
private void setMode() {
|
|
boolean bl = GLFW.glfwGetWindowMonitor(this.window) != 0L;
|
|
if (this.fullscreen) {
|
|
Monitor monitor = this.screenManager.findBestMonitor(this);
|
|
if (monitor == null) {
|
|
LOGGER.warn("Failed to find suitable monitor for fullscreen mode");
|
|
this.fullscreen = false;
|
|
} else {
|
|
if (MacosUtil.IS_MACOS) {
|
|
MacosUtil.exitNativeFullscreen(this.window);
|
|
}
|
|
|
|
VideoMode videoMode = monitor.getPreferredVidMode(this.preferredFullscreenVideoMode);
|
|
if (!bl) {
|
|
this.windowedX = this.x;
|
|
this.windowedY = this.y;
|
|
this.windowedWidth = this.width;
|
|
this.windowedHeight = this.height;
|
|
}
|
|
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.width = videoMode.getWidth();
|
|
this.height = videoMode.getHeight();
|
|
GLFW.glfwSetWindowMonitor(this.window, monitor.getMonitor(), this.x, this.y, this.width, this.height, videoMode.getRefreshRate());
|
|
if (MacosUtil.IS_MACOS) {
|
|
MacosUtil.clearResizableBit(this.window);
|
|
}
|
|
}
|
|
} else {
|
|
this.x = this.windowedX;
|
|
this.y = this.windowedY;
|
|
this.width = this.windowedWidth;
|
|
this.height = this.windowedHeight;
|
|
GLFW.glfwSetWindowMonitor(this.window, 0L, this.x, this.y, this.width, this.height, -1);
|
|
}
|
|
}
|
|
|
|
public void toggleFullScreen() {
|
|
this.fullscreen = !this.fullscreen;
|
|
}
|
|
|
|
public void setWindowed(int windowedWidth, int windowedHeight) {
|
|
this.windowedWidth = windowedWidth;
|
|
this.windowedHeight = windowedHeight;
|
|
this.fullscreen = false;
|
|
this.setMode();
|
|
}
|
|
|
|
private void updateFullscreen(boolean vsyncEnabled, @Nullable TracyFrameCapture tracyFrameCapture) {
|
|
RenderSystem.assertOnRenderThread();
|
|
|
|
try {
|
|
this.setMode();
|
|
this.eventHandler.resizeDisplay();
|
|
this.updateVsync(vsyncEnabled);
|
|
this.updateDisplay(tracyFrameCapture);
|
|
} catch (Exception var4) {
|
|
LOGGER.error("Couldn't toggle fullscreen", (Throwable)var4);
|
|
}
|
|
}
|
|
|
|
public int calculateScale(int guiScale, boolean forceUnicode) {
|
|
int i = 1;
|
|
|
|
while (
|
|
i != guiScale
|
|
&& i < this.framebufferWidth
|
|
&& i < this.framebufferHeight
|
|
&& this.framebufferWidth / (i + 1) >= 320
|
|
&& this.framebufferHeight / (i + 1) >= 240
|
|
) {
|
|
i++;
|
|
}
|
|
|
|
if (forceUnicode && i % 2 != 0) {
|
|
i++;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
public void setGuiScale(double scaleFactor) {
|
|
this.guiScale = scaleFactor;
|
|
int i = (int)(this.framebufferWidth / scaleFactor);
|
|
this.guiScaledWidth = this.framebufferWidth / scaleFactor > i ? i + 1 : i;
|
|
int j = (int)(this.framebufferHeight / scaleFactor);
|
|
this.guiScaledHeight = this.framebufferHeight / scaleFactor > j ? j + 1 : j;
|
|
}
|
|
|
|
public void setTitle(String title) {
|
|
GLFW.glfwSetWindowTitle(this.window, title);
|
|
}
|
|
|
|
/**
|
|
* Gets a pointer to the native window object that is passed to GLFW.
|
|
*/
|
|
public long getWindow() {
|
|
return this.window;
|
|
}
|
|
|
|
public boolean isFullscreen() {
|
|
return this.fullscreen;
|
|
}
|
|
|
|
public boolean isIconified() {
|
|
return this.iconified;
|
|
}
|
|
|
|
public int getWidth() {
|
|
return this.framebufferWidth;
|
|
}
|
|
|
|
public int getHeight() {
|
|
return this.framebufferHeight;
|
|
}
|
|
|
|
public void setWidth(int framebufferWidth) {
|
|
this.framebufferWidth = framebufferWidth;
|
|
}
|
|
|
|
public void setHeight(int framebufferHeight) {
|
|
this.framebufferHeight = framebufferHeight;
|
|
}
|
|
|
|
public int getScreenWidth() {
|
|
return this.width;
|
|
}
|
|
|
|
public int getScreenHeight() {
|
|
return this.height;
|
|
}
|
|
|
|
public int getGuiScaledWidth() {
|
|
return this.guiScaledWidth;
|
|
}
|
|
|
|
public int getGuiScaledHeight() {
|
|
return this.guiScaledHeight;
|
|
}
|
|
|
|
public int getX() {
|
|
return this.x;
|
|
}
|
|
|
|
public int getY() {
|
|
return this.y;
|
|
}
|
|
|
|
public double getGuiScale() {
|
|
return this.guiScale;
|
|
}
|
|
|
|
@Nullable
|
|
public Monitor findBestMonitor() {
|
|
return this.screenManager.findBestMonitor(this);
|
|
}
|
|
|
|
public void updateRawMouseInput(boolean enableRawMouseMotion) {
|
|
InputConstants.updateRawMouseInput(this.window, enableRawMouseMotion);
|
|
}
|
|
|
|
public void setWindowCloseCallback(Runnable windowCloseCallback) {
|
|
GLFWWindowCloseCallback gLFWWindowCloseCallback = GLFW.glfwSetWindowCloseCallback(this.window, l -> windowCloseCallback.run());
|
|
if (gLFWWindowCloseCallback != null) {
|
|
gLFWWindowCloseCallback.free();
|
|
}
|
|
}
|
|
|
|
public boolean isMinimized() {
|
|
return this.minimized;
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public static class WindowInitFailed extends SilentInitException {
|
|
WindowInitFailed(String string) {
|
|
super(string);
|
|
}
|
|
}
|
|
}
|