Skip to content

Commit

Permalink
Model Viewer: Improve mouse capture; code cleanup and add perf metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadelessFox committed May 20, 2024
1 parent 8a13bdb commit 9536db0
Show file tree
Hide file tree
Showing 15 changed files with 496 additions and 514 deletions.
27 changes: 27 additions & 0 deletions modules/bundle-lwjgl/src/main/java/com/shade/gl/DebugGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.shade.gl;

import com.shade.util.NotNull;
import org.lwjgl.opengl.GL43;

/**
* Creates a named debug group for the current OpenGL context.
* <p>
* <b>Note</b>: you can't reuse the same instance of this class. It's meant to be used in a try-with-resources block.
* <blockquote><pre>
* try (var group = new DebugGroup("My Group")) {
* // OpenGL calls
* }</pre>
* </blockquote>
*
* @param name the name of the debug group
*/
public record DebugGroup(@NotNull String name) implements AutoCloseable {
public DebugGroup {
GL43.glPushDebugGroup(GL43.GL_DEBUG_SOURCE_APPLICATION, 0, name);
}

@Override
public void close() {
GL43.glPopDebugGroup();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import com.formdev.flatlaf.FlatClientProperties;
import com.shade.decima.model.rtti.objects.RTTIObject;
import com.shade.decima.model.viewer.Camera;
import com.shade.decima.model.viewer.ModelViewport;
import com.shade.decima.model.viewer.RenderLoop;
import com.shade.decima.model.viewer.camera.FirstPersonCamera;
import com.shade.decima.model.viewer.isr.Node;
import com.shade.decima.model.viewer.isr.impl.NodeModel;
import com.shade.decima.ui.data.ValueController;
Expand All @@ -15,14 +14,22 @@
import com.shade.platform.ui.UIColor;
import com.shade.platform.ui.dialogs.ProgressDialog;
import com.shade.platform.ui.menus.MenuManager;
import com.shade.util.NotNull;
import com.shade.util.Nullable;
import org.lwjgl.opengl.awt.AWTGLCanvas;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ModelViewerPanel extends JComponent implements Disposable, PropertyChangeListener {
public static final DataKey<ModelViewerPanel> PANEL_KEY = new DataKey<>("panel", ModelViewerPanel.class);
Expand All @@ -46,7 +53,7 @@ public ModelViewerPanel() {
add(bottomToolbar, BorderLayout.SOUTH);

try {
viewport = new ModelViewport(new FirstPersonCamera());
viewport = new ModelViewport(new Camera());
viewport.setPreferredSize(new Dimension(400, 400));
viewport.setMinimumSize(new Dimension(100, 100));
viewport.addPropertyChangeListener(this);
Expand Down Expand Up @@ -163,4 +170,150 @@ private void updatePreview() {
public ValueController<RTTIObject> getController() {
return controller;
}

private static class RenderLoop extends Thread implements Disposable {
private final Window window;
private final AWTGLCanvas canvas;

private final Handler handler;

private final AtomicBoolean isRunning = new AtomicBoolean(true);
private final AtomicBoolean isThrottling = new AtomicBoolean(false);

private final Lock renderLock = new ReentrantLock();
private final Condition canRender = renderLock.newCondition();

public RenderLoop(@NotNull Window window, @NotNull AWTGLCanvas canvas) {
super("Render Loop");

this.window = window;
this.canvas = canvas;
this.handler = new Handler();

canvas.addHierarchyListener(handler);
canvas.addComponentListener(handler);
window.addWindowListener(handler);
}

@Override
public void run() {
while (isRunning.get()) {
try {
renderLock.lock();

while (isThrottling.get()) {
canRender.awaitUninterruptibly();
}
} finally {
renderLock.unlock();
}

try {
SwingUtilities.invokeAndWait(() -> {
beforeRender();

if (canvas.isValid()) {
canvas.render();
}

afterRender();
});
} catch (InterruptedException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}

canvas.removeHierarchyListener(handler);
window.removeWindowListener(handler);
}

@Override
public void dispose() {
isRunning.set(false);
}

protected void beforeRender() {
// do nothing by default
}

protected void afterRender() {
// do nothing by default
}

private class Handler extends WindowAdapter implements HierarchyListener, ComponentListener {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if (e.getID() == HierarchyEvent.HIERARCHY_CHANGED && (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
handle();
}
}

@Override
public void componentResized(ComponentEvent e) {
handle();
}

@Override
public void componentMoved(ComponentEvent e) {
handle();
}

@Override
public void componentShown(ComponentEvent e) {
handle();
}

@Override
public void componentHidden(ComponentEvent e) {
handle();
}

@Override
public void windowActivated(WindowEvent e) {
handleAsync();
}

@Override
public void windowDeactivated(WindowEvent e) {
handleAsync();
}

private void handleAsync() {
SwingUtilities.invokeLater(this::handle);
}

private void handle() {
renderLock.lock();

try {
isThrottling.set(isThrottling());
canRender.signal();
} finally {
renderLock.unlock();
}
}

private boolean isThrottling() {
return !canvas.isShowing() || canvas.getWidth() <= 0 || canvas.getHeight() <= 0 || !isActive(window);
}

private static boolean isActive(@NotNull Window window) {
if (window instanceof Dialog dialog && dialog.getModalityType() != Dialog.ModalityType.MODELESS) {
return false;
}

if (window.isActive()) {
return true;
}

for (Window ownedWindow : window.getOwnedWindows()) {
if (isActive(ownedWindow)) {
return true;
}
}

return false;
}
}
}
}
Loading

0 comments on commit 9536db0

Please sign in to comment.