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-2024.
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  
45  import java.util.ArrayList;
46  import java.util.Enumeration;
47  import java.util.Iterator;
48  import java.util.logging.Logger;
49  import javax.swing.JLabel;
50  import javax.swing.tree.TreeNode;
51  
52  import org.jogamp.java3d.*;
53  
54  /**
55   * The Renderer class attempts to maximize throughput of graphics operations on MolecularAssembly
56   * instances.
57   *
58   * @author Michael J. Schnieders
59   * @since 1.0
60   */
61  public class Renderer extends Behavior {
62  
63    private static final Logger logger = Logger.getLogger(Renderer.class.getName());
64    private static long frameNumber = 0;
65    private static long frameDuration;
66    private ArrayList<MSNode> nodesToUpdate = null;
67    private ArrayList<MSNode> nodesCache = null;
68    private boolean doTransform, doView, doColor;
69    private boolean doTransformCache, doViewCache, doColorCache;
70    private final JLabel statusBar;
71    private RendererCache.ViewModel viewModel, viewModelCache;
72    private RendererCache.ColorModel colorModel, colorModelCache;
73    private WakeupOnBehaviorPost postid;
74    private boolean timer = false;
75    private boolean gc = false;
76  
77    /**
78     * Constructor
79     *
80     * @param b Bounds of this behavior
81     * @param s JLabel for status
82     */
83    public Renderer(Bounds b, JLabel s) {
84      setSchedulingBounds(b);
85      statusBar = s;
86    }
87  
88    /**
89     * This node arms UpdateBehavior with a graphics operation to carry out
90     *
91     * @param nodes Nodes where the operation will be performed
92     * @param t     True for a change in atomic position
93     * @param v     True for a change in rendering method
94     * @param vtype The rendering method to use
95     * @param c     True for a change in rendering color
96     * @param ctype The coloring method to use
97     */
98    public void arm(
99        ArrayList<MSNode> nodes,
100       boolean t,
101       boolean v,
102       RendererCache.ViewModel vtype,
103       boolean c,
104       RendererCache.ColorModel ctype) {
105     // If the node isn't null, the last rendering
106     // operation hasn't finished so one operation will be cached
107     if (nodesToUpdate != null) {
108       nodesCache = nodes;
109       doTransformCache = t;
110       doViewCache = v;
111       viewModelCache = vtype;
112       doColorCache = c;
113       colorModelCache = ctype;
114     } else {
115       nodesToUpdate = nodes;
116       doTransform = t;
117       doView = v;
118       viewModel = vtype;
119       doColor = c;
120       colorModel = ctype;
121       postId(1);
122     }
123   }
124 
125   /**
126    * arm
127    *
128    * @param node  a {@link ffx.potential.bonded.MSNode} object.
129    * @param t     a boolean.
130    * @param v     a boolean.
131    * @param vtype a {@link ffx.potential.bonded.RendererCache.ViewModel} object.
132    * @param c     a boolean.
133    * @param ctype a {@link ffx.potential.bonded.RendererCache.ColorModel} object.
134    */
135   public void arm(
136       MSNode node,
137       boolean t,
138       boolean v,
139       RendererCache.ViewModel vtype,
140       boolean c,
141       RendererCache.ColorModel ctype) {
142     ArrayList<MSNode> temp = new ArrayList<>();
143     temp.add(node);
144     arm(temp, t, v, vtype, c, ctype);
145   }
146 
147   /**
148    * {@inheritDoc}
149    *
150    * <p>Initialize this behavior to respond to postID messages
151    */
152   @Override
153   public void initialize() {
154     postid = new WakeupOnBehaviorPost(this, 1);
155     wakeupOn(postid);
156   }
157 
158   /**
159    * Check to see if a graphics operation is pending/executing
160    *
161    * @return Whether a node has been cued
162    */
163   public boolean isArmed() {
164     return nodesToUpdate != null;
165   }
166 
167   /**
168    * isCacheFull
169    *
170    * @return a boolean.
171    */
172   public boolean isCacheFull() {
173     return nodesCache != null;
174   }
175 
176   /**
177    * {@inheritDoc}
178    *
179    * <p>This method is called by the Java3D Behavior thread after the following sequence of events:
180    * 1.) A graphics operation is loaded using the "arm" method. 2.) The PostID call is processed by
181    * the Java3D Behavior Thread.
182    */
183   @Override
184   public void processStimulus(Iterator<WakeupCriterion> parm1) {
185     // Do not perform two operations before the frame has be refreshed.
186     if (getView().getFrameNumber() == frameNumber) {
187       System.out.print(".");
188       wakeupOn(postid);
189       postId(1);
190       return;
191     }
192     // Check that the requested View and Color Models are known.
193     String viewString = null;
194     String colorString = null;
195     if (viewModel != null) {
196       try {
197         viewString = viewModel.toString();
198       } catch (Exception e) {
199         statusBar.setText("Unknown ViewModel: " + viewModel);
200         return;
201       }
202     }
203     if (colorModel != null) {
204       try {
205         colorString = colorModel.toString();
206       } catch (Exception e) {
207         statusBar.setText("Unknown ColorModel: " + colorModel);
208         return;
209       }
210     }
211     if (timer) {
212       startTimer();
213       if (viewString != null) {
214         logger.info("Applying ViewModel Change: " + viewString);
215       }
216       if (colorString != null) {
217         System.out.println("Applying ColorModel Change: " + colorString);
218       }
219     }
220     // Perform the requested rendering operation
221     ArrayList<ArrayList<BranchGroup>> newChildren = new ArrayList<>();
222     for (MSNode nodeToUpdate : nodesToUpdate) {
223       if (nodeToUpdate == null) {
224         continue;
225       }
226       if (doTransform) {
227         nodeToUpdate.update();
228       }
229       if (doColor) {
230         nodeToUpdate.setColor(colorModel, null, null);
231         if (statusBar != null) {
232           statusBar.setText("  Color by \"" + colorString + "\" applied to " + nodeToUpdate);
233         }
234       }
235       if (doView) {
236         ArrayList<BranchGroup> newShapes = new ArrayList<>();
237         newChildren.add(newShapes);
238         nodeToUpdate.setView(viewModel, newShapes);
239         if (statusBar != null) {
240           statusBar.setText("  Style \"" + viewString + "\" applied to " + nodeToUpdate);
241         }
242       }
243     }
244     // Wait for the parallel nodes to finish
245     try {
246       if (ROLSP.GO_PARALLEL && ROLSP.parallelNotDone > 0) {
247         logger.info("Renderer waiting for " + ROLSP.parallelNotDone + " processes...");
248       }
249       while (ROLSP.GO_PARALLEL && ROLSP.parallelNotDone > 0) {
250         synchronized (this) {
251           wait(10);
252         }
253       }
254     } catch (Exception e) {
255       System.out.println("Exception Waiting for Parallel MultiScale Methods to Finish");
256     } finally {
257       // If there are new children, they can not be added in parallel
258       // because Java3D does not seem to be thread safe.
259       // (There are ArrayList that are not synchronized).
260       // Here we will add them one at a time. The cases are setView being
261       // called on nodes below the
262       // Scenegraph attachment points (MolecularAssemblies), setView being
263       // called on the root node, setView
264       // being called on a ParallelMSM node, or setView being called on
265       // the MolecularAssembly.
266       for (int i = 0; i < nodesToUpdate.size(); i++) {
267         if (newChildren.isEmpty()) {
268           break;
269         }
270         MSNode nodeToUpdate = nodesToUpdate.get(i);
271         if (nodeToUpdate == null) {
272           continue;
273         }
274         if (nodeToUpdate instanceof MolecularAssembly ma) {
275           ma.sceneGraphChange(null);
276         } else if (nodeToUpdate instanceof ROLSP) {
277           MolecularAssembly ma = (MolecularAssembly) nodeToUpdate.getChildAt(0);
278           ma.sceneGraphChange(null);
279         } else if (nodeToUpdate instanceof MSRoot) {
280           for (Enumeration<TreeNode> e = nodeToUpdate.children(); e.hasMoreElements(); ) {
281             MSNode updatedNode = (MSNode) e.nextElement();
282             MolecularAssembly ma;
283             if (updatedNode instanceof ROLSP) {
284               ma = (MolecularAssembly) updatedNode.getChildAt(0);
285             } else {
286               ma = (MolecularAssembly) updatedNode;
287             }
288             ma.sceneGraphChange(null);
289           }
290         } else {
291           ArrayList<BranchGroup> newShapes = newChildren.get(i);
292           if (!newShapes.isEmpty()) {
293             MolecularAssembly ma = nodeToUpdate.getMSNode(MolecularAssembly.class);
294             ma.sceneGraphChange(newShapes);
295           }
296         }
297       }
298     }
299     if (timer) {
300       stopTimer();
301     }
302     nodesToUpdate = null;
303     wakeupOn(postid);
304     if (nodesCache != null) {
305       nodesToUpdate = nodesCache;
306       doTransform = doTransformCache;
307       doView = doViewCache;
308       viewModel = viewModelCache;
309       doColor = doColorCache;
310       colorModel = colorModelCache;
311       nodesCache = null;
312       postId(1);
313     }
314   }
315 
316   private void startTimer() {
317     Runtime runtime = Runtime.getRuntime();
318     frameDuration = getView().getLastFrameDuration();
319     if (gc) {
320       logger.info(" Running GC for accurate memory usage.");
321       runtime.gc();
322       logger.info(" Done\n Proceeding with graphics operation...");
323     }
324   }
325 
326   private void stopTimer() {
327     Runtime runtime = Runtime.getRuntime();
328     frameNumber = getView().getFrameNumber();
329     frameDuration = getView().getLastFrameDuration();
330     logger.info(" Frame Duration After Op: " + frameDuration / 1000);
331     if (gc) {
332       runtime.gc();
333       logger.info(" Done");
334     }
335   }
336 }