1 package com.jme3.system.awt; 2 3 import com.jme3.post.SceneProcessor; 4 import com.jme3.renderer.RenderManager; 5 import com.jme3.renderer.ViewPort; 6 import com.jme3.renderer.queue.RenderQueue; 7 import com.jme3.texture.FrameBuffer; 8 import com.jme3.texture.Image.Format; 9 import com.jme3.util.BufferUtils; 10 import com.jme3.util.Screenshots; 11 import java.awt.*; 12 import java.awt.event.ComponentAdapter; 13 import java.awt.event.ComponentEvent; 14 import java.awt.geom.AffineTransform; 15 import java.awt.image.AffineTransformOp; 16 import java.awt.image.BufferStrategy; 17 import java.awt.image.BufferedImage; 18 import java.nio.ByteBuffer; 19 import java.nio.IntBuffer; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 import java.util.concurrent.atomic.AtomicBoolean; 23 24 public class AwtPanel extends Canvas implements SceneProcessor { 25 26 private boolean attachAsMain = false; 27 28 private BufferedImage img; 29 private FrameBuffer fb; 30 private ByteBuffer byteBuf; 31 private IntBuffer intBuf; 32 private RenderManager rm; 33 private PaintMode paintMode; 34 private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>(); 35 36 // Visibility/drawing vars 37 private BufferStrategy strategy; 38 private AffineTransformOp transformOp; 39 private AtomicBoolean hasNativePeer = new AtomicBoolean(false); 40 private AtomicBoolean showing = new AtomicBoolean(false); 41 private AtomicBoolean repaintRequest = new AtomicBoolean(false); 42 43 // Reshape vars 44 private int newWidth = 1; 45 private int newHeight = 1; 46 private AtomicBoolean reshapeNeeded = new AtomicBoolean(false); 47 private final Object lock = new Object(); 48 AwtPanel(PaintMode paintMode)49 public AwtPanel(PaintMode paintMode){ 50 this.paintMode = paintMode; 51 52 if (paintMode == PaintMode.Accelerated){ 53 setIgnoreRepaint(true); 54 } 55 56 addComponentListener(new ComponentAdapter(){ 57 @Override 58 public void componentResized(ComponentEvent e) { 59 synchronized (lock){ 60 int newWidth2 = Math.max(getWidth(), 1); 61 int newHeight2 = Math.max(getHeight(), 1); 62 if (newWidth != newWidth2 || newHeight != newHeight2){ 63 newWidth = newWidth2; 64 newHeight = newHeight2; 65 reshapeNeeded.set(true); 66 System.out.println("EDT: componentResized " + newWidth + ", " + newHeight); 67 } 68 } 69 } 70 }); 71 } 72 73 @Override addNotify()74 public void addNotify(){ 75 super.addNotify(); 76 77 synchronized (lock){ 78 hasNativePeer.set(true); 79 System.out.println("EDT: addNotify"); 80 } 81 82 requestFocusInWindow(); 83 } 84 85 @Override removeNotify()86 public void removeNotify(){ 87 synchronized (lock){ 88 hasNativePeer.set(false); 89 System.out.println("EDT: removeNotify"); 90 } 91 92 super.removeNotify(); 93 } 94 95 @Override paint(Graphics g)96 public void paint(Graphics g){ 97 Graphics2D g2d = (Graphics2D) g; 98 synchronized (lock){ 99 g2d.drawImage(img, transformOp, 0, 0); 100 } 101 } 102 checkVisibilityState()103 public boolean checkVisibilityState(){ 104 if (!hasNativePeer.get()){ 105 if (strategy != null){ 106 // strategy.dispose(); 107 strategy = null; 108 System.out.println("OGL: Not visible. Destroy strategy."); 109 } 110 return false; 111 } 112 113 boolean currentShowing = isShowing(); 114 if (showing.getAndSet(currentShowing) != currentShowing){ 115 if (currentShowing){ 116 System.out.println("OGL: Enter showing state."); 117 }else{ 118 System.out.println("OGL: Exit showing state."); 119 } 120 } 121 return currentShowing; 122 } 123 repaintInThread()124 public void repaintInThread(){ 125 // Convert screenshot. 126 byteBuf.clear(); 127 rm.getRenderer().readFrameBuffer(fb, byteBuf); 128 129 synchronized (lock){ 130 // All operations on img must be synchronized 131 // as it is accessed from EDT. 132 Screenshots.convertScreenShot2(intBuf, img); 133 repaint(); 134 } 135 } 136 drawFrameInThread()137 public void drawFrameInThread(){ 138 // Convert screenshot. 139 byteBuf.clear(); 140 rm.getRenderer().readFrameBuffer(fb, byteBuf); 141 Screenshots.convertScreenShot2(intBuf, img); 142 143 synchronized (lock){ 144 // All operations on strategy should be synchronized (?) 145 if (strategy == null){ 146 try { 147 createBufferStrategy(1, 148 new BufferCapabilities( 149 new ImageCapabilities(true), 150 new ImageCapabilities(true), 151 BufferCapabilities.FlipContents.UNDEFINED) 152 ); 153 } catch (AWTException ex) { 154 ex.printStackTrace(); 155 } 156 strategy = getBufferStrategy(); 157 System.out.println("OGL: Visible. Create strategy."); 158 } 159 160 // Draw screenshot. 161 do { 162 do { 163 Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics(); 164 if (g2d == null){ 165 System.out.println("OGL: DrawGraphics was null."); 166 return; 167 } 168 169 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 170 RenderingHints.VALUE_RENDER_SPEED); 171 172 g2d.drawImage(img, transformOp, 0, 0); 173 g2d.dispose(); 174 strategy.show(); 175 } while (strategy.contentsRestored()); 176 } while (strategy.contentsLost()); 177 } 178 } 179 isActiveDrawing()180 public boolean isActiveDrawing(){ 181 return paintMode != PaintMode.OnRequest && showing.get(); 182 } 183 attachTo(boolean overrideMainFramebuffer, ViewPort ... vps)184 public void attachTo(boolean overrideMainFramebuffer, ViewPort ... vps){ 185 if (viewPorts.size() > 0){ 186 for (ViewPort vp : viewPorts){ 187 vp.setOutputFrameBuffer(null); 188 } 189 viewPorts.get(viewPorts.size()-1).removeProcessor(this); 190 } 191 192 viewPorts.addAll(Arrays.asList(vps)); 193 viewPorts.get(viewPorts.size()-1).addProcessor(this); 194 195 this.attachAsMain = overrideMainFramebuffer; 196 } 197 initialize(RenderManager rm, ViewPort vp)198 public void initialize(RenderManager rm, ViewPort vp) { 199 if (this.rm == null){ 200 // First time called in OGL thread 201 this.rm = rm; 202 reshapeInThread(1, 1); 203 } 204 } 205 reshapeInThread(int width, int height)206 private void reshapeInThread(int width, int height) { 207 byteBuf = BufferUtils.ensureLargeEnough(byteBuf, width * height * 4); 208 intBuf = byteBuf.asIntBuffer(); 209 210 fb = new FrameBuffer(width, height, 1); 211 fb.setDepthBuffer(Format.Depth); 212 fb.setColorBuffer(Format.RGB8); 213 214 if (attachAsMain){ 215 rm.getRenderer().setMainFrameBufferOverride(fb); 216 } 217 218 synchronized (lock){ 219 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 220 } 221 222 // synchronized (lock){ 223 // img = (BufferedImage) getGraphicsConfiguration().createCompatibleImage(width, height); 224 // } 225 226 AffineTransform tx = AffineTransform.getScaleInstance(1, -1); 227 tx.translate(0, -img.getHeight()); 228 transformOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); 229 230 for (ViewPort vp : viewPorts){ 231 if (!attachAsMain){ 232 vp.setOutputFrameBuffer(fb); 233 } 234 vp.getCamera().resize(width, height, true); 235 236 // NOTE: Hack alert. This is done ONLY for custom framebuffers. 237 // Main framebuffer should use RenderManager.notifyReshape(). 238 for (SceneProcessor sp : vp.getProcessors()){ 239 sp.reshape(vp, width, height); 240 } 241 } 242 } 243 isInitialized()244 public boolean isInitialized() { 245 return fb != null; 246 } 247 preFrame(float tpf)248 public void preFrame(float tpf) { 249 } 250 postQueue(RenderQueue rq)251 public void postQueue(RenderQueue rq) { 252 } 253 254 @Override invalidate()255 public void invalidate(){ 256 // For "PaintMode.OnDemand" only. 257 repaintRequest.set(true); 258 } 259 postFrame(FrameBuffer out)260 public void postFrame(FrameBuffer out) { 261 if (!attachAsMain && out != fb){ 262 throw new IllegalStateException("Why did you change the output framebuffer?"); 263 } 264 265 if (reshapeNeeded.getAndSet(false)){ 266 reshapeInThread(newWidth, newHeight); 267 }else{ 268 if (!checkVisibilityState()){ 269 return; 270 } 271 272 switch (paintMode){ 273 case Accelerated: 274 drawFrameInThread(); 275 break; 276 case Repaint: 277 repaintInThread(); 278 break; 279 case OnRequest: 280 if (repaintRequest.getAndSet(false)){ 281 repaintInThread(); 282 } 283 break; 284 } 285 } 286 } 287 reshape(ViewPort vp, int w, int h)288 public void reshape(ViewPort vp, int w, int h) { 289 } 290 cleanup()291 public void cleanup() { 292 } 293 } 294