Profiling - tracks GPU and CPU usage as well as displaying performance metrics and scene information

This commit is contained in:
Halbear 2026-06-12 19:51:53 +01:00
parent c9844fc415
commit 620838a41d
22 changed files with 827 additions and 81 deletions

View file

@ -55,6 +55,7 @@ public class Render {
public TextureCache GetTextureCache(){return textureCache;}
public MaterialsCache GetMaterialsCache(){return materialsCache;}
public VulkanContext GetVulkanContext(){return RendererContext;}
public Render(EngineInstance engineInstance) {
RendererContext = new VulkanContext(engineInstance.window());

View file

@ -6,6 +6,8 @@ import org.tinylog.Logger;
import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import static org.lwjgl.vulkan.VK10.*;
@ -30,10 +32,15 @@ public class EngineConfig {
private int Width;
private int Height;
public static int FrameRate_PRIMARYTHREAD =0;
public static int TPS_PRIMARYTHREAD =0;
public static int TPS =0;
public static long LAST_AVERAGE_GPU_TIME = 0;
public static int FrameRate_MAINENGINETHREAD =0;
public static int TPS_MAINENGINETHREAD =0;
public static int FrameRate_FASTENGINETHREAD =0;
public static int TPS_FASTENGINETHREAD =0;
public static int FrameRate_RENDERTHREAD =0;
public static int TPS_RENDERTHREAD =0;
public static int FrameRate_WINDOW =0;
public static int RENDERFRAMES =0;
public static boolean VSYNC = false;
@ -55,7 +62,23 @@ public class EngineConfig {
private int MaxDescriptors = 1000;
private int AAValue = 1;
private AntiAliasType AAMode = AntiAliasType.FXAA;
private List<String[]> MemoryProfiling = new ArrayList<>();
private List<String[]> SystemProfiling = new ArrayList<>();
public String GPU_NAME = "Vulkan compatible device";
public String CPU_NAME = "Vulkan compatible device";
public int CPU_CORE_COUNT = 0;
public String CPU_DETAILS = "";
public String GetGPU(){return GPU_NAME;}
public void SetGPU(String Gpu){GPU_NAME = Gpu;}
public void SetCPU(String Name, int CoreCount){
CPU_NAME = Name;
CPU_CORE_COUNT = CoreCount;
CPU_DETAILS = CPU_NAME + " with " + CPU_CORE_COUNT + " Threads";
}
public int GetCoreCount(){return CPU_CORE_COUNT;}
public String GetCPUName(){return CPU_NAME;}
public String GetCPUDetails(){return CPU_DETAILS;}
public void GPU_Does_Not_Support_Logging(){
GPU_SUPPORTS_LOGGING = false;
}
@ -63,6 +86,11 @@ public class EngineConfig {
public String GetDefaultTexturePath(){return DefaultTexturePath;}
public int GetMaxDescriptors(){return MaxDescriptors;}
public int RenderAA(){return AAValue;}
public AntiAliasType RenderAAType(){return AAMode;}
public List<String[]> GetMemoryProfiling(){return MemoryProfiling;}
public List<String[]> GetSystemProfiling(){return SystemProfiling;}
public void SetMemoryProfiling(List<String[]> memory){MemoryProfiling = memory;}
public void SetSystemProfiling(List<String[]> Profiling){SystemProfiling = Profiling;}
public enum AntiAliasType{
NONE,

View file

@ -10,4 +10,5 @@ public interface GameLogic {
void CameraInput(EngineInstance engineInstance, long FrameDiffNanoSeconds);
void Update(EngineInstance engineInstance, long FrameDiffNanoSeconds);
void UpdateFastThread(EngineInstance engineInstance, long FrameDiffNanoSeconds);
void SecondUpdate(EngineInstance engineInstance);
}

View file

@ -4,6 +4,7 @@ import imgui.ImGui;
import imgui.ImGuiIO;
import imgui.ImVec2;
import imgui.flag.ImGuiCond;
import imgui.flag.ImGuiWindowFlags;
import net.halbear.Terrain4J.EngineCore.Display.Window;
import net.halbear.Terrain4J.EngineCore.Input.KeyboardInput;
import net.halbear.Terrain4J.EngineCore.Input.MouseListener;
@ -12,6 +13,8 @@ import net.halbear.Terrain4J.EngineCore.Logic.Rendering.ModelLoader;
import net.halbear.Terrain4J.EngineCore.Main.Scene.Actor;
import net.halbear.Terrain4J.EngineCore.Main.Scene.Camera;
import net.halbear.Terrain4J.EngineCore.Main.Scene.Scene;
import net.halbear.Terrain4J.EngineCore.Profiling.CPUMonitor;
import net.halbear.Terrain4J.EngineCore.Profiling.GPUProfiler;
import net.halbear.Terrain4J.EngineCore.Vulkan.GUI.GuiTexture;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.MaterialData;
import net.halbear.Terrain4J.EngineCore.Vulkan.Rendering.VkModel.ModelData;
@ -20,13 +23,14 @@ import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import static org.lwjgl.glfw.GLFW.*;
public class GameCore implements GameLogic {
private static final float MOUSE_SENSITIVITY = 0.1f;
private static final float MOVEMENT_SPEED = 0.01f;
private static final float MOVEMENT_SPEED = 1f;
private final Vector3f rotatingAngle = new Vector3f(0, 1, 0);
private float angle = 180;
private List<Float> angles = new ArrayList<>();
@ -38,6 +42,9 @@ public class GameCore implements GameLogic {
private int Ticks = 0;
private int GUI_MODE = 0;
private GuiTexture guiTexture;
private List<String[]> PerformanceMetrics = new ArrayList<>();
private List<String[]> SettingPerformanceMetrics = new ArrayList<>();
private boolean FetchingMetrics = false;
@Override
public void cleanup() {
@ -49,38 +56,38 @@ public class GameCore implements GameLogic {
Scene scene = engineInstance.scene();
List<ModelData> models = new ArrayList<>();
//ModelData SponzaData = ModelLoader.LoadModel("resources/models/Unit02/evangelion_unit-02.json");
//List<MaterialData> SponzaMaterial = ModelLoader.LoadMaterials("resources/models/Unit02/evangelion_unit-02_mat.json");
//ModelData SponzaData = ModelLoader.LoadModel("resources/models/Cafe/exterior.json");
//List<MaterialData> SponzaMaterial = ModelLoader.LoadMaterials("resources/models/Cafe/exterior_mat.json");
// ModelData SponzaData1 = ModelLoader.LoadModel("resources/models/Cafe/interior.json");
// List<MaterialData> SponzaMaterial1= ModelLoader.LoadMaterials("resources/models/Cafe/interior_mat.json");
//scene.AddActor(new Actor("Sponza1", SponzaData.ID(), new Vector3f(0,0,-5f)));
//scene.AddActor(new Actor("Sponza2", SponzaData1.ID(), new Vector3f(0,0,-5f)));
//ModelData SponzaData = ModelLoader.LoadModel("resources/models/Sponza/Sponza.json");
//List<MaterialData> SponzaMaterial = ModelLoader.LoadMaterials("resources/models/Sponza/Sponza_mat.json");
ModelData SponzaData = ModelLoader.LoadModel("resources/models/Cafe/exterior.json");
List<MaterialData> SponzaMaterial = ModelLoader.LoadMaterials("resources/models/Cafe/exterior_mat.json");
ModelData SponzaData1 = ModelLoader.LoadModel("resources/models/Cafe/interior.json");
List<MaterialData> SponzaMaterial1= ModelLoader.LoadMaterials("resources/models/Cafe/interior_mat.json");
scene.AddActor(new Actor("Sponza1", SponzaData.ID(), new Vector3f(0,0,-5f)));
scene.AddActor(new Actor("Sponza2", SponzaData1.ID(), new Vector3f(0,0,-5f)));
models.add(ModelLoader.LoadModel("resources/models/Metro/OBJ/MetroLeadCar.json"));
models.add(ModelLoader.LoadModel("resources/models/melona/melona.json"));
models.add(ModelLoader.LoadModel("resources/models/Alice/Welsh040Alice.json"));
models.add(ModelLoader.LoadModel("resources/models/Sloop/SloopOBJ.json"));
models.add(ModelLoader.LoadModel("resources/models/Corvette/corvetteclass.json"));
for(int i = 0; i < 5000; i++) {
CubeActors.add(new Actor("AdvancedModel" + i, models.get((int)Math.round(Math.min(Math.max(Math.random() *(Math.random() * models.size() - 1),0),models.size() - 1))).ID(), new Vector3f((float)(-200 + Math.random() * 400), (float)(-50 + Math.random() * 100), (float)(250 + Math.random() * -500))));
angles.add((float)(Math.random() * 360));
rotatingAngles.add(new Vector3f((float)(Math.random()*2), (float)(Math.random()*2), (float)(Math.random()*2)));
Velocities.add(new Vector3f((float)(-2 + Math.random()*4), -2 + (float)(Math.random()*4), -2 + (float)(Math.random()*4)));
}
//models.add(ModelLoader.LoadModel("resources/models/Metro/OBJ/MetroLeadCar.json"));
//models.add(ModelLoader.LoadModel("resources/models/melona/melona.json"));
//models.add(ModelLoader.LoadModel("resources/models/Alice/Welsh040Alice.json"));
//models.add(ModelLoader.LoadModel("resources/models/Sloop/SloopOBJ.json"));
//models.add(ModelLoader.LoadModel("resources/models/Corvette/corvetteclass.json"));
//for(int i = 0; i < 0; i++) {
// CubeActors.add(new Actor("AdvancedModel" + i, models.get((int)Math.round(Math.min(Math.max(Math.random() *(Math.random() * models.size() - 1),0),models.size() - 1))).ID(), new Vector3f((float)(-200 + Math.random() * 400), (float)(-50 + Math.random() * 100), (float)(250 + Math.random() * -500))));
// angles.add((float)(Math.random() * 360));
// rotatingAngles.add(new Vector3f((float)(Math.random()*2), (float)(Math.random()*2), (float)(Math.random()*2)));
// Velocities.add(new Vector3f((float)(-2 + Math.random()*4), -2 + (float)(Math.random()*4), -2 + (float)(Math.random()*4)));
// }
CubeActors.forEach(scene::AddActor);
List<MaterialData> materials = new ArrayList<>();
materials.addAll(ModelLoader.LoadMaterials("resources/models/Metro/OBJ/MetroLeadCar_mat.json"));
materials.addAll(ModelLoader.LoadMaterials("resources/models/Corvette/corvetteclass_mat.json"));
materials.addAll(ModelLoader.LoadMaterials("resources/models/melona/melona_mat.json"));
materials.addAll(ModelLoader.LoadMaterials("resources/models/Alice/Welsh040Alice_mat.json"));
materials.addAll(ModelLoader.LoadMaterials("resources/models/Sloop/SloopOBJ_mat.json"));
// materials.addAll(SponzaMaterial);
//materials.addAll(SponzaMaterial1);
//models.add(SponzaData);
//models.add(SponzaData1);
//materials.addAll(ModelLoader.LoadMaterials("resources/models/Metro/OBJ/MetroLeadCar_mat.json"));
//materials.addAll(ModelLoader.LoadMaterials("resources/models/Corvette/corvetteclass_mat.json"));
//materials.addAll(ModelLoader.LoadMaterials("resources/models/melona/melona_mat.json"));
//materials.addAll(ModelLoader.LoadMaterials("resources/models/Alice/Welsh040Alice_mat.json"));
//materials.addAll(ModelLoader.LoadMaterials("resources/models/Sloop/SloopOBJ_mat.json"));
materials.addAll(SponzaMaterial);
materials.addAll(SponzaMaterial1);
models.add(SponzaData);
models.add(SponzaData1);
Camera camera = scene.GetCamera();
camera.SetPosition(40.0f, 155.0f, -42.0f);
camera.SetPosition(0,0,0);
@ -120,7 +127,7 @@ public class GameCore implements GameLogic {
@Override
public void RenderThreadInput(EngineInstance engineInstance, long FrameDiffNanoSeconds) {
HandleGui(engineInstance);
HandleGui(engineInstance, FrameDiffNanoSeconds);
}
@Override
@ -163,7 +170,8 @@ public class GameCore implements GameLogic {
} else camera.Sprint(false);
}
private boolean HandleGui(EngineInstance engineInstance){
private boolean HandleGui(EngineInstance engineInstance, long frameTimeNS){
FetchingMetrics = true;
ImGuiIO imGuiIO = ImGui.getIO();
MouseListener mouseListener = engineInstance.window().getMouseInput();
Vector2f mousePosition = mouseListener.getCurrentPos();
@ -172,8 +180,38 @@ public class GameCore implements GameLogic {
imGuiIO.addMouseButtonEvent(1,mouseListener.isRightButtonPressed());
if(GUI_MODE == 0){
double FrameTimeMS = (frameTimeNS/1000000.0);
double AverageGPUTimeMS = EngineConfig.LAST_AVERAGE_GPU_TIME/1000000.0;
int windowFlags = ImGuiWindowFlags.NoDecoration
| ImGuiWindowFlags.AlwaysAutoResize
| ImGuiWindowFlags.NoSavedSettings
| ImGuiWindowFlags.NoFocusOnAppearing
| ImGuiWindowFlags.NoNav
| ImGuiWindowFlags.NoMove;
ImGui.newFrame();
ImGui.showDemoWindow();
ImGui.setNextWindowPos(0,0, ImGuiCond.Always);
//ImGui.setNextWindowSize(300,100);
ImGui.begin("FPS overlay",windowFlags);
ImGui.text("PERFORMANCE METRICS:");
ImGui.separator();
ImGui.text(String.format("Vulkan FPS: " + EngineConfig.FrameRate_RENDERTHREAD + "fps | FrameTime: %.2f" + "ms | Avg. GPU Time: %.3f" + "ms | TPS: " + EngineConfig.TPS_RENDERTHREAD,FrameTimeMS,AverageGPUTimeMS));
ImGui.text(String.format("Primary Thread FPS: " + EngineConfig.FrameRate_PRIMARYTHREAD + "fps | GLFW Window FrameRate:" + EngineConfig.FrameRate_WINDOW + "fps | TPS: " + EngineConfig.TPS_PRIMARYTHREAD));
ImGui.text(String.format("Main Thread FPS: " + EngineConfig.FrameRate_MAINENGINETHREAD + "fps | TPS: " + EngineConfig.TPS_MAINENGINETHREAD));
ImGui.text(String.format("Fast Thread FPS: " + EngineConfig.FrameRate_FASTENGINETHREAD + "fps | TPS: " + EngineConfig.TPS_FASTENGINETHREAD));
if(PerformanceMetrics.size() > 0) {
ImGui.text(" ");
ImGui.separator();
for (int i = 0; i < PerformanceMetrics.size(); i++) {
String[] memoryUsageMetrics = PerformanceMetrics.get(i);
for(int j = 0; j < memoryUsageMetrics.length; j++){
ImGui.text(memoryUsageMetrics[j]);
}
ImGui.separator();
ImGui.text(" ");
}
}
ImGui.end();
ImGui.endFrame();
ImGui.render();
} else if (GUI_MODE == 1){
@ -190,6 +228,7 @@ public class GameCore implements GameLogic {
ImGui.endFrame();
ImGui.render();
}
FetchingMetrics = false;
return imGuiIO.getWantCaptureKeyboard();
}
@ -218,4 +257,30 @@ public class GameCore implements GameLogic {
CubeActors.get(i).UpdateModelMatrix();
}
}
@Override
public void SecondUpdate(EngineInstance engineInstance) {
SettingPerformanceMetrics.clear();
List<String[]> Metrics = new ArrayList<>();
String[] SceneDetails = new String[5];
Vector3f Position = engineInstance.scene().GetCamera().GetPosition();
Vector3f Rotation = engineInstance.scene().GetCamera().GetRotation();
SceneDetails[0] = String.format("INSTANCE INFORMATION: ");
SceneDetails[1] = String.format(" Camera Position: X: %.2f Y: %.2f Z: %.2f",Position.x ,Position.y ,Position.z);
SceneDetails[2] = String.format(" Camera Rotation: X: %.2f° Y: %.2f° Z: %.2f°",Rotation.x *EngineConfig.RadToDeg,Rotation.y *EngineConfig.RadToDeg ,Rotation.z *EngineConfig.RadToDeg);
SceneDetails[3] = String.format(" Anti Aliasing: " + EngineConfig.getInstance().RenderAAType().name());
SceneDetails[4] = String.format(" FOV: %.2f°",(EngineConfig.getInstance().GetFOV()*EngineConfig.RadToDeg));
String[] DeviceInfo = new String[5];
DeviceInfo[0] = String.format("SYSTEM INFORMATION: ");
DeviceInfo[1] = String.format(" CPU: " + EngineConfig.getInstance().GetCPUDetails());
DeviceInfo[2] = String.format(" RAM: " + CPUMonitor.RamSize);
DeviceInfo[3] = String.format(" GPU: " + EngineConfig.getInstance().GetGPU());
DeviceInfo[4] = String.format(" VRAM: " + GPUProfiler.GetVramCapacity());
Metrics.add(SceneDetails);
Metrics.add(DeviceInfo);
SettingPerformanceMetrics.addAll(Metrics);
SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetMemoryProfiling());
SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetSystemProfiling());
PerformanceMetrics = List.copyOf(SettingPerformanceMetrics);
}
}

View file

@ -34,6 +34,8 @@ public class PrimaryRuntime {
public final CPUMonitor cpuProfiler;
private static GameLogic gameLogic;
public static GameLogic GetGameCore(){return gameLogic;}
public PrimaryRuntime(String windowTitle, GameLogic appLogic) {
String[] knownDLSSfiles = {"nvngx_dlss.dll", "nvngx_dlssg.dll", "_nvngx.dll"};
for (String dllName : knownDLSSfiles) {
@ -57,6 +59,7 @@ public class PrimaryRuntime {
FastThread = new FastTickThread(engineInstance, appLogic);
DrawingThread = new RenderThread(window, engineInstance, initData, appLogic);
cpuProfiler = new CPUMonitor();
EngineConfig.getInstance().SetCPU(CPUMonitor.getCpuName(), CPUMonitor.getCoreCount());
Thread.currentThread().setName("Terrain4J Primary Runtime Thread");
}
public static MainThread GetMainThread(){return PrimaryThread;}
@ -121,8 +124,7 @@ public class PrimaryRuntime {
SecondCounter = 0;
EngineConfig.FrameRate_WINDOW = EngineConfig.RENDERFRAMES;
EngineConfig.FrameRate_PRIMARYTHREAD = Frames;
EngineConfig.TPS = TickFrames;
Logger.info("Primary Thread FPS: " + Frames + " TPS: " + TickFrames + " Window Draw FPS: " + EngineConfig.RENDERFRAMES);
EngineConfig.TPS_PRIMARYTHREAD = TickFrames;
Frames = 0;
TickFrames = 0;
EngineConfig.RENDERFRAMES = 0;

View file

@ -1,19 +1,102 @@
package net.halbear.Terrain4J.EngineCore.Profiling;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import org.tinylog.Logger;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import com.sun.management.OperatingSystemMXBean;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class CPUMonitor {
private final OperatingSystemMXBean OSBean;
public static String RamSize = "0.0GB";
public CPUMonitor(){
OSBean = ManagementFactory.getOperatingSystemMXBean();
OSBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
}
public String LogCPUUsage(){
return "";
public static String GetRamSize(){return RamSize;}
public static String[] LogCPUUsage(){
OperatingSystemMXBean OSBean= (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
double processCPU = OSBean.getProcessCpuLoad();
double SystemCPU = OSBean.getCpuLoad();
long totalPhysicalMemory = OSBean.getTotalPhysicalMemorySize();
long freePhysicalMemory = OSBean.getFreePhysicalMemorySize();
long usedPhysicalMemory = totalPhysicalMemory - freePhysicalMemory;
RamSize = String.format("%.2f" + "gb",ConvertToGB(totalPhysicalMemory));
Runtime runtime = Runtime.getRuntime();
long maxHeap = runtime.maxMemory();
long totalHeap = runtime.totalMemory();
long freeHeap = runtime.freeMemory();
long usedHeap = totalHeap - freeHeap;
int TotalCores = Runtime.getRuntime().availableProcessors();
double ActiveSystemCores = TotalCores * SystemCPU;
double ActiveProcessCores = TotalCores * processCPU;
String[] CPUMetrics = new String[6];
CPUMetrics[0] = String.format("CPU & SYSTEM MEMORY USAGE:\n");
CPUMetrics[1] = String.format(" Terrain4J CPU Core Usage: %d / %d", (int)Math.ceil(ActiveProcessCores),TotalCores);
CPUMetrics[2] = String.format(" Terrain4J CPU Usage: %.2f%%%n", processCPU * 100);
CPUMetrics[3] = String.format(" Terrain4J Memory Usage: %d MB / %d MB%n", ConvertToMB(usedHeap), ConvertToMB(maxHeap));
CPUMetrics[4] = String.format(" Total CPU Load: %.2f%%%n", SystemCPU * 100);
CPUMetrics[5] = String.format(" System Memory Used: %d MB / %d MB%n", ConvertToMB(usedPhysicalMemory), ConvertToMB(totalPhysicalMemory));
return CPUMetrics;
}
public static List<String> GetThreadsInUse(){
List<String> Threads = new ArrayList<>();
Thread.getAllStackTraces().keySet().forEach(thread ->
{
Threads.add(thread.getName());
});
return Threads;
}
private static double ConvertToGB(long bytes) {
return (double)(bytes / (1024 * 1024* 1024));
}
private static long ConvertToMB(long bytes) {
return bytes / (1024 * 1024);
}
public static int getCoreCount(){
return Runtime.getRuntime().availableProcessors();
}
public static String getCpuName() {
String os = System.getProperty("os.name").toLowerCase();
String command = "";
if (os.contains("win")) {
command = "wmic cpu get name";
} else if (os.contains("mac")) {
command = "sysctl -n machdep.cpu.brand_string";
} else if (os.contains("nix") || os.contains("nux") || os.contains("aix")) {
command = "sh -c lscpu | grep 'Model name:'";
} else {
return "Unknown OS - Cannot fetch CPU name";
}
try {
Process process = Runtime.getRuntime().exec(command);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
StringBuilder output = new StringBuilder();
while ((line = reader.readLine()) != null) {
if (!line.trim().isEmpty() && !line.toLowerCase().contains("name")) {
output.append(line.trim());
break;
}
}
return output.toString().isEmpty() ? "Unknown CPU" : output.toString();
}
} catch (Exception e) {
return "Error reading CPU name: " + e.getMessage();
}
}
}

View file

@ -1,17 +1,56 @@
package net.halbear.Terrain4J.EngineCore.Profiling;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers.Device;
import net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.vulkan.VkCommandBuffer;
import org.lwjgl.vulkan.VkDevice;
import org.lwjgl.vulkan.VkQueryPoolCreateInfo;
import org.lwjgl.vulkan.*;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.List;
import static org.lwjgl.vulkan.EXTMemoryBudget.VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT;
import static org.lwjgl.vulkan.VK13.*;
public class GPUProfiler {
private static String VramCapacity = "";
public static String GetVramCapacity(){return VramCapacity;}
public static List<String[]> GetVramUsage(VkPhysicalDevice physicalDevice){
List<String[]> result = new ArrayList<>();
try(var MemStack = MemoryStack.stackPush()){
VkPhysicalDeviceMemoryBudgetPropertiesEXT memoryBudget =
VkPhysicalDeviceMemoryBudgetPropertiesEXT.calloc(MemStack)
.sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT);
VkPhysicalDeviceMemoryProperties2 MemoryProperties2 = VkPhysicalDeviceMemoryProperties2.calloc(MemStack)
.sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2)
.pNext(memoryBudget.address());
vkGetPhysicalDeviceMemoryProperties2(physicalDevice,MemoryProperties2);
VkPhysicalDeviceMemoryProperties MemoryProperties = MemoryProperties2.memoryProperties();
int HeapCount = MemoryProperties.memoryHeapCount();
for(int i = 0; i < HeapCount; i++){
VkMemoryHeap heap = MemoryProperties.memoryHeaps(i);
if ((heap.flags() & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0) {
long AllocatedByProcess = memoryBudget.heapUsage(i);
long OverallSystemBudget = memoryBudget.heapBudget(i);
long TotalHeapSize = heap.size();
String[] properties = new String[5];
VramCapacity = String.format(" %.2f GB\n",TotalHeapSize / (1024.0 * 1024.0*1024.0));
properties[0] = String.format("VIDEO MEMORY USAGE METRICS:\n");
properties[1] = String.format("VRAM Heap %d :", i);
properties[2] = String.format(" Total Capacity: %.2f MB\n", TotalHeapSize / (1024.0 * 1024.0));
properties[3] = String.format(" Terrain4J Usage: %.2f MB\n", AllocatedByProcess / (1024.0 * 1024.0));
properties[4] = String.format(" System Budget: %.2f MB\n", OverallSystemBudget / (1024.0 * 1024.0));
result.add(properties);
}
}
}
return result;
}
private long RemoteryInstance;
public GPUProfiler(){

View file

@ -83,6 +83,7 @@ public class EngineThread implements Runnable{
FrameTimes[1] += FrameTimes[0];
FrameTimes[2] += FrameTimes[0];
FrameTimes[3] += FrameTimes[0];
CycleEvent();
if (FrameTimes[1] >= EngineAccuracy){
FrameEvent(now, InitialTime);
Frames++;

View file

@ -29,7 +29,7 @@ public class FastTickThread extends EngineThread {
@Override
public void SecondEvent(){
EngineConfig.FrameRate_FASTENGINETHREAD = Frames;
Logger.info(ThreadName + " Thread FPS: " + EngineConfig.FrameRate_FASTENGINETHREAD + " TPS: " + TickFrames);
EngineConfig.TPS_FASTENGINETHREAD = TickFrames;
}
public void UpdateTickRateThreshold(){

View file

@ -4,8 +4,13 @@ import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance;
import net.halbear.Terrain4J.EngineCore.Logic.GameLogic;
import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime;
import net.halbear.Terrain4J.EngineCore.Profiling.CPUMonitor;
import org.tinylog.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MainThread extends EngineThread {
@Override
@ -14,6 +19,7 @@ public class MainThread extends EngineThread {
}
@Override
public void FrameEvent(long now, long InitialTime){
gameLogic.Update(PrimaryRuntime.GetEngineInstance(), (now - InitialTime));
}
@Override
public void CycleEvent(){
@ -22,7 +28,24 @@ public class MainThread extends EngineThread {
@Override
public void SecondEvent(){
EngineConfig.FrameRate_MAINENGINETHREAD = Frames;
Logger.info(ThreadName + " Thread FPS: " + EngineConfig.FrameRate_MAINENGINETHREAD + " TPS: " + TickFrames);
EngineConfig.TPS_MAINENGINETHREAD = TickFrames;
List<String[]> profiling = new ArrayList<>();
profiling.addAll(Collections.singleton(CPUMonitor.LogCPUUsage()));
List<String> Threads = CPUMonitor.GetThreadsInUse();
String[] ThreadInformation = new String[Math.min(Threads.size() + 3,14)];
ThreadInformation[0] = "\nTERRAIN4J PROCESS INFORMATION:\n";
ThreadInformation[1] = String.format("Thread Count: %d \n",Threads.size());
ThreadInformation[2] = "v Threads In Use v\n";
for(int i = 0; i < Math.min(Threads.size(),10); i++){
ThreadInformation[3 + i] = String.format("Thread %d -> %s",i,Threads.get(i));
}
if(Threads.size() > 10){
ThreadInformation[13] = "...";
}
profiling.addAll(Collections.singleton(ThreadInformation));
EngineConfig.getInstance().SetSystemProfiling(profiling);
gameLogic.SecondUpdate(PrimaryRuntime.GetEngineInstance());
}
public void UpdateTickRateThreshold(){
CappedToTickRate = EngineConfig.getInstance().GetMainTickCap();

View file

@ -5,8 +5,15 @@ import net.halbear.Terrain4J.EngineCore.Display.Window;
import net.halbear.Terrain4J.EngineCore.Logic.*;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime;
import net.halbear.Terrain4J.EngineCore.Profiling.CPUMonitor;
import net.halbear.Terrain4J.EngineCore.Profiling.GPUProfiler;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DisplayToScreen.SceneRender;
import org.tinylog.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class RenderThread extends EngineThread {
private final Render render;
private Window window;
@ -26,16 +33,21 @@ public class RenderThread extends EngineThread {
@Override
public void FrameEvent(long now, long InitialTime){
gameLogic.RenderThreadInput(PrimaryRuntime.GetEngineInstance(), (now-InitialTime));
gameLogic.Update(PrimaryRuntime.GetEngineInstance(), (now - InitialTime));
render.render(PrimaryRuntime.GetEngineInstance());
}
@Override
public void CycleEvent(){
}
@Override
public void SecondEvent(){
Logger.info("Render FPS: " + Frames);
EngineConfig.LAST_AVERAGE_GPU_TIME = SceneRender.GetGPUTimeNS();
List<String[]> Profiling =GPUProfiler.GetVramUsage(render.GetVulkanContext().GetPhysicalDevice().GetPhysicalDevice());
EngineConfig.getInstance().SetMemoryProfiling(Profiling);
EngineConfig.FrameRate_RENDERTHREAD = Frames;
EngineConfig.TPS_RENDERTHREAD = TickFrames;
}

View file

@ -20,47 +20,37 @@ import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SRGB;
public class TextureCache {
public static final int MAX_TEXTURES = 500;
private final IndexedLinkedHashMap<String, Texture> TextureMap;
private final IndexedLinkedHashMap<String, Texture> UnpaddedTextureMap;
private final List<String> ActualTextures;
public TextureCache(){
TextureMap = new IndexedLinkedHashMap<>();
UnpaddedTextureMap = new IndexedLinkedHashMap<>();
ActualTextures = new ArrayList<>();
}
public Texture AddToMainMap(VulkanContext VkCtx, String ID, String TexturePath, int Format) {
ImageSrc imageSrc = null;
Texture result = null;
try{
imageSrc = GraphUtils.LoadImage(TexturePath);
result = AddTexture(VkCtx, ID, imageSrc, Format);
} catch (IOException exception){
Logger.error("Could not load texture from patch [{}], exception: {}",TexturePath, exception);
} finally{
if(imageSrc != null){
GraphUtils.CleanImageData(imageSrc);
}
}
return result;
public Texture AddTexture(VulkanContext VkCtx, String ID, ImageSrc imageSrc, int Format) {
return AddTexture(VkCtx,ID,imageSrc,Format,false);
}
public Texture AddTexture(VulkanContext VkCtx, String ID, ImageSrc imageSrc, int Format){
public Texture AddTexture(VulkanContext VkCtx, String ID, ImageSrc imageSrc, int Format, boolean PaddingTexture){
if(TextureMap.size() > MAX_TEXTURES){
throw new IllegalArgumentException("Texture Cache Is Full");
}
Texture newTexture = UnpaddedTextureMap.get(ID);
Texture newTexture = TextureMap.get(ID);
if(newTexture == null){
newTexture = new Texture(VkCtx, ID, imageSrc, Format);
UnpaddedTextureMap.put(ID, newTexture);
if(!PaddingTexture)ActualTextures.add(ID);
TextureMap.put(ID, newTexture);
}
return newTexture;
}
public Texture AddTexture(VulkanContext VkCtx, String ID, String TexturePath, int Format) {
return AddTexture(VkCtx,ID,TexturePath,Format,false);
}
public Texture AddTexture(VulkanContext VkCtx, String ID, String TexturePath, int Format, boolean PaddingTexture) {
ImageSrc imageSrc = null;
Texture result = null;
try{
imageSrc = GraphUtils.LoadImage(TexturePath);
result = AddTexture(VkCtx, ID, imageSrc, Format);
result = AddTexture(VkCtx, ID, imageSrc, Format,PaddingTexture);
} catch (IOException exception){
Logger.error("Could not load texture from patch [{}], exception: {}",TexturePath, exception);
} finally{
@ -73,14 +63,21 @@ public class TextureCache {
public void TransitionTexts(VulkanContext VkCtx, CommandPool CmdPool, Queue queue){
Logger.debug("Recording Texture Transition");
IndexedLinkedHashMap<String, Texture> NewTextureMap = new IndexedLinkedHashMap<>();
TextureMap.forEach((name,texture)->{
if(ActualTextures.contains(name)){
NewTextureMap.put(name,texture);
}
});
TextureMap.clear();
TextureMap.putAll(UnpaddedTextureMap);
TextureMap.putAll(NewTextureMap);
NewTextureMap.clear();
int TextureCount = TextureMap.size();
if(TextureCount < MAX_TEXTURES){
int PaddingTextCount = MAX_TEXTURES - TextureCount;
String DefaultTexturePath = EngineConfig.getInstance().GetDefaultTexturePath();
for(int i = 0; i < PaddingTextCount; i++){
AddToMainMap(VkCtx, UUID.randomUUID().toString(), DefaultTexturePath, VK_FORMAT_R8G8B8A8_SRGB);
AddTexture(VkCtx, UUID.randomUUID().toString(), DefaultTexturePath, VK_FORMAT_R8G8B8A8_SRGB,true);
}
}
var CommandBuffer = new CommandBuffer(VkCtx, CmdPool, true, true);

View file

@ -1,5 +1,6 @@
package net.halbear.Terrain4J.EngineCore.Vulkan.Structure.DeviceLayers;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Vulkan.Structure.VulkanInstance;
import org.lwjgl.PointerBuffer;
@ -13,6 +14,7 @@ import java.util.HashSet;
import java.util.Set;
import static net.halbear.Terrain4J.EngineCore.Vulkan.Util.VulkanUtils.vkCheck;
import static org.lwjgl.vulkan.EXTMemoryBudget.VK_EXT_MEMORY_BUDGET_EXTENSION_NAME;
import static org.lwjgl.vulkan.VK13.*;
public class PhysicalDevice {
@ -28,6 +30,7 @@ public class PhysicalDevice {
static{
REQUIRED_EXTENSIONS = new HashSet<>();
REQUIRED_EXTENSIONS.add(KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME);
REQUIRED_EXTENSIONS.add(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
}
private PhysicalDevice(VkPhysicalDevice VulkanPhysicalDevice){
@ -92,6 +95,7 @@ public class PhysicalDevice {
if (result == null){
throw new RuntimeException("No Graphics Driver Found!");
}
EngineConfig.getInstance().SetGPU(result.FetchDeviceName());
Logger.debug("Selected Graphics Driver: [{}]", result.FetchDeviceName());
}
return result;

View file

@ -51,7 +51,7 @@ public class SceneRender implements SceneRenderer {//dynamic rendering
private final TextureSampler textureSampler;
private static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT;
private static final int DEPTH_FORMAT = VK_FORMAT_D16_UNORM;
private static final int DEPTH_FORMAT = VK_FORMAT_D32_SFLOAT_S8_UINT;
private final VkClearValue ClearValueDepth;
private final ByteBuffer PushConstBuffer;
private Attachment AttachmentDepth;
@ -68,6 +68,8 @@ public class SceneRender implements SceneRenderer {//dynamic rendering
private boolean Bb = false;
public int Frames = 0;
private static String LastGPULog = "";
public static long GPUTIME = 0;
public static long CYCLES = 0;
private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/scene_fragment.glsl";
private static final String FRAGMENT_SHADER_FILE_SPV = FRAGMENT_SHADER_FILE_GLSL + ".spv";
@ -77,9 +79,19 @@ public class SceneRender implements SceneRenderer {//dynamic rendering
private final Pipeline VkPipeline;
private Matrix4f ProjectionMatrix;
public static long GetGPUTimeNS(){
long time = 0;
if(CYCLES > 0){
time = GPUTIME/CYCLES;
}
GPUTIME = 0;
CYCLES = 0;
return time;
}
public SceneRender(VulkanContext vulkanContext){
ClearValueColour = VkClearValue.calloc().color(
c -> c.float32(0, R).float32(1, G).float32(2, B).float32(3, 0.0f));
c -> c.float32(0, R).float32(1, G).float32(2, B).float32(3, 1.0f));
ClearValueDepth = VkClearValue.calloc().color(c -> c.float32(0, 0.0f));
AttachmentColour = CreateColourAttachment(vulkanContext);
AttachmentDepth = CreateDepthAttachment(vulkanContext);
@ -222,6 +234,7 @@ public class SceneRender implements SceneRenderer {//dynamic rendering
VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT);
long InitialTime = System.nanoTime();
vkCmdBeginRendering(CommandHandle, RenderInfo);
vkCmdBindPipeline(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkPipeline.GetVulkanPipeline());
@ -255,6 +268,8 @@ public class SceneRender implements SceneRenderer {//dynamic rendering
RenderActors(engineInstance, CommandHandle, modelsCache, materialsCache, true);
vkCmdEndRendering(CommandHandle);
GPUTIME += System.nanoTime() - InitialTime;
CYCLES++;
}
}

View file

@ -11,16 +11,23 @@ display_width=640
#the precision of frames, set this to 16000000, and it will limit the whole engine to 64FPS
frame_accuracy=0
throttle_on_unfocus=true
throttle_accuracy=500000000
throttle_accuracy=100000000
#event tick rate for game process
master_tick_rate=120
master_tick_rate=240
main_thread_tickrate=120
fast_thread_tickrate=240
cap_master_to_tickrate=false
cap_master_to_tickrate=true
cap_main_to_tickrate=true
cap_fast_to_tickrate=true
#Renderer Properties
#AA settings
#0=No AA
#1=FXAA
#2=MSAAx2
#3=MSAAx4
#4=MSAAx8
anti_alias_mode=0
Debug_Shaders = false
ShaderRecompiling = true
#Vulkan Debugging toggle
@ -34,5 +41,7 @@ PhysicalDeviceName=
#3D Projection Properties
field_of_view=60.0f
z_near_plane=1.0f
z_far_plane=100.0f
z_far_plane=0.1f
z_near_plane=10000.0f