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 index e73d2f2..a640d19 100644 --- a/rendering/src/main/java/de/staropensource/engine/rendering/renderer/Renderer.java +++ b/rendering/src/main/java/de/staropensource/engine/rendering/renderer/Renderer.java @@ -19,22 +19,20 @@ 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.Window; -import de.staropensource.engine.rendering.type.window.VsyncMode; +import lombok.AccessLevel; import lombok.Getter; +import lombok.Setter; 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. @@ -52,7 +50,18 @@ public final class Renderer { * to frame renders. * * @since v1-alpha9 + * -- GETTER -- + * Returns all frame handlers. + *

+ * Frame handlers are invoked before + * all windows are rendered, allowing + * the application or game to respond + * to frame renders. + * + * @return frame handlers + * @since v1-alpha9 */ + @Getter(value = AccessLevel.PACKAGE) private static final @NotNull List<@NotNull FrameHandler> frameHandlers = Collections.synchronizedList(new ArrayList<>()); /** @@ -96,8 +105,17 @@ public final class Renderer { * * @return amount of frames rendered * @since v1-alpha9 + * -- SETTER -- + * Sets the frame count aka. + * the amount of frames rendered. + *

+ * Updated every frame. + * + * @param frameCount new amount of frames rendered + * @since v1-alpha9 */ @Getter + @Setter(value = AccessLevel.PACKAGE) private static long frameCount = 0L; /** @@ -120,8 +138,20 @@ public final class Renderer { * * @return delta time * @since v1-alpha9 + * -- SETTER -- + * Sets the delta time, also + * known as the render time. + *

+ * Delta time is the time in seconds + * between the last and current frame. + *

+ * Updated every frame. + * + * @param deltaTime new delta time + * @since v1-alpha9 */ @Getter + @Setter(value = AccessLevel.PACKAGE) private static double deltaTime = 0d; /** @@ -137,8 +167,16 @@ public final class Renderer { * * @return frames per second * @since v1-alpha9 + * -- SETTER -- + * Sets the frames per second (FPS) count. + *

+ * Updated every second. + * + * @param framesPerSecond new frames per second + * @since v1-alpha9 */ @Getter + @Setter(value = AccessLevel.PACKAGE) private static double framesPerSecond = 0d; /** @@ -161,8 +199,17 @@ public final class Renderer { * * @return last frame time * @since v1-alpha9 + * -- SETTER -- + * Sets the time it took + * to calculate the last frame. + *

+ * Updated every frame. + * + * @param lastFrameTime new last frame time + * @since v1-alpha9 */ @Getter + @Setter(value = AccessLevel.PACKAGE) private static Map<@NotNull String, @NotNull Long> lastFrameTime = Collections.unmodifiableMap(new HashMap<>()); @@ -287,119 +334,13 @@ public final class Renderer { * @since v1-alpha9 */ @SuppressWarnings({ "InfiniteLoopStatement" }) - private static void render() throws Throwable { - long previousFrameCount = 0L; // Frame count one second ago - long systemTimeNow; // Current system time - long systemTimePrevious = System.currentTimeMillis(); // Previous system time - LinkedList deltaTimes = 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 - + private static void render() { 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 - int resetSettings = 0; - if (RenderingSubsystemConfiguration.getInstance().getVsyncMode() == VsyncMode.ON) - resetSettings |= BGFX_RESET_TRANSPARENT_BACKBUFFER; - for (Window window : Window.getWindows()) - if (window.isTransparent()) - resetSettings |= BGFX_RESET_TRANSPARENT_BACKBUFFER; - - bgfx_reset( - Window.getWindows().getFirst().getSize().getX(), - Window.getWindows().getFirst().getSize().getY(), - resetSettings, - 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); - if (RenderingSubsystemConfiguration.getInstance().getVsyncMode() == VsyncMode.OFF && RenderingSubsystemConfiguration.getInstance().getMaximumFramesPerSecond() > 0) { - execTimes.replace("Waiting", (long) (1d / RenderingSubsystemConfiguration.getInstance().getMaximumFramesPerSecond() * 1000d)); - - 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); - execTimes.clear(); - systemTimeNow = System.currentTimeMillis(); - deltaTime = (double) (systemTimeNow - systemTimePrevious) / 1000; - systemTimePrevious = systemTimeNow; - deltaTimes.add(deltaTime); - - - // Perform per-second operations - if (System.currentTimeMillis() >= timesPSO) { - // Calculate FPS count - framesPerSecond = 1 / NumberUtil.calculateMeanDouble(deltaTimes); - - // 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" + - "-> Resizable: " + window.isResizable() + "\n" + - "-> Borderless: " + window.isBorderless() + "\n" + - "-> Focused: " + window.isFocused() + "\n" + - "-> On top: " + window.isOnTop() + "\n" + - "-> Transparent: " + window.isTransparent() + "\n" + - "-> Rendering: " + window.isRendering() - ); - - - - // Reset per-second variables - previousFrameCount = frameCount; - deltaTimes.clear(); - timesPSO = System.currentTimeMillis() + 1000; - } + RenderingCode.invokeFrameHandlers(); + RenderingCode.renderWindows(); + RenderingCode.waitForNextFrame(); + RenderingCode.performPerFrameOperations(); + RenderingCode.performPerSecondOperations(); } } } diff --git a/rendering/src/main/java/de/staropensource/engine/rendering/renderer/RenderingCode.java b/rendering/src/main/java/de/staropensource/engine/rendering/renderer/RenderingCode.java new file mode 100644 index 0000000..a4f5b9d --- /dev/null +++ b/rendering/src/main/java/de/staropensource/engine/rendering/renderer/RenderingCode.java @@ -0,0 +1,245 @@ +/* + * 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.type.FrameHandler; +import de.staropensource.engine.rendering.type.Window; +import de.staropensource.engine.rendering.type.window.VsyncMode; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; + +import static org.lwjgl.bgfx.BGFX.*; +import static org.lwjgl.bgfx.BGFX.bgfx_frame; +import static org.lwjgl.glfw.GLFW.glfwPollEvents; + +/** + * Contains the rendering code. + * + * @since v1-alpha9 + */ +@SuppressWarnings("FieldCanBeLocal") +final class RenderingCode { + /** + * Contains the frame count of second ago. + * + * @since v1-alpha9 + */ + private static long previousFrameCount = 0L; + + /** + * Contains the current system time. + *

+ * This variable is used for calculations where + * system time is used over multiple instructions, + * potentially allowing unwanted deviations in + * time and therefore the calculations. + * + * @since v1-alpha9 + */ + private static long systemTimeNow; + + /** + * Contains the system time at + * the end of the last frame. + *

+ * Used for delta time calculation. + * + * @since v1-alpha9 + */ + private static long systemTimePrevious = System.currentTimeMillis(); + + /** + * Contains a list of delta + * time values over one second. + * + * @since v1-alpha9 + */ + private static final LinkedList deltaTimes = new LinkedList<>(); + + /** + * Contains separate timings in one frame. + * + * @since v1-alpha9 + */ + private static final Map execTimes = new LinkedHashMap<>(); + + /** + * Contains the amount to wait until the next frame is allowed to pass. + * + * @since v1-alpha9 + */ + private static long timesWait; + + /** + * Contains the time to wait until per-second + * operations can be executed again. + * + * @since v1-alpha9 + */ + private static long timesPSO = System.currentTimeMillis() + 1000; + + + /** + * Invokes all frame handlers. + * + * @since v1-alpha9 + */ + public static void invokeFrameHandlers() { + for (FrameHandler frameHandler : Renderer.getFrameHandlers()) + execTimes.put("Frame handler '" + frameHandler.getClass().getName() + "'", Miscellaneous.measureExecutionTime(frameHandler::run)); + } + + /** + * Renders all windows out. + * + * @since v1-alpha9 + */ + public static void renderWindows() { + execTimes.put("Rendering", Miscellaneous.measureExecutionTime(() -> { + // Poll for events + glfwPollEvents(); + + // Reset backbuffer + resetBackBuffer(); + + // Render all windows + for (Window window : Window.getWindows()) + if (window.isRendering()) { + window.updateState(); + window.render(); + } + + // Advance to next frame + bgfx_frame(false); + })); + } + + /** + * Resets bgfx's backbuffer. + * + * @since v1-alpha9 + */ + private static void resetBackBuffer() { + int resetSettings = 0; + if (RenderingSubsystemConfiguration.getInstance().getVsyncMode() == VsyncMode.ON) + resetSettings |= BGFX_RESET_TRANSPARENT_BACKBUFFER; + for (Window window : Window.getWindows()) + if (window.isTransparent()) + resetSettings |= BGFX_RESET_TRANSPARENT_BACKBUFFER; + + bgfx_reset( + Window.getWindows().getFirst().getSize().getX(), + Window.getWindows().getFirst().getSize().getY(), + resetSettings, + BGFX_TEXTURE_FORMAT_RGBA4 + ); + } + + /** + * Waits for the next frame. + * + * @since v1-alpha9 + */ + public static void waitForNextFrame() { + execTimes.put("Waiting", 0L); + if (RenderingSubsystemConfiguration.getInstance().getVsyncMode() == VsyncMode.OFF && RenderingSubsystemConfiguration.getInstance().getMaximumFramesPerSecond() > 0) { + execTimes.replace("Waiting", (long) (1d / RenderingSubsystemConfiguration.getInstance().getMaximumFramesPerSecond() * 1000d)); + + 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(); + } + } + } + + /** + * Performs per-frame operations. + * + * @since v1-alpha9 + */ + public static void performPerFrameOperations() { + Renderer.setFrameCount(Renderer.getFrameCount() + 1); + Renderer.setLastFrameTime(new HashMap<>(execTimes)); + systemTimeNow = System.currentTimeMillis(); + Renderer.setDeltaTime((double) (systemTimeNow - systemTimePrevious) / 1000); + systemTimePrevious = systemTimeNow; + deltaTimes.add(Renderer.getDeltaTime()); + execTimes.clear(); + } + + /** + * Performs per-second operations. + * + * @since v1-alpha9 + */ + public static void performPerSecondOperations() { + // Perform per-second operations + if (System.currentTimeMillis() >= timesPSO) { + // Calculate FPS count + Renderer.setFramesPerSecond( 1 / NumberUtil.calculateMeanDouble(deltaTimes)); + + // Log frame count + if (RenderingSubsystemConfiguration.getInstance().isDebugFrames()) + Logger.diag("Frames " + previousFrameCount + "-" + Renderer.getFrameCount() + "\n-> Frames/s: " + Renderer.getFramesPerSecond() + "\n-> Delta time: " + Renderer.getDeltaTime()); + + // 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" + + "-> Resizable: " + window.isResizable() + "\n" + + "-> Borderless: " + window.isBorderless() + "\n" + + "-> Focused: " + window.isFocused() + "\n" + + "-> On top: " + window.isOnTop() + "\n" + + "-> Transparent: " + window.isTransparent() + "\n" + + "-> Rendering: " + window.isRendering() + ); + + + + // Reset per-second variables + previousFrameCount = Renderer.getFrameCount(); + deltaTimes.clear(); + timesPSO = System.currentTimeMillis() + 1000; + } + } +} 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 5ca6461..859d420 100644 --- a/testapp/src/main/java/de/staropensource/engine/testapp/Main.java +++ b/testapp/src/main/java/de/staropensource/engine/testapp/Main.java @@ -149,7 +149,7 @@ public final class Main { if (shutdown || window.isClosureRequested()) Engine.getInstance().shutdown(); - //window.setPosition(new Vec2i((int) Renderer.getFrameCount() / 10, (int) Renderer.getFrameCount() / 10)); + window.setPosition(new Vec2i((int) Renderer.getFrameCount() / 10, (int) Renderer.getFrameCount() / 10)); } });