diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystem.java b/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystem.java index 90e9692..3ad044c 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystem.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystem.java @@ -21,25 +21,30 @@ package de.staropensource.engine.rendering; import de.staropensource.engine.base.annotation.EngineSubsystem; import de.staropensource.engine.base.annotation.EventListener; +import de.staropensource.engine.base.implementable.Event; import de.staropensource.engine.base.implementable.SubsystemClass; import de.staropensource.engine.base.implementable.helper.EventHelper; import de.staropensource.engine.base.logging.Logger; -import de.staropensource.engine.base.type.EventPriority; -import de.staropensource.engine.base.utility.Math; +import de.staropensource.engine.base.utility.misc.NumberUtil; import de.staropensource.engine.base.utility.information.EngineInformation; import de.staropensource.engine.base.implementation.versioning.StarOpenSourceVersioningSystem; import de.staropensource.engine.base.event.InternalEngineShutdownEvent; import de.staropensource.engine.base.type.DependencyVector; -import de.staropensource.engine.base.utility.Miscellaneous; +import de.staropensource.engine.base.utility.misc.Miscellaneous; import de.staropensource.engine.rendering.event.InputEvent; import de.staropensource.engine.rendering.event.RenderingErrorEvent; import de.staropensource.engine.rendering.exception.NotOnMainThreadException; +import de.staropensource.engine.rendering.renderer.Renderer; import de.staropensource.engine.rendering.type.Window; +import de.staropensource.engine.rendering.type.window.RenderingPlatform; import de.staropensource.engine.rendering.type.window.VsyncMode; import lombok.Getter; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.*; +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicReference; @@ -117,8 +122,13 @@ public final class RenderingSubsystem extends SubsystemClass { // Precompute event listeners cacheEvents(); + + // Initialize GLFW initGlfw(); + // Initialize renderer + Renderer.initialize(); + // Warn about subsystem and API instability Logger.warn("The rendering subsystem is experimental. Subsystem and API stability are not guaranteed."); } @@ -150,10 +160,10 @@ public final class RenderingSubsystem extends SubsystemClass { Logger.diag("Setting initialization hints"); switch (RenderingSubsystemConfiguration.getInstance().getInitialPlatform()) { case ANY -> glfwInitHint(GLFW_PLATFORM, GLFW_ANY_PLATFORM); - case WAYLAND -> tryPlatform(GLFW_PLATFORM_WAYLAND); - case X11 -> tryPlatform(GLFW_PLATFORM_X11); - case WIN32 -> tryPlatform(GLFW_PLATFORM_WIN32); - case COCOA -> tryPlatform(GLFW_PLATFORM_COCOA); + case WAYLAND -> tryPlatform(GLFW_PLATFORM_WAYLAND, RenderingPlatform.WAYLAND); + case X11 -> tryPlatform(GLFW_PLATFORM_X11, RenderingPlatform.X11); + case WIN32 -> tryPlatform(GLFW_PLATFORM_WIN32, RenderingPlatform.WIN32); + case COCOA -> tryPlatform(GLFW_PLATFORM_COCOA, RenderingPlatform.COCOA); case NONE -> glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_NULL); } glfwInitHint(GLFW_WAYLAND_LIBDECOR, RenderingSubsystemConfiguration.getInstance().isInitialDisableLibdecor() ? GLFW_WAYLAND_DISABLE_LIBDECOR : GLFW_WAYLAND_PREFER_LIBDECOR); @@ -182,7 +192,7 @@ public final class RenderingSubsystem extends SubsystemClass { long shutdownTime = Miscellaneous.measureExecutionTime(() -> { // Close all windows - for (Window window : Window.getWindows()) + for (Window window : new ArrayList<>(Window.getWindows())) window.close(); instance.errorCallback.free(); @@ -198,7 +208,7 @@ public final class RenderingSubsystem extends SubsystemClass { * @see RenderingSubsystemConfiguration#errorRenderingFailures * @since v1-alpha9 */ - @EventListener(event = RenderingErrorEvent.class, priority = EventPriority.EXCLUSIVELY_IMPORTANT) + @EventListener(event = RenderingErrorEvent.class, priority = Event.Priority.EXCLUSIVELY_IMPORTANT) private static void logRenderingError(@NotNull String error) { Logger.error("Rendering error occurred: " + error); } @@ -220,6 +230,9 @@ public final class RenderingSubsystem extends SubsystemClass { LinkedHashMap<@NotNull Window, @NotNull Throwable> throwables = new LinkedHashMap<>(); + // Poll for events + glfwPollEvents(); + // Update and render all windows for (Window window : Window.getWindows()) { if (!window.isRendering()) @@ -232,14 +245,14 @@ public final class RenderingSubsystem extends SubsystemClass { Logger.error("Rendering window " + window + " failed: Threw throwable " + throwable.getClass().getName() + (throwable.getMessage() == null ? "" : ": " + throwable.getMessage())); throwables.put(window, throwable); } - } - // Poll for events - glfwPollEvents(); + //bgfx_frame(false); + } return throwables; } /** + * * Renders all windows continuously. * To render all windows just once, invoke * {@link #renderWindows()} instead. @@ -270,6 +283,8 @@ public final class RenderingSubsystem extends SubsystemClass { if (!RenderingSubsystemConfiguration.getInstance().isDebugFrames()) reportDuration = Long.MAX_VALUE; + Logger.info("Entering render loop"); + // Run while the 'output' is empty while (output.get().isEmpty()) { renderTime = Miscellaneous.measureExecutionTime(() -> { @@ -293,11 +308,33 @@ public final class RenderingSubsystem extends SubsystemClass { Thread.onSpinWait(); } + // Calculate delta time and frame count every second if (System.currentTimeMillis() >= reportDuration) { - deltaTime = Math.getMeanLong(splitDeltaTime); // Calculate delta time + deltaTime = NumberUtil.calculateMeanLong(splitDeltaTime); // Calculate delta time Logger.diag("Delta time average: " + deltaTime + " | Frames/s: " + 1000 / deltaTime); // Print delta time and frame count to console + if (RenderingSubsystemConfiguration.getInstance().isDebugWindowStates()) + for (Window window : Window.getWindows()) + Logger.diag( + "Window state for " + window.getUniqueIdentifier() + "\n" + + "-> Terminated: " + window.isTerminated() + "\n" + + "-> Name: " + window.getName() + "\n" + + "-> Title: " + window.getTitle() + "\n" + + "-> Size: " + window.getSize() + "\n" + + " -> Minimum: " + window.getMinimumSize() + "\n" + + " -> Maximum: " + window.getMaximumSize() + "\n" + + "-> Position: " + window.getPosition() + "\n" + + "-> Mode: " + window.getMode() + "\n" + + "-> Monitor: " + window.getMonitor() + "\n" + + "-> Resizable: " + window.isResizable() + "\n" + + "-> Borderless: " + window.isBorderless() + "\n" + + "-> Focusable: " + window.isFocusable() + "\n" + + "-> On top: " + window.isOnTop() + "\n" + + "-> Transparent: " + window.isTransparent() + "\n" + + "-> Rendering: " + window.isRendering() + ); + reportDuration = System.currentTimeMillis() + 1000; // Update 'reportDuration' splitDeltaTime.clear(); // Clear 'splitDeltaTime' list } @@ -312,12 +349,21 @@ public final class RenderingSubsystem extends SubsystemClass { * and if so, specifies it as the platform to use. * * @param platform platform to try + * @param renderingPlatform {@link RenderingPlatform} used to log that the platform is unsupported (set to {@code null} to disable) * @since v1-alpha9 */ - private void tryPlatform(int platform) { + private void tryPlatform(int platform, @Nullable RenderingPlatform renderingPlatform) { if (glfwPlatformSupported(platform)) - glfwInitHint(GLFW_PLATFORM, platform); - else + if (platform != GLFW_PLATFORM_WAYLAND) + glfwInitHint(GLFW_PLATFORM, platform); + else { + Logger.warn("Wayland is not supported by the StarOpenSource Engine due to various issues with it, sorry."); + tryPlatform(GLFW_PLATFORM_X11, RenderingPlatform.X11); + } + else { + if (renderingPlatform != null) + Logger.warn("Platform RenderingPlatform." + renderingPlatform.name() + " is not supported GLFW. Using RenderingPlatform.ANY instead"); glfwInitHint(GLFW_PLATFORM, GLFW_ANY_PLATFORM); + } } } diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystemConfiguration.java b/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystemConfiguration.java index 32ed27c..9d02290 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystemConfiguration.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/RenderingSubsystemConfiguration.java @@ -23,6 +23,7 @@ import de.staropensource.engine.base.implementable.Configuration; import de.staropensource.engine.base.logging.Logger; import de.staropensource.engine.base.utility.PropertiesReader; import de.staropensource.engine.rendering.event.RenderingErrorEvent; +import de.staropensource.engine.rendering.type.window.RenderingAdapter; import de.staropensource.engine.rendering.type.window.RenderingPlatform; import de.staropensource.engine.rendering.type.window.VsyncMode; import lombok.Getter; @@ -97,24 +98,48 @@ public final class RenderingSubsystemConfiguration extends Configuration { * Contains whether or not the delta time and * FPS count should be logged to the console * every second. - *

- * Changes will no longer be picked up as - * soon as the rendering loop is running. * * @since v1-alpha9 * -- GETTER -- * Returns whether or not the delta time and * FPS count should be logged to the console * every second. - *

- * Changes will no longer be picked up as - * soon as the rendering loop is running. * * @return print delta time and FPS count? * @since v1-alpha9 */ private boolean debugFrames; + /** + * Contains if to log the state of all + * windows every second. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns if to log the state of all + * windows every second. + * + * @return log states of all windows? + * @since v1-alpha9 + */ + private boolean debugWindowStates; + + /** + * Contains if to allow updates to a window's + * position. May cause errors and crashes to + * appear, we do not know why. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns if to allow updates to a window's + * position. May cause errors and crashes to + * appear, we do not know why. + * + * @return allow window position updates? + * @since v1-alpha9 + */ + private boolean debugAllowPositionUpdates; + /** * Contains the platform GLFW shall try initialising. @@ -172,6 +197,18 @@ public final class RenderingSubsystemConfiguration extends Configuration { private boolean errorRenderingFailures; + /** + * Contains the adapter bgfx shall use. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns the adapter bgfx shall use. + * + * @return bgfx adapter + * @since v1-alpha9 + */ + private RenderingAdapter renderingAdapter; + /** * Contains which {@link VsyncMode} to use. *

@@ -241,18 +278,27 @@ public final class RenderingSubsystemConfiguration extends Configuration { case "debug" -> debug = parser.getBoolean(group + property); case "debugInput" -> debugInput = parser.getBoolean(group + property); case "debugFrames" -> debugFrames = parser.getBoolean(group + property); + case "debugWindowStates" -> debugWindowStates = parser.getBoolean(group + property); + case "debugAllowPositionUpdates" -> debugAllowPositionUpdates = parser.getBoolean(group + property); case "initialPlatform" -> { try { initialPlatform = RenderingPlatform.valueOf(parser.getString(group + property).toUpperCase()); } catch (IllegalArgumentException ignored) { - Logger.error("Platform " + parser.getString(group + property) + " is not valid"); + Logger.error("Rendering platform " + parser.getString(group + property) + " is not valid"); } } case "initialDisableLibdecor" -> initialDisableLibdecor = parser.getBoolean(group + property); case "errorRenderingFailures" -> errorRenderingFailures = parser.getBoolean(group + property); + case "renderingAdapter" -> { + try { + renderingAdapter = RenderingAdapter.valueOf(parser.getString(group + property).toUpperCase()); + } catch (IllegalArgumentException exception) { + Logger.error("Rendering adapter " + parser.getString(group + property) + " is not valid"); + } + } case "vsyncMode" -> { try { vsyncMode = VsyncMode.valueOf(parser.getString(group + property).toUpperCase()); @@ -271,21 +317,26 @@ public final class RenderingSubsystemConfiguration extends Configuration { if (!debug) { debugInput = false; debugFrames = false; + debugWindowStates = false; + debugAllowPositionUpdates = false; } } /** {@inheritDoc} */ @Override public void loadDefaultConfiguration() { - debug = false; + debug = true; debugInput = false; - debugFrames = false; + debugFrames = true; + debugWindowStates = true; + debugAllowPositionUpdates = true; initialPlatform = RenderingPlatform.ANY; initialDisableLibdecor = false; errorRenderingFailures = true; + renderingAdapter = RenderingAdapter.ANY; vsyncMode = VsyncMode.ON; maximumFramesPerSecond = 60; } @@ -297,12 +348,15 @@ public final class RenderingSubsystemConfiguration extends Configuration { case "debug" -> { return debug; } case "debugInput" -> { return debugInput; } case "debugFrames" -> { return debugFrames; } + case "debugWindowStates" -> { return debugWindowStates; } + case "debugAllowPositionUpdates" -> { return debugAllowPositionUpdates; } case "initialPlatform" -> { return initialPlatform; } case "disableLibdecor" -> { return initialDisableLibdecor; } case "errorRenderingFailures" -> { return errorRenderingFailures; } + case "renderingAdapter" -> { return renderingAdapter; } case "vsyncMode" -> { return vsyncMode; } case "maximumFramesPerSecond" -> { return maximumFramesPerSecond; } default -> { return null; } diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/callback/KeyCallback.java b/rendering/src/main/java/de/staropensource/engine/rendering/callback/KeyCallback.java index 28fe6aa..985161d 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/callback/KeyCallback.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/callback/KeyCallback.java @@ -21,7 +21,6 @@ package de.staropensource.engine.rendering.callback; import de.staropensource.engine.rendering.type.Window; import de.staropensource.engine.rendering.event.InputEvent; -import de.staropensource.engine.rendering.callback.WindowCallback; import de.staropensource.engine.rendering.type.input.Key; import de.staropensource.engine.rendering.type.input.KeyState; import org.jetbrains.annotations.NotNull; diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/event/RenderingThreadThrowableEvent.java b/rendering/src/main/java/de/staropensource/engine/rendering/event/RenderingThreadThrowableEvent.java new file mode 100644 index 0000000..3ca7e8b --- /dev/null +++ b/rendering/src/main/java/de/staropensource/engine/rendering/event/RenderingThreadThrowableEvent.java @@ -0,0 +1,58 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.rendering.event; + +import de.staropensource.engine.base.implementable.Event; +import de.staropensource.engine.base.implementable.helper.EventHelper; +import org.jetbrains.annotations.NotNull; + +/** + * Emitted when the rendering thread exits due to an unhandled exception. + * + * @since v1-alpha9 + */ +public final class RenderingThreadThrowableEvent implements Event { + /** + * Creates and initializes an instance of this event. + * + * @since v1-alpha9 + */ + public RenderingThreadThrowableEvent() {} + + /** + * {@inheritDoc} + * + * @deprecated use the {@code callEvent} method with arguments + * @see #callEvent(Throwable) + */ + @Deprecated + @Override + public void callEvent() {} + + /** + * Emits the event and calls all event listeners. + * + * @param throwable thrown {@link Throwable} + * @since v1-alpha9 + */ + public void callEvent(@NotNull Throwable throwable) { + EventHelper.invokeAnnotatedMethods(getClass(), throwable); + } +} diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/renderer/Renderer.java b/rendering/src/main/java/de/staropensource/engine/rendering/renderer/Renderer.java new file mode 100644 index 0000000..3c93872 --- /dev/null +++ b/rendering/src/main/java/de/staropensource/engine/rendering/renderer/Renderer.java @@ -0,0 +1,379 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.rendering.renderer; + +import de.staropensource.engine.base.logging.Logger; +import de.staropensource.engine.base.utility.misc.Miscellaneous; +import de.staropensource.engine.base.utility.misc.NumberUtil; +import de.staropensource.engine.rendering.RenderingSubsystemConfiguration; +import de.staropensource.engine.rendering.exception.NotOnMainThreadException; +import de.staropensource.engine.rendering.type.FrameHandler; +import de.staropensource.engine.rendering.type.Monitor; +import de.staropensource.engine.rendering.type.Window; +import de.staropensource.engine.rendering.type.window.VsyncMode; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.time.LocalTime; +import java.util.*; + +import static org.lwjgl.bgfx.BGFX.*; +import static org.lwjgl.glfw.GLFW.*; + +/** + * Renders all windows out. + * + * @since v1-alpha9 + */ +@SuppressWarnings({ "JavadocDeclaration" }) +public final class Renderer { + /** + * Contains all frame handlers. + *

+ * Frame handlers are invoked before + * all windows are rendered, allowing + * the application or game to respond + * to frame renders. + * + * @since v1-alpha9 + */ + private static final @NotNull List<@NotNull FrameHandler> frameHandlers = Collections.synchronizedList(new ArrayList<>()); + + /** + * Contains if the renderer has been initialized. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns if the renderer has been initialized. + * + * @return renderer initialized? + * @since v1-alpha9 + */ + @Getter + private static boolean initialized = false; + + /** + * Contains if the renderer is running. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns if the renderer is running. + * + * @return renderer running? + * @since v1-alpha9 + */ + @Getter + private static boolean running = false; + + /** + * Contains the frame count aka. + * the amount of frames rendered. + *

+ * Updated every frame. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns the frame count aka. + * the amount of frames rendered. + *

+ * Updated every frame. + * + * @return amount of frames rendered + * @since v1-alpha9 + */ + @Getter + private static long frameCount = 0L; + + /** + * Contains the delta time, also + * known as the render time. + *

+ * Updated every second. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns the delta time, also + * known as the render time. + *

+ * Updated every second. + * + * @return delta time + * @since v1-alpha9 + */ + @Getter + private static double deltaTime = 0d; + + /** + * Contains the frames per second (FPS) count. + *

+ * Updated every second. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns the frames per second (FPS) count. + *

+ * Updated every second. + * + * @return frames per second + * @since v1-alpha9 + */ + @Getter + private static double framesPerSecond = 0d; + + /** + * Contains the time it took + * to calculate the last frame. + *

+ * Updated every frame. + * + * @since v1-alpha9 + * -- GETTER -- + * Contains the time it took + * to calculate the last frame. + *

+ * Updated every frame. + * + * @return last frame time + * @since v1-alpha9 + */ + @Getter + private static Map<@NotNull String, @NotNull Long> lastFrameTime = Collections.unmodifiableMap(new HashMap<>()); + + + // ----> Initialization + /** + * Initializes the renderer. + * + * @since v1-alpha9 + */ + public static void initialize() { + if (initialized) + return; + + addFrameHandler(new FrameHandler() { + @Override + public @NotNull FrameHandler.Priority getPriority() { + return Priority.VERY_IMPORTANT; + } + + @Override + public void run() { + bgfx_set_view_clear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000, 1.0f, 0); + } + }); + addFrameHandler(new FrameHandler() { + /** {@inheritDoc} */ + @Override + public @NotNull FrameHandler.Priority getPriority() { + return Priority.VERY_UNIMPORTANT; + } + + /** {@inheritDoc} */ + @Override + public void run() { + int offset = 2; + + bgfx_dbg_text_clear(0, false); + bgfx_dbg_text_printf(0, 0, 0x0f, "It's " + NumberUtil.padNumbers(LocalTime.now().getHour(), 2) + ":" + NumberUtil.padNumbers(LocalTime.now().getMinute(), 2) + ":" + NumberUtil.padNumbers(LocalTime.now().getSecond(), 2)); + bgfx_dbg_text_printf(0, 1, 0x0f, "LFT.size(): " + lastFrameTime.size()); + for (String item : lastFrameTime.keySet()) { + bgfx_dbg_text_printf(0, offset, 0x0f, item + ": " + lastFrameTime.get(item) + "ms"); + offset += 1; + } + } + }); + + initialized = true; + } + + + // -----> Frame handler management + /** + * Adds the specified frame handler. + * + * @param frameHandler {@link FrameHandler} to add + * @since v1-alpha9 + */ + public static void addFrameHandler(@NotNull FrameHandler frameHandler) { + frameHandlers.add(frameHandler); + frameHandlers.sort(Comparator.comparing(FrameHandler::getPriority)); + } + + /** + * Removes the specified frame handler. + * + * @param frameHandler {@link FrameHandler} to remove + * @since v1-alpha9 + */ + public static void removeFrameHandler(@NotNull FrameHandler frameHandler) { + frameHandlers.remove(frameHandler); + } + + + // -----> Rendering logic + /** + * Starts the renderer. + * + * @throws NotOnMainThreadException if not running on the main thread + * @throws RuntimeException on major rendering error + * @since v1-alpha9 + */ + public static void start() throws RuntimeException { + // Check if running on main thread + if (!Miscellaneous.onMainThread()) + throw new NotOnMainThreadException(); + + String threadName = Thread.currentThread().getName(); + int threadPriority = Thread.currentThread().getPriority(); + RuntimeException exception = null; + + // Update thread + Thread.currentThread().setName("Rendering thread"); + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + + // Start renderer + running = true; + try { + render(); + } catch (Throwable throwable) { + exception = new RuntimeException("Renderer failed", throwable); + } + + // Revert thread changes + Thread.currentThread().setName(threadName); + Thread.currentThread().setPriority(threadPriority); + + // Throw if necessary + if (exception != null) + throw exception; + } + + /** + * Renders all windows. + * + * @since v1-alpha9 + */ + @SuppressWarnings({ "InfiniteLoopStatement" }) + private static void render() throws Throwable { + long previousFrameCount = 0L; // Frame count one second ago + LinkedList deltaTimeFractions = new LinkedList<>(); // Contains all delta time fractions + Map execTimes = new LinkedHashMap<>(); // Contains the amount of time of all rendering operations + long timesWait; // Time to wait until the next frame + long timesPSO = System.currentTimeMillis() + 1000; // Time to wait until invoking per-second operations + + while (true) { + // Invoke frame handlers + for (FrameHandler frameHandler : frameHandlers) + execTimes.put("Frame handler '" + frameHandler.getClass().getName() + "'", Miscellaneous.measureExecutionTime(frameHandler::run)); + + + // Perform rendering + execTimes.put("Rendering", Miscellaneous.measureExecutionTime(() -> { + // Poll for events + glfwPollEvents(); + + // Reset backbuffer + bgfx_reset(Window.getWindows().getFirst().getSize().getX(), Window.getWindows().getFirst().getSize().getY(), RenderingSubsystemConfiguration.getInstance().getVsyncMode() == VsyncMode.ON ? BGFX_RESET_VSYNC : BGFX_RESET_NONE, BGFX_TEXTURE_FORMAT_RGBA4); + + // Render all windows + for (Window window : Window.getWindows()) + if (window.isRendering()) { + window.updateState(); + window.render(); + } + + // Advance to next frame + bgfx_frame(false); + })); + + + // Determine time to wait for the next frame + execTimes.put("Waiting", 0L); + switch (RenderingSubsystemConfiguration.getInstance().getVsyncMode()) { + case OFF -> execTimes.replace("Waiting", (long) (1d / RenderingSubsystemConfiguration.getInstance().getMaximumFramesPerSecond() * 1000d)); + case ON -> { + for (Monitor monitor : Monitor.getMonitors()) + if (monitor.getRefreshRate() > execTimes.get("Waiting")) + execTimes.replace("Waiting", (long) monitor.getRefreshRate()); + } + default -> {} + } + for (String time : execTimes.keySet()) + if (!time.equals("Waiting")) + execTimes.replace("Waiting", execTimes.get("Waiting") - execTimes.get(time)); + + // Wait until next frame + if (execTimes.get("Waiting") > 0) { + timesWait = execTimes.get("Waiting") + System.currentTimeMillis(); + + while (System.currentTimeMillis() < timesWait) + Thread.onSpinWait(); + } + + + // Perform per-frame operations + frameCount += 1; + lastFrameTime = new HashMap<>(execTimes); + deltaTimeFractions.add(execTimes.get("Rendering") + execTimes.get("Waiting")); + execTimes.clear(); + + + // Perform per-second operations + if (System.currentTimeMillis() >= timesPSO) { + // Calculate delta time and FPS count + deltaTime = NumberUtil.calculateMeanLong(deltaTimeFractions); + framesPerSecond = 1000 / deltaTime; + + // Log frame count + if (RenderingSubsystemConfiguration.getInstance().isDebugFrames()) + Logger.diag("Frames " + previousFrameCount + "-" + frameCount + "\n-> Frames/s: " + framesPerSecond + "\n-> Delta time: " + deltaTime); + + // Log window states + if (RenderingSubsystemConfiguration.getInstance().isDebugWindowStates()) + for (Window window : Window.getWindows()) + Logger.diag( + "Window state for " + window.getUniqueIdentifier() + "\n" + + "-> Terminated: " + window.isTerminated() + "\n" + + "-> Name: " + window.getName() + "\n" + + "-> Title: " + window.getTitle() + "\n" + + "-> Size: " + window.getSize() + "\n" + + " -> Minimum: " + window.getMinimumSize() + "\n" + + " -> Maximum: " + window.getMaximumSize() + "\n" + + "-> Position: " + window.getPosition() + "\n" + + "-> Mode: " + window.getMode() + "\n" + + "-> Monitor: " + window.getMonitor() + "\n" + + "-> Resizable: " + window.isResizable() + "\n" + + "-> Borderless: " + window.isBorderless() + "\n" + + "-> Focusable: " + window.isFocusable() + "\n" + + "-> On top: " + window.isOnTop() + "\n" + + "-> Transparent: " + window.isTransparent() + "\n" + + "-> Rendering: " + window.isRendering() + ); + + + + // Reset per-second variables + previousFrameCount = frameCount; + deltaTimeFractions.clear(); + timesPSO = System.currentTimeMillis() + 1000; + } + } + } +} diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/type/FrameHandler.java b/rendering/src/main/java/de/staropensource/engine/rendering/type/FrameHandler.java new file mode 100644 index 0000000..a0d834e --- /dev/null +++ b/rendering/src/main/java/de/staropensource/engine/rendering/type/FrameHandler.java @@ -0,0 +1,93 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.rendering.type; + +import org.jetbrains.annotations.NotNull; + +/** + * Used for performing actions + * before a frame is rendered. + * + * @since v1-alpha9 + */ +public interface FrameHandler { + /** + * Returns the priority this frame handler has. + * + * @return priority + * @since v1-alpha9 + */ + @NotNull FrameHandler.Priority getPriority(); + + /** + * Invokes this frame handler. + * + * @since v1-alpha9 + */ + void run(); + + /** + * Contains all priority levels + * a {@link FrameHandler} can have. + * + * @since v1-alpha9 + */ + enum Priority { + /** + * {@link FrameHandler}s with + * this priority are called first. + * + * @since v1-alpha9 + */ + VERY_IMPORTANT, + + /** + * {@link FrameHandler}s with + * this priority are called 2nd. + * + * @since v1-alpha9 + */ + IMPORTANT, + + /** + * {@link FrameHandler}s with + * this priority are called 3rd. + * + * @since v1-alpha9 + */ + DEFAULT, + + /** + * {@link FrameHandler}s with + * this priority are called 4th. + * + * @since v1-alpha9 + */ + UNIMPORTANT, + + /** + * {@link FrameHandler}s with + * this priority are called last. + * + * @since v1-alpha9 + */ + VERY_UNIMPORTANT, + } +} diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/type/Window.java b/rendering/src/main/java/de/staropensource/engine/rendering/type/Window.java index f89e553..acf4bb0 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/type/Window.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/type/Window.java @@ -22,19 +22,21 @@ package de.staropensource.engine.rendering.type; import de.staropensource.engine.base.logging.Logger; import de.staropensource.engine.base.type.Tristate; import de.staropensource.engine.base.type.vector.Vec2i; -import de.staropensource.engine.base.utility.Miscellaneous; +import de.staropensource.engine.base.utility.misc.Miscellaneous; +import de.staropensource.engine.base.utility.misc.TypeConversion; +import de.staropensource.engine.rendering.RenderingSubsystemConfiguration; import de.staropensource.engine.rendering.callback.KeyCallback; import de.staropensource.engine.rendering.callback.MouseButtonCallback; import de.staropensource.engine.rendering.event.InputEvent; import de.staropensource.engine.rendering.exception.NotOnMainThreadException; import de.staropensource.engine.rendering.exception.WindowCreationFailureException; -import de.staropensource.engine.rendering.type.window.WindowMode; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.lwjgl.bgfx.BGFX; import org.lwjgl.bgfx.BGFXInit; import org.lwjgl.glfw.*; import org.lwjgl.stb.STBImage; @@ -60,7 +62,7 @@ import static org.lwjgl.system.MemoryUtil.*; * @since v1-alpha9 */ @Getter -@SuppressWarnings({ "JavadocDeclaration" }) +@SuppressWarnings({ "unused", "JavadocDeclaration" }) public final class Window implements AutoCloseable { /** * A set of all active windows. @@ -69,18 +71,7 @@ public final class Window implements AutoCloseable { */ private static final @NotNull List<@NotNull Window> windows = new ArrayList<>(); - /** - * Contains the internal window identifier. - * - * @since v1-alpha9 - * -- GETTER -- - * Returns the internal window identifier. - * - * @return window identifier - * @since v1-alpha9 - */ - private long identifier; - + // -----> Internal fields /** * Contains if this window is fresh. *

@@ -99,8 +90,39 @@ public final class Window implements AutoCloseable { * @return fresh flag state * @since v1-alpha9 */ - private boolean fresh = true; + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private boolean fresh; + /** + * Contains the internal GLFW window identifier. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns the internal GLFW window identifier. + * + * @return GLFW window identifier + * @since v1-alpha9 + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private long internalWindowIdentifier; + + /** + * Contains the internal bgfx view identifier. + * + * @since v1-alpha9 + * -- GETTER -- + * Returns the internal bgfx view identifier. + * + * @return bgfx view identifier + * @since v1-alpha9 + */ + @Getter(value = AccessLevel.NONE) + @Setter(value = AccessLevel.NONE) + private int internalViewIdentifier; + + // -----> Window properties /** * Contains if this window can be interacted with or not. * @@ -258,7 +280,7 @@ public final class Window implements AutoCloseable { private @NotNull Vec2i position; /** - * Contains in which {@link WindowMode} this window is in. + * Contains in which {@link Mode} this window is in. * * @since v1-alpha9 * -- GETTER -- @@ -267,7 +289,7 @@ public final class Window implements AutoCloseable { * @return window mode * @since v1-alpha9 */ - private @NotNull WindowMode windowMode; + private @NotNull Window.Mode mode; /** * Contains on which {@link Monitor} the window is displayed on. @@ -287,24 +309,6 @@ public final class Window implements AutoCloseable { @Setter private @NotNull Monitor monitor; - /** - * Contains how fast the window may update it's contents. - * - * @since v1-alpha9 - * -- GETTER -- - * Returns how fast the window may update it's contents. - * - * @return new window frame limit - * @since v1-alpha9 - * -- SETTER -- - * Sets how fast the window may update it's contents. - * - * @param framerate new frame limit - * @since v1-alpha9 - */ - @Setter - private int framerate; - /** * Contains if this window can be resized by the user. * @@ -403,16 +407,20 @@ public final class Window implements AutoCloseable { */ private GLFWMouseButtonCallback mouseButtonCallback; + + // -----> Static methods /** * Returns a set of active windows. * * @return active windows * @since v1-alpha9 */ - public static @NotNull HashSet<@NotNull Window> getWindows() { - return new HashSet<>(windows); + public static @NotNull List<@NotNull Window> getWindows() { + return Collections.unmodifiableList(windows); } + + // -----> Initialization /** * Creates and initializes an instance of this abstract class. * @@ -423,7 +431,7 @@ public final class Window implements AutoCloseable { * @param minimumSize minimum size * @param maximumSize maximum size * @param position position - * @param windowMode window mode + * @param mode window mode * @param monitor monitor * @param resizable resizable flag * @param borderless borderless flag @@ -441,7 +449,7 @@ public final class Window implements AutoCloseable { @NotNull Vec2i minimumSize, @NotNull Vec2i maximumSize, @NotNull Vec2i position, - @NotNull WindowMode windowMode, + @NotNull Window.Mode mode, @NotNull Monitor monitor, boolean resizable, boolean borderless, @@ -450,7 +458,11 @@ public final class Window implements AutoCloseable { boolean transparent, boolean rendering) throws NotOnMainThreadException { // Initialize variables + this.fresh = true; + this.internalWindowIdentifier = 0L; + this.internalViewIdentifier = 0; this.uniqueIdentifier = UUID.randomUUID(); + this.terminated = false; this.name = name; this.title = title; this.icons = icons; @@ -458,7 +470,7 @@ public final class Window implements AutoCloseable { this.minimumSize = minimumSize; this.maximumSize = maximumSize; this.position = position; - this.windowMode = windowMode; + this.mode = mode; this.monitor = monitor; this.resizable = resizable; this.borderless = borderless; @@ -468,25 +480,45 @@ public final class Window implements AutoCloseable { this.rendering = rendering; // Log about window creation - Logger.diag("Creating new window with properties: uniqueIdentifier=" + uniqueIdentifier + " name=\"" + name + "\" title=\"" + title + "\" icons=" + Arrays.toString(icons) + " size=" + size + " minimumSize=" + minimumSize + " maximumSize=" + maximumSize + " position=" + position + " windowMode=" + windowMode + " monitor=" + monitor.getUniqueIdentifier() + " (" + monitor.getName() + ") resizable=" + resizable + " borderless=" + borderless + " focusable=" + focusable + " onTop=" + onTop + " transparent=" + transparent + " rendering=" + rendering); + Logger.verb("Creating new window " + this.uniqueIdentifier + "\n" + + "-> Name: " + this.name + "\n" + + "-> Title: " + this.title + "\n" + + "-> Size: " + this.size + "\n" + + " -> Minimum: " + this.minimumSize + "\n" + + " -> Maximum: " + this.maximumSize + "\n" + + "-> Position: " + this.position + "\n" + + "-> Mode: " + this.mode + "\n" + + "-> Monitor: " + this.monitor + "\n" + + "-> Resizable: " + this.resizable + "\n" + + "-> Borderless: " + this.borderless + "\n" + + "-> Focusable: " + this.focusable + "\n" + + "-> On top: " + this.onTop + "\n" + + "-> Transparent: " + this.transparent + "\n" + + "-> Rendering: " + this.rendering + ); - // Allow windowing API to initialize window + // Check if another window already exists + if (!Window.getWindows().isEmpty()) + Logger.crash("Window already present\nThe StarOpenSource Engine is unable to initialize more than one window at the moment, sorry."); + + // Initialize window initializeWindow(); // Update state updateState(); + // Initialize bgfx + if (glfwGetPlatform() != GLFW_PLATFORM_NULL) + initBgfx(); + // Add to window set windows.add(this); fresh = false; } + /** - * Allows the windowing API to initialize the window. - *

- * NEVER place any code in the constructor. Instead, write - * API-specific window initialization code in here - * or stuff may break unexpectedly. + * Initializes this window. * * @throws NotOnMainThreadException if not running on the main thread * @since v1-alpha9 @@ -504,12 +536,13 @@ public final class Window implements AutoCloseable { } // Set window hints - glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // The window's visibility is later changed in setWindowState, this is just for setting up the window - glfwWindowHint(GLFW_POSITION_X, getPosition().getX()); - glfwWindowHint(GLFW_POSITION_Y, getPosition().getY()); + if (RenderingSubsystemConfiguration.getInstance().isDebugAllowPositionUpdates()) { + glfwWindowHint(GLFW_POSITION_X, getPosition().getX()); + glfwWindowHint(GLFW_POSITION_Y, getPosition().getY()); + } glfwWindowHint(GLFW_CENTER_CURSOR, 0); - glfwWindowHint(GLFW_FOCUSED, Miscellaneous.getIntegerizedBoolean(focused)); - glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, Miscellaneous.getIntegerizedBoolean(isTransparent())); + glfwWindowHint(GLFW_FOCUSED, TypeConversion.booleanToInteger(focused)); + glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, TypeConversion.booleanToInteger(transparent)); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); glfwWindowHintString(GLFW_WAYLAND_APP_ID, getName()); @@ -517,8 +550,8 @@ public final class Window implements AutoCloseable { glfwWindowHintString(GLFW_X11_INSTANCE_NAME, getName()); // Create window - identifier = glfwCreateWindow(getSize().getX(), getSize().getY(), getTitle(), NULL, NULL); - if (identifier == NULL) + internalWindowIdentifier = glfwCreateWindow(getSize().getX(), getSize().getY(), getTitle(), NULL, NULL); + if (internalWindowIdentifier == NULL) throw new WindowCreationFailureException(); // Own context @@ -532,18 +565,15 @@ public final class Window implements AutoCloseable { mouseButtonCallback = GLFWMouseButtonCallback.create(new MouseButtonCallback(this)); // Set callback - glfwSetKeyCallback(identifier, keyCallback); - glfwSetMouseButtonCallback(identifier, mouseButtonCallback); + glfwSetKeyCallback(internalWindowIdentifier, keyCallback); + glfwSetMouseButtonCallback(internalWindowIdentifier, mouseButtonCallback); // Update the window state setIcons(getIcons()); setSize(getSize()); setMinimumSize(getMinimumSize()); setMaximumSize(getMaximumSize()); - setWindowMode(getWindowMode()); - - // Initialize bgfx - initBgfx(); + setMode(getMode()); } /** @@ -552,6 +582,10 @@ public final class Window implements AutoCloseable { * @since v1-alpha9 */ private void initBgfx() { + // Ensure the window is not terminated + if (terminated) + return; + try (MemoryStack stack = stackPush()) { Logger.verb("Initializing bgfx"); @@ -560,13 +594,29 @@ public final class Window implements AutoCloseable { BGFXInit init = BGFXInit.calloc(stack); bgfx_init_ctor(init); + // Set adapter + Logger.diag("Setting adapter"); + init.vendorId(switch (RenderingSubsystemConfiguration.getInstance().getRenderingAdapter()) { + case ANY -> BGFX_PCI_ID_NONE; + case SOFTWARE -> BGFX_PCI_ID_SOFTWARE_RASTERIZER; + case AMD -> BGFX_PCI_ID_AMD; + case APPLE -> BGFX_PCI_ID_APPLE; + case INTEL -> BGFX_PCI_ID_INTEL; + case NVIDIA -> BGFX_PCI_ID_NVIDIA; + case MICROSOFT -> BGFX_PCI_ID_MICROSOFT; + }); + // Set initial resolution Logger.diag("Setting initial resolution"); init - .resolution(it -> it - .width(size.getX()) - .height(size.getY()) - .reset(BGFX_RESET_VSYNC)); + .resolution( + it -> it + .width(size.getX()) + .height(size.getY()) + .reset( + BGFX_RESET_VSYNC + ) + ); // Determine platform to render for Logger.diag("Setting platform"); @@ -574,18 +624,17 @@ public final class Window implements AutoCloseable { case GLFW_PLATFORM_X11 -> init .platformData() .ndt(GLFWNativeX11.glfwGetX11Display()) - .nwh(GLFWNativeX11.glfwGetX11Window(identifier)); + .nwh(GLFWNativeX11.glfwGetX11Window(internalWindowIdentifier)); case GLFW_PLATFORM_WAYLAND -> init .platformData() .ndt(GLFWNativeWayland.glfwGetWaylandDisplay()) - .nwh(GLFWNativeWayland.glfwGetWaylandWindow(identifier)); + .nwh(GLFWNativeWayland.glfwGetWaylandWindow(internalWindowIdentifier)); case GLFW_PLATFORM_WIN32 -> init .platformData() - .nwh(GLFWNativeWin32.glfwGetWin32Window(identifier)); + .nwh(GLFWNativeWin32.glfwGetWin32Window(internalWindowIdentifier)); case GLFW_PLATFORM_COCOA -> init .platformData() - .nwh(GLFWNativeCocoa.glfwGetCocoaWindow(identifier)); - case GLFW_PLATFORM_NULL -> {} + .nwh(GLFWNativeCocoa.glfwGetCocoaWindow(internalWindowIdentifier)); default -> Logger.crash("Invalid GLFW platform \"" + glfwGetPlatform() + "\""); } @@ -595,8 +644,6 @@ public final class Window implements AutoCloseable { Logger.crash("Unable to initialize bgfx"); bgfx_set_debug(BGFX_DEBUG_TEXT); - bgfx_set_view_rect(0, 0, 0, size.getX(), size.getY()); - bgfx_touch(0); } catch (UnsatisfiedLinkError error) { Logger.crash("Failed to load LWJGL native libraries", error); } @@ -613,7 +660,7 @@ public final class Window implements AutoCloseable { */ public void updateState() throws NotOnMainThreadException { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; // Ensure running on the main thread @@ -621,14 +668,14 @@ public final class Window implements AutoCloseable { throw new NotOnMainThreadException(); // Update window mode - if (Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_ICONIFIED))) - setWindowMode(WindowMode.MINIMIZED); - else if (Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_MAXIMIZED))) - setWindowMode(WindowMode.MAXIMIZED); - else if (Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_VISIBLE))) - setWindowMode(WindowMode.WINDOWED); - else if (Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_VISIBLE))) - setWindowMode(WindowMode.HIDDEN); + if (TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_ICONIFIED))) + mode = Mode.MINIMIZED; + else if (TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_MAXIMIZED))) + mode = Mode.MAXIMIZED; + else if (TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_VISIBLE))) + mode = Mode.WINDOWED; + else if (TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_VISIBLE))) + mode = Mode.HIDDEN; // Update monitor if (!getMonitor().isConnected()) { @@ -638,10 +685,12 @@ public final class Window implements AutoCloseable { if (monitor.isConnected()) newMonitor = monitor; - if (newMonitor == null) + if (newMonitor == null) { Logger.crash("Unable to set a new target monitor for window " + getUniqueIdentifier() + " as no monitors are connected to the system"); + return; + } - setMonitor(Objects.requireNonNull(newMonitor)); + monitor = newMonitor; } // Update vectors @@ -649,17 +698,17 @@ public final class Window implements AutoCloseable { IntBuffer width = stack.mallocInt(2); IntBuffer height = stack.mallocInt(2); - glfwGetWindowSize(identifier, width, height); - setSize(new Vec2i(width.get(), height.get())); + glfwGetWindowSize(internalWindowIdentifier, width, height); + size = new Vec2i(width.get(), height.get()); - glfwGetWindowPos(identifier, width, height); - setPosition(new Vec2i(width.get(), height.get())); + glfwGetWindowPos(internalWindowIdentifier, width, height); + position = new Vec2i(width.get(), height.get()); } // Update booleans - setResizable(Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_RESIZABLE))); - setOnTop(Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_FLOATING))); - setTransparent(Miscellaneous.getBooleanizedInteger(glfwGetWindowAttrib(identifier, GLFW_TRANSPARENT_FRAMEBUFFER))); + resizable = TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_RESIZABLE)); + onTop = TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_FLOATING)); + transparent = TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_TRANSPARENT_FRAMEBUFFER)); } /** @@ -672,18 +721,15 @@ public final class Window implements AutoCloseable { * @since v1-alpha9 */ public void render() throws NotOnMainThreadException { - // Ensure running on the main thread - if (!Miscellaneous.onMainThread()) - throw new NotOnMainThreadException(); - // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; + // Ensure rendering is enabled if (!isRendering()) return; - glfwSwapBuffers(identifier); + bgfx_set_view_rect(0, 0, 0, size.getX(), size.getY()); } /** @@ -696,8 +742,13 @@ public final class Window implements AutoCloseable { if (!Miscellaneous.onMainThread()) throw new NotOnMainThreadException(); - closeInternal(); + // Ensure the window is not terminated + if (terminated) + return; + + windows.remove(this); terminated = true; + closeInternal(); } /** @@ -709,11 +760,12 @@ public final class Window implements AutoCloseable { */ private void closeInternal() { // Terminate bgfx - bgfx_shutdown(); + if (glfwGetPlatform() != GLFW_PLATFORM_NULL) + bgfx_shutdown(); // Destroy the window - Callbacks.glfwFreeCallbacks(identifier); - glfwDestroyWindow(identifier); + Callbacks.glfwFreeCallbacks(internalWindowIdentifier); + glfwDestroyWindow(internalWindowIdentifier); } /** @@ -724,11 +776,15 @@ public final class Window implements AutoCloseable { * @since v1-alpha9 */ public boolean isClosureRequested() { + // Ensure running on the main thread + if (!Miscellaneous.onMainThread()) + throw new NotOnMainThreadException(); + // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return false; - return glfwWindowShouldClose(identifier); + return glfwWindowShouldClose(internalWindowIdentifier); } /** @@ -738,11 +794,15 @@ public final class Window implements AutoCloseable { * @since v1-alpha9 */ public boolean isFocused() { + // Ensure running on the main thread + if (!Miscellaneous.onMainThread()) + throw new NotOnMainThreadException(); + // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return false; - return Miscellaneous.getTristatedInteger(glfwGetWindowAttrib(identifier, GLFW_FOCUSED)).toBoolean(); + return TypeConversion.integerToBoolean(glfwGetWindowAttrib(internalWindowIdentifier, GLFW_FOCUSED)); } /** @@ -752,11 +812,15 @@ public final class Window implements AutoCloseable { * @since v1-alpha9 */ public void focus() { + // Ensure running on the main thread + if (!Miscellaneous.onMainThread()) + throw new NotOnMainThreadException(); + // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; - glfwFocusWindow(identifier); + glfwFocusWindow(internalWindowIdentifier); } /** @@ -770,11 +834,15 @@ public final class Window implements AutoCloseable { * @since v1-alpha9 */ public void requestAttention() { + // Ensure running on the main thread + if (!Miscellaneous.onMainThread()) + throw new NotOnMainThreadException(); + // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; - glfwRequestWindowAttention(identifier); + glfwRequestWindowAttention(internalWindowIdentifier); } // -----> Setters & getters @@ -801,7 +869,7 @@ public final class Window implements AutoCloseable { */ public void setName(@NotNull String name) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.name = name; @@ -816,11 +884,11 @@ public final class Window implements AutoCloseable { */ public void setTitle(@NotNull String title) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.title = title; - glfwSetWindowTitle(identifier, title); + glfwSetWindowTitle(internalWindowIdentifier, title); } /** @@ -833,7 +901,7 @@ public final class Window implements AutoCloseable { @ApiStatus.Experimental public void setIcons(@NotNull Path @Nullable [] icons) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.icons = icons; @@ -873,7 +941,7 @@ public final class Window implements AutoCloseable { iconsBuffer.position(0); Logger.diag("setting icons"); Logger.flush(); - glfwSetWindowIcon(identifier, iconsBuffer); + glfwSetWindowIcon(internalWindowIdentifier, iconsBuffer); // Free icons Logger.diag("freeing icons"); @@ -894,11 +962,11 @@ public final class Window implements AutoCloseable { */ public void setSize(@NotNull Vec2i size) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.size = size; - glfwSetWindowSize(identifier, size.getX(), size.getY()); + glfwSetWindowSize(internalWindowIdentifier, size.getX(), size.getY()); } /** @@ -911,11 +979,11 @@ public final class Window implements AutoCloseable { */ public void setMinimumSize(@NotNull Vec2i minimumSize) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.minimumSize = minimumSize; - glfwSetWindowSizeLimits(identifier, minimumSize.getX(), minimumSize.getY(), getMaximumSize().getX(), getMaximumSize().getY()); + glfwSetWindowSizeLimits(internalWindowIdentifier, minimumSize.getX(), minimumSize.getY(), getMaximumSize().getX(), getMaximumSize().getY()); } /** @@ -928,11 +996,11 @@ public final class Window implements AutoCloseable { */ public void setMaximumSize(@NotNull Vec2i maximumSize) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.maximumSize = maximumSize; - glfwSetWindowSizeLimits(identifier, getMinimumSize().getX(), getMinimumSize().getY(), maximumSize.getX(), maximumSize.getY()); + glfwSetWindowSizeLimits(internalWindowIdentifier, getMinimumSize().getX(), getMinimumSize().getY(), maximumSize.getX(), maximumSize.getY()); } /** @@ -943,49 +1011,50 @@ public final class Window implements AutoCloseable { */ public void setPosition(@NotNull Vec2i position) { // Ensure the window is not terminated - if (isTerminated()) + if (isTerminated() || !RenderingSubsystemConfiguration.getInstance().isDebugAllowPositionUpdates()) return; this.position = position; - glfwSetWindowSize(identifier, position.getX(), position.getY()); + glfwSetWindowSize(internalWindowIdentifier, position.getX(), position.getY()); } /** * Sets the window mode. * - * @param windowMode new mode + * @param mode new mode * @since v1-alpha9 */ - public void setWindowMode(@NotNull WindowMode windowMode) { + public void setMode(@NotNull Window.Mode mode) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; - this.windowMode = windowMode; - switch (windowMode) { - case HIDDEN -> glfwHideWindow(identifier); + this.mode = mode; + switch (mode) { + case HIDDEN -> glfwHideWindow(internalWindowIdentifier); case WINDOWED -> { - glfwShowWindow(identifier); - glfwRestoreWindow(identifier); + glfwShowWindow(internalWindowIdentifier); + glfwRestoreWindow(internalWindowIdentifier); } case MINIMIZED -> { - glfwShowWindow(identifier); - glfwIconifyWindow(identifier); + glfwShowWindow(internalWindowIdentifier); + glfwIconifyWindow(internalWindowIdentifier); } case MAXIMIZED -> { - glfwShowWindow(identifier); - glfwRestoreWindow(identifier); - glfwMaximizeWindow(identifier); + glfwShowWindow(internalWindowIdentifier); + glfwRestoreWindow(internalWindowIdentifier); + glfwMaximizeWindow(internalWindowIdentifier); } case BORDERLESS_FULLSCREEN -> { - glfwShowWindow(identifier); - glfwRestoreWindow(identifier); + glfwShowWindow(internalWindowIdentifier); + glfwRestoreWindow(internalWindowIdentifier); + glfwSetWindowPos(internalWindowIdentifier, 0, 0); // TODO } case EXCLUSIVE_FULLSCREEN -> { - glfwShowWindow(identifier); - glfwRestoreWindow(identifier); - // TODO + glfwShowWindow(internalWindowIdentifier); + glfwRestoreWindow(internalWindowIdentifier); + glfwSetWindowPos(internalWindowIdentifier, 0, 0); } } } @@ -998,7 +1067,7 @@ public final class Window implements AutoCloseable { */ public void setResizable(boolean resizable) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.resizable = resizable; @@ -1012,7 +1081,7 @@ public final class Window implements AutoCloseable { */ public void setBorderless(boolean borderless) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.borderless = borderless; @@ -1026,7 +1095,7 @@ public final class Window implements AutoCloseable { */ public void setFocusable(boolean focusable) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.focusable = focusable; @@ -1040,7 +1109,7 @@ public final class Window implements AutoCloseable { */ public void setOnTop(boolean onTop) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.onTop = onTop; @@ -1057,14 +1126,79 @@ public final class Window implements AutoCloseable { */ public void setTransparent(boolean transparent) { // Ensure the window is not terminated - if (isTerminated()) + if (terminated) return; this.transparent = transparent; initializeWindow(); } - // -----> Builder inner class + // -----> Inner classes + /** + * How a window can be displayed. + * + * @since v1-alpha9 + */ + public enum Mode { + /** + * Marks the window as hidden, making it invisible and unable to be interacted with. + * + * @since v1-alpha9 + */ + HIDDEN, + + /** + * Marks the window as windowed, which + * will allow the user to drag around the window freely. + * + * @since v1-alpha9 + */ + WINDOWED, + + /** + * Same as {@link #HIDDEN} mode, but the window can be + * summoned back into {@link #WINDOWED} mode by the user + * by (for example) clicking an icon or {@code ALT+TAB}-ing. + * + * @since v1-alpha9 + */ + MINIMIZED, + + /** + * Same as {@link #WINDOWED}, but will make the window occupy + * most of the screen space, except for windows/bars/docks. + * + * @since v1-alpha9 + */ + MAXIMIZED, + + /** + * Makes the window will have the same + * size as the monitor it is currently on. + * + * @since v1-alpha9 + */ + BORDERLESS_FULLSCREEN, + + /** + * Makes the window occupy the entire + * monitor it is currently on without + * allowing other windows to occupy + * the same space. + *

+ * This will increase rendering + * throughput as the window manager + * or compositor does not need to + * care about other windows occupying + * the same monitor. Use (and recommend) + * this mode if you/your users + * want more frames per second. + * + * @since v1-alpha9 + */ + EXCLUSIVE_FULLSCREEN + } + /** * Provides an API for building {@link Window}s more easily. * @@ -1131,10 +1265,10 @@ public final class Window implements AutoCloseable { /** * Contains the window mode. * - * @see Window#windowMode + * @see Window#mode * @since v1-alpha9 */ - private @Nullable WindowMode windowMode = null; + private @Nullable Window.Mode mode = null; /** * Contains the target monitor. @@ -1235,8 +1369,8 @@ public final class Window implements AutoCloseable { minimumSize = new Vec2i(-1, -1); if (maximumSize == null) maximumSize = new Vec2i(-1, -1); - if (windowMode == null) - windowMode = WindowMode.WINDOWED; + if (mode == null) + mode = Mode.WINDOWED; if (monitor == null) monitor = Monitor.getMonitors().getFirst(); @@ -1255,7 +1389,7 @@ public final class Window implements AutoCloseable { renderingBoolean = false; // Create new Window instance - return new Window(name, title, icons, size, minimumSize, maximumSize, position, windowMode, monitor, resizableBoolean, borderlessBoolean, focusableBoolean, onTopBoolean, transparentBoolean, renderingBoolean); + return new Window(name, title, icons, size, minimumSize, maximumSize, position, mode, monitor, resizableBoolean, borderlessBoolean, focusableBoolean, onTopBoolean, transparentBoolean, renderingBoolean); } /** @@ -1340,11 +1474,11 @@ public final class Window implements AutoCloseable { * Returns the window mode. * * @return window mode - * @see Window#windowMode + * @see Window#mode * @since v1-alpha9 */ - public @Nullable WindowMode getWindowMode() { - return windowMode; + public @Nullable Window.Mode getWindowMode() { + return mode; } /** @@ -1519,13 +1653,13 @@ public final class Window implements AutoCloseable { /** * Sets the window mode. * - * @param windowMode new window mode + * @param mode new window mode * @return builder instance - * @see Window#windowMode + * @see Window#mode * @since v1-alpha9 */ - public @NotNull Builder setWindowMode(@Nullable WindowMode windowMode) { - this.windowMode = windowMode; + public @NotNull Builder setWindowMode(@Nullable Window.Mode mode) { + this.mode = mode; return this; } diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/type/window/WindowMode.java b/rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingAdapter.java similarity index 50% rename from rendering/src/main/java/de/staropensource/engine/rendering/type/window/WindowMode.java rename to rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingAdapter.java index 5276ccd..00ae02a 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/type/window/WindowMode.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingAdapter.java @@ -20,66 +20,57 @@ package de.staropensource.engine.rendering.type.window; /** - * Contains how a window should be displayed. + * Represents all available rendering adapters. * * @since v1-alpha9 */ -public enum WindowMode { +public enum RenderingAdapter { /** - * Marks the window as hidden, making it invisible and unable to be interacted with. + * Allows the bgfx to autodetect the adapter to use. * * @since v1-alpha9 */ - HIDDEN, + ANY, /** - * Marks the window as windowed, which - * will allow the user to drag around the window freely. + * Tells bgfx to use the CPU for rendering. * * @since v1-alpha9 */ - WINDOWED, + SOFTWARE, /** - * Same as {@link #HIDDEN} mode, but the window can be - * summoned back into {@link #WINDOWED} mode by the user - * by (for example) clicking an icon or {@code ALT+TAB}-ing. + * Tells bgfx to use AMD graphics cards for rendering. * * @since v1-alpha9 */ - MINIMIZED, + AMD, /** - * Same as {@link #WINDOWED}, but will make the window occupy - * most of the screen space, except for windows/bars/docks. + * Tells bgfx to use Apple's SOC for rendering. * * @since v1-alpha9 */ - MAXIMIZED, + APPLE, /** - * Makes the window will have the same - * size as the monitor it is currently on. + * Tells bgfx to use Intel graphics cards for rendering. * * @since v1-alpha9 */ - BORDERLESS_FULLSCREEN, + INTEL, /** - * Makes the window occupy the entire - * monitor it is currently on without - * allowing other windows to occupy - * the same space. - *

- * This will increase rendering - * throughput as the window manager - * or compositor does not need to - * care about other windows occupying - * the same monitor. Use (and recommend) - * this mode if you/your users - * want more frames per second. + * Tells bgfx to use NVIDIA graphics cards for rendering. * * @since v1-alpha9 */ - EXCLUSIVE_FULLSCREEN + NVIDIA, + + /** + * Tells bgfx to use Windows' integrated graphics driver for rendering. + * + * @since v1-alpha9 + */ + MICROSOFT, } diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingPlatform.java b/rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingPlatform.java index 529ff57..15bada0 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingPlatform.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/type/window/RenderingPlatform.java @@ -20,13 +20,13 @@ package de.staropensource.engine.rendering.type.window; /** - * Represents all available platforms. + * Represents all available rendering platforms. * * @since v1-alpha9 */ public enum RenderingPlatform { /** - * Allows the subsystem to autodetect the platform to use. + * Allows GLFW to autodetect the platform to use. * * @since v1-alpha9 */ @@ -61,7 +61,7 @@ public enum RenderingPlatform { COCOA, /** - * Prefer initializing without any platform. + * Prefer initializing headless. * * @since v1-alpha9 */ diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/type/window/VsyncMode.java b/rendering/src/main/java/de/staropensource/engine/rendering/type/window/VsyncMode.java index 9c09c9e..0bb7854 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/type/window/VsyncMode.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/type/window/VsyncMode.java @@ -42,5 +42,10 @@ public enum VsyncMode { * * @since v1-alpha9 */ - ON + ON, + + /** + * Party + */ + PARTY } diff --git a/rendering/src/main/java/module-info.java b/rendering/src/main/java/module-info.java index 63fb1c7..8967a86 100644 --- a/rendering/src/main/java/module-info.java +++ b/rendering/src/main/java/module-info.java @@ -14,11 +14,13 @@ module sosengine.rendering { requires org.lwjgl.stb; requires org.lwjgl.glfw; requires org.lwjgl.bgfx; + requires java.desktop; // API access exports de.staropensource.engine.rendering; exports de.staropensource.engine.rendering.event; exports de.staropensource.engine.rendering.exception; + exports de.staropensource.engine.rendering.renderer; exports de.staropensource.engine.rendering.type; exports de.staropensource.engine.rendering.type.input; exports de.staropensource.engine.rendering.type.window; @@ -27,6 +29,7 @@ module sosengine.rendering { opens de.staropensource.engine.rendering; opens de.staropensource.engine.rendering.event; opens de.staropensource.engine.rendering.exception; + opens de.staropensource.engine.rendering.renderer; opens de.staropensource.engine.rendering.type; opens de.staropensource.engine.rendering.type.input; opens de.staropensource.engine.rendering.type.window; diff --git a/testapp/src/main/java/de/staropensource/engine/testapp/Main.java b/testapp/src/main/java/de/staropensource/engine/testapp/Main.java index 8ae3103..5ca6461 100644 --- a/testapp/src/main/java/de/staropensource/engine/testapp/Main.java +++ b/testapp/src/main/java/de/staropensource/engine/testapp/Main.java @@ -25,9 +25,9 @@ import de.staropensource.engine.base.implementable.EventListenerCode; import de.staropensource.engine.base.implementable.helper.EventHelper; import de.staropensource.engine.base.logging.Logger; import de.staropensource.engine.base.type.vector.Vec2i; -import de.staropensource.engine.base.utility.misc.Miscellaneous; -import de.staropensource.engine.rendering.RenderingSubsystem; import de.staropensource.engine.rendering.event.InputEvent; +import de.staropensource.engine.rendering.renderer.Renderer; +import de.staropensource.engine.rendering.type.FrameHandler; import de.staropensource.engine.rendering.type.Window; import de.staropensource.engine.rendering.type.input.Key; import de.staropensource.engine.rendering.type.input.KeyState; @@ -35,8 +35,6 @@ import lombok.Getter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.LinkedHashMap; - /** * The main class of the sos!engine development application. * @@ -140,14 +138,23 @@ public final class Main { return; } - // Render loop - LinkedHashMap<@NotNull Window, @NotNull Throwable> renderLoopFailures = RenderingSubsystem - .getInstance() - .runRenderLoop(() -> { - if (shutdown || window.isClosureRequested()) - Engine.getInstance().shutdown(); - }); + // Add frame handlers + Renderer.addFrameHandler(new FrameHandler() { + @Override + public @NotNull FrameHandler.Priority getPriority() { + return Priority.DEFAULT; + } + @Override + public void run() { + if (shutdown || window.isClosureRequested()) + Engine.getInstance().shutdown(); + //window.setPosition(new Vec2i((int) Renderer.getFrameCount() / 10, (int) Renderer.getFrameCount() / 10)); + } + }); + + // Start renderer + Renderer.start(); } catch (Exception exception) { Logger.crash("Caught throwable in main thread:", exception); }