View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.potential;
39  
40  import ffx.potential.bonded.MSNode;
41  import ffx.potential.bonded.MSRoot;
42  import ffx.potential.bonded.ROLSP;
43  import ffx.potential.bonded.RendererCache;
44  import org.jogamp.java3d.Behavior;
45  import org.jogamp.java3d.Bounds;
46  import org.jogamp.java3d.BranchGroup;
47  import org.jogamp.java3d.WakeupCriterion;
48  import org.jogamp.java3d.WakeupOnBehaviorPost;
49  
50  import javax.swing.JLabel;
51  import javax.swing.tree.TreeNode;
52  import java.util.ArrayList;
53  import java.util.Enumeration;
54  import java.util.Iterator;
55  import java.util.logging.Logger;
56  
57  /**
58   * The Renderer class attempts to maximize throughput of graphics operations on MolecularAssembly
59   * instances.
60   *
61   * @author Michael J. Schnieders
62   * @since 1.0
63   */
64  public class Renderer extends Behavior {
65  
66    private static final Logger logger = Logger.getLogger(Renderer.class.getName());
67    private static long frameNumber = 0;
68    private static long frameDuration;
69    private ArrayList<MSNode> nodesToUpdate = null;
70    private ArrayList<MSNode> nodesCache = null;
71    private boolean doTransform, doView, doColor;
72    private boolean doTransformCache, doViewCache, doColorCache;
73    private final JLabel statusBar;
74    private RendererCache.ViewModel viewModel, viewModelCache;
75    private RendererCache.ColorModel colorModel, colorModelCache;
76    private WakeupOnBehaviorPost postid;
77    private boolean timer = false;
78    private boolean gc = false;
79  
80    /**
81     * Constructor
82     *
83     * @param b Bounds of this behavior
84     * @param s JLabel for status
85     */
86    public Renderer(Bounds b, JLabel s) {
87      setSchedulingBounds(b);
88      statusBar = s;
89    }
90  
91    /**
92     * This node arms UpdateBehavior with a graphics operation to carry out
93     *
94     * @param nodes Nodes where the operation will be performed
95     * @param t     True for a change in atomic position
96     * @param v     True for a change in rendering method
97     * @param vtype The rendering method to use
98     * @param c     True for a change in rendering color
99     * @param ctype The coloring method to use
100    */
101   public void arm(
102       ArrayList<MSNode> nodes,
103       boolean t,
104       boolean v,
105       RendererCache.ViewModel vtype,
106       boolean c,
107       RendererCache.ColorModel ctype) {
108     // If the node isn't null, the last rendering
109     // operation hasn't finished so one operation will be cached
110     if (nodesToUpdate != null) {
111       nodesCache = nodes;
112       doTransformCache = t;
113       doViewCache = v;
114       viewModelCache = vtype;
115       doColorCache = c;
116       colorModelCache = ctype;
117     } else {
118       nodesToUpdate = nodes;
119       doTransform = t;
120       doView = v;
121       viewModel = vtype;
122       doColor = c;
123       colorModel = ctype;
124       postId(1);
125     }
126   }
127 
128   /**
129    * arm
130    *
131    * @param node  a {@link ffx.potential.bonded.MSNode} object.
132    * @param t     a boolean.
133    * @param v     a boolean.
134    * @param vtype a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
135    * @param c     a boolean.
136    * @param ctype a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
137    */
138   public void arm(
139       MSNode node,
140       boolean t,
141       boolean v,
142       RendererCache.ViewModel vtype,
143       boolean c,
144       RendererCache.ColorModel ctype) {
145     ArrayList<MSNode> temp = new ArrayList<>();
146     temp.add(node);
147     arm(temp, t, v, vtype, c, ctype);
148   }
149 
150   /**
151    * {@inheritDoc}
152    *
153    * <p>Initialize this behavior to respond to postID messages
154    */
155   @Override
156   public void initialize() {
157     postid = new WakeupOnBehaviorPost(this, 1);
158     wakeupOn(postid);
159   }
160 
161   /**
162    * Check to see if a graphics operation is pending/executing
163    *
164    * @return Whether a node has been cued
165    */
166   public boolean isArmed() {
167     return nodesToUpdate != null;
168   }
169 
170   /**
171    * isCacheFull
172    *
173    * @return a boolean.
174    */
175   public boolean isCacheFull() {
176     return nodesCache != null;
177   }
178 
179   /**
180    * {@inheritDoc}
181    *
182    * <p>This method is called by the Java3D Behavior thread after the following sequence of events:
183    * 1.) A graphics operation is loaded using the "arm" method. 2.) The PostID call is processed by
184    * the Java3D Behavior Thread.
185    */
186   @Override
187   public void processStimulus(Iterator<WakeupCriterion> parm1) {
188     // Do not perform two operations before the frame has be refreshed.
189     if (getView().getFrameNumber() == frameNumber) {
190       System.out.print(".");
191       wakeupOn(postid);
192       postId(1);
193       return;
194     }
195     // Check that the requested View and Color Models are known.
196     String viewString = null;
197     String colorString = null;
198     if (viewModel != null) {
199       try {
200         viewString = viewModel.toString();
201       } catch (Exception e) {
202         statusBar.setText("Unknown ViewModel: " + viewModel);
203         return;
204       }
205     }
206     if (colorModel != null) {
207       try {
208         colorString = colorModel.toString();
209       } catch (Exception e) {
210         statusBar.setText("Unknown ColorModel: " + colorModel);
211         return;
212       }
213     }
214     if (timer) {
215       startTimer();
216       if (viewString != null) {
217         logger.info("Applying ViewModel Change: " + viewString);
218       }
219       if (colorString != null) {
220         System.out.println("Applying ColorModel Change: " + colorString);
221       }
222     }
223     // Perform the requested rendering operation
224     ArrayList<ArrayList<BranchGroup>> newChildren = new ArrayList<>();
225     for (MSNode nodeToUpdate : nodesToUpdate) {
226       if (nodeToUpdate == null) {
227         continue;
228       }
229       if (doTransform) {
230         nodeToUpdate.update();
231       }
232       if (doColor) {
233         nodeToUpdate.setColor(colorModel, null, null);
234         if (statusBar != null) {
235           statusBar.setText("  Color by \"" + colorString + "\" applied to " + nodeToUpdate);
236         }
237       }
238       if (doView) {
239         ArrayList<BranchGroup> newShapes = new ArrayList<>();
240         newChildren.add(newShapes);
241         nodeToUpdate.setView(viewModel, newShapes);
242         if (statusBar != null) {
243           statusBar.setText("  Style \"" + viewString + "\" applied to " + nodeToUpdate);
244         }
245       }
246     }
247     // Wait for the parallel nodes to finish
248     try {
249       if (ROLSP.GO_PARALLEL && ROLSP.parallelNotDone > 0) {
250         logger.info("Renderer waiting for " + ROLSP.parallelNotDone + " processes...");
251       }
252       while (ROLSP.GO_PARALLEL && ROLSP.parallelNotDone > 0) {
253         synchronized (this) {
254           wait(10);
255         }
256       }
257     } catch (Exception e) {
258       System.out.println("Exception Waiting for Parallel MultiScale Methods to Finish");
259     } finally {
260       // If there are new children, they can not be added in parallel
261       // because Java3D does not seem to be thread safe.
262       // (There are ArrayList that are not synchronized).
263       // Here we will add them one at a time. The cases are setView being
264       // called on nodes below the
265       // Scenegraph attachment points (MolecularAssemblies), setView being
266       // called on the root node, setView
267       // being called on a ParallelMSM node, or setView being called on
268       // the MolecularAssembly.
269       for (int i = 0; i < nodesToUpdate.size(); i++) {
270         if (newChildren.isEmpty()) {
271           break;
272         }
273         MSNode nodeToUpdate = nodesToUpdate.get(i);
274         if (nodeToUpdate == null) {
275           continue;
276         }
277         if (nodeToUpdate instanceof MolecularAssembly ma) {
278           ma.sceneGraphChange(null);
279         } else if (nodeToUpdate instanceof ROLSP) {
280           MolecularAssembly ma = (MolecularAssembly) nodeToUpdate.getChildAt(0);
281           ma.sceneGraphChange(null);
282         } else if (nodeToUpdate instanceof MSRoot) {
283           for (Enumeration<TreeNode> e = nodeToUpdate.children(); e.hasMoreElements(); ) {
284             MSNode updatedNode = (MSNode) e.nextElement();
285             MolecularAssembly ma;
286             if (updatedNode instanceof ROLSP) {
287               ma = (MolecularAssembly) updatedNode.getChildAt(0);
288             } else {
289               ma = (MolecularAssembly) updatedNode;
290             }
291             ma.sceneGraphChange(null);
292           }
293         } else {
294           ArrayList<BranchGroup> newShapes = newChildren.get(i);
295           if (!newShapes.isEmpty()) {
296             MolecularAssembly ma = nodeToUpdate.getMSNode(MolecularAssembly.class);
297             ma.sceneGraphChange(newShapes);
298           }
299         }
300       }
301     }
302     if (timer) {
303       stopTimer();
304     }
305     nodesToUpdate = null;
306     wakeupOn(postid);
307     if (nodesCache != null) {
308       nodesToUpdate = nodesCache;
309       doTransform = doTransformCache;
310       doView = doViewCache;
311       viewModel = viewModelCache;
312       doColor = doColorCache;
313       colorModel = colorModelCache;
314       nodesCache = null;
315       postId(1);
316     }
317   }
318 
319   private void startTimer() {
320     Runtime runtime = Runtime.getRuntime();
321     frameDuration = getView().getLastFrameDuration();
322     if (gc) {
323       logger.info(" Running GC for accurate memory usage.");
324       runtime.gc();
325       logger.info(" Done\n Proceeding with graphics operation...");
326     }
327   }
328 
329   private void stopTimer() {
330     Runtime runtime = Runtime.getRuntime();
331     frameNumber = getView().getFrameNumber();
332     frameDuration = getView().getLastFrameDuration();
333     logger.info(" Frame Duration After Op: " + frameDuration / 1000);
334     if (gc) {
335       runtime.gc();
336       logger.info(" Done");
337     }
338   }
339 }