1package com.jme3.system.awt;
2
3import com.jme3.post.SceneProcessor;
4import com.jme3.renderer.RenderManager;
5import com.jme3.renderer.ViewPort;
6import com.jme3.renderer.queue.RenderQueue;
7import com.jme3.texture.FrameBuffer;
8import com.jme3.texture.Image.Format;
9import com.jme3.util.BufferUtils;
10import com.jme3.util.Screenshots;
11import java.awt.*;
12import java.awt.event.ComponentAdapter;
13import java.awt.event.ComponentEvent;
14import java.awt.geom.AffineTransform;
15import java.awt.image.AffineTransformOp;
16import java.awt.image.BufferStrategy;
17import java.awt.image.BufferedImage;
18import java.nio.ByteBuffer;
19import java.nio.IntBuffer;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.concurrent.atomic.AtomicBoolean;
23
24public 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
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
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
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
96    public void paint(Graphics g){
97        Graphics2D g2d = (Graphics2D) g;
98        synchronized (lock){
99            g2d.drawImage(img, transformOp, 0, 0);
100        }
101    }
102
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
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
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
180    public boolean isActiveDrawing(){
181        return paintMode != PaintMode.OnRequest && showing.get();
182    }
183
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
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
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
244    public boolean isInitialized() {
245        return fb != null;
246    }
247
248    public void preFrame(float tpf) {
249    }
250
251    public void postQueue(RenderQueue rq) {
252    }
253
254    @Override
255    public void invalidate(){
256        // For "PaintMode.OnDemand" only.
257        repaintRequest.set(true);
258    }
259
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
288    public void reshape(ViewPort vp, int w, int h) {
289    }
290
291    public void cleanup() {
292    }
293}
294