1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.system.lwjgl;
34
35import com.jme3.input.JoyInput;
36import com.jme3.input.KeyInput;
37import com.jme3.input.MouseInput;
38import com.jme3.input.TouchInput;
39import com.jme3.input.lwjgl.JInputJoyInput;
40import com.jme3.input.lwjgl.LwjglKeyInput;
41import com.jme3.input.lwjgl.LwjglMouseInput;
42import com.jme3.system.AppSettings;
43import com.jme3.system.JmeContext.Type;
44import com.jme3.system.JmeSystem;
45import java.util.concurrent.atomic.AtomicBoolean;
46import java.util.logging.Level;
47import java.util.logging.Logger;
48import org.lwjgl.LWJGLException;
49import org.lwjgl.Sys;
50import org.lwjgl.opengl.Display;
51import org.lwjgl.opengl.OpenGLException;
52import org.lwjgl.opengl.Util;
53
54public abstract class LwjglAbstractDisplay extends LwjglContext implements Runnable {
55
56    private static final Logger logger = Logger.getLogger(LwjglAbstractDisplay.class.getName());
57
58    protected AtomicBoolean needClose = new AtomicBoolean(false);
59    protected boolean wasActive = false;
60    protected int frameRate = 0;
61    protected boolean autoFlush = true;
62
63    /**
64     * @return Type.Display or Type.Canvas
65     */
66    public abstract Type getType();
67
68    /**
69     * Set the title if its a windowed display
70     * @param title
71     */
72    public abstract void setTitle(String title);
73
74    /**
75     * Restart if its a windowed or full-screen display.
76     */
77    public abstract void restart();
78
79    /**
80     * Apply the settings, changing resolution, etc.
81     * @param settings
82     */
83    protected abstract void createContext(AppSettings settings) throws LWJGLException;
84
85    /**
86     * Destroy the context.
87     */
88    protected abstract void destroyContext();
89
90    /**
91     * Does LWJGL display initialization in the OpenGL thread
92     */
93    protected void initInThread(){
94        try{
95            if (!JmeSystem.isLowPermissions()){
96                // Enable uncaught exception handler only for current thread
97                Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
98                    public void uncaughtException(Thread thread, Throwable thrown) {
99                        listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown);
100                        if (needClose.get()){
101                            // listener.handleError() has requested the
102                            // context to close. Satisfy request.
103                            deinitInThread();
104                        }
105                    }
106                });
107            }
108
109            // For canvas, this will create a pbuffer,
110            // allowing us to query information.
111            // When the canvas context becomes available, it will
112            // be replaced seamlessly.
113            createContext(settings);
114            printContextInitInfo();
115
116            created.set(true);
117        } catch (Exception ex){
118            try {
119                if (Display.isCreated())
120                    Display.destroy();
121            } catch (Exception ex2){
122                logger.log(Level.WARNING, null, ex2);
123            }
124
125            listener.handleError("Failed to create display", ex);
126            return; // if we failed to create display, do not continue
127        }
128        super.internalCreate();
129        listener.initialize();
130    }
131
132    protected boolean checkGLError(){
133        try {
134            Util.checkGLError();
135        } catch (OpenGLException ex){
136            listener.handleError("An OpenGL error has occured!", ex);
137        }
138        // NOTE: Always return true since this is used in an "assert" statement
139        return true;
140    }
141
142    /**
143     * execute one iteration of the render loop in the OpenGL thread
144     */
145    protected void runLoop(){
146        if (!created.get())
147            throw new IllegalStateException();
148
149        listener.update();
150
151        // All this does is call swap buffers
152        // If the canvas is not active, there's no need to waste time
153        // doing that ..
154        if (renderable.get()){
155            assert checkGLError();
156
157            // calls swap buffers, etc.
158            try {
159                if (autoFlush){
160                    Display.update(false);
161                }else{
162                    Display.processMessages();
163                    Thread.sleep(50);
164                    // add a small wait
165                    // to reduce CPU usage
166                }
167            } catch (Throwable ex){
168                listener.handleError("Error while swapping buffers", ex);
169            }
170        }
171
172        if (frameRate > 0)
173            Display.sync(frameRate);
174
175        if (renderable.get()){
176            if (autoFlush){
177                // check input after we synchronize with framerate.
178                // this reduces input lag.
179                Display.processMessages();
180            }
181        }
182
183        // Subclasses just call GLObjectManager clean up objects here
184        // it is safe .. for now.
185        renderer.onFrame();
186    }
187
188    /**
189     * De-initialize in the OpenGL thread.
190     */
191    protected void deinitInThread(){
192        destroyContext();
193
194        listener.destroy();
195        logger.info("Display destroyed.");
196        super.internalDestroy();
197    }
198
199    public void run(){
200        if (listener == null)
201            throw new IllegalStateException("SystemListener is not set on context!"
202                                          + "Must set with JmeContext.setSystemListner().");
203
204        logger.log(Level.INFO, "Using LWJGL {0}", Sys.getVersion());
205        initInThread();
206        while (true){
207            if (renderable.get()){
208                if (Display.isCloseRequested())
209                    listener.requestClose(false);
210
211                if (wasActive != Display.isActive()) {
212                    if (!wasActive) {
213                        listener.gainFocus();
214                        timer.reset();
215                        wasActive = true;
216                    } else {
217                        listener.loseFocus();
218                        wasActive = false;
219                    }
220                }
221            }
222
223            runLoop();
224
225            if (needClose.get())
226                break;
227        }
228        deinitInThread();
229    }
230
231    public JoyInput getJoyInput() {
232        if (joyInput == null){
233            joyInput = new JInputJoyInput();
234        }
235        return joyInput;
236    }
237
238    public MouseInput getMouseInput() {
239        if (mouseInput == null){
240            mouseInput = new LwjglMouseInput(this);
241        }
242        return mouseInput;
243    }
244
245    public KeyInput getKeyInput() {
246        if (keyInput == null){
247            keyInput = new LwjglKeyInput(this);
248        }
249        return keyInput;
250    }
251
252    public TouchInput getTouchInput() {
253        return null;
254    }
255
256    public void setAutoFlushFrames(boolean enabled){
257        this.autoFlush = enabled;
258    }
259
260    public void destroy(boolean waitFor){
261        needClose.set(true);
262        if (waitFor)
263            waitFor(false);
264    }
265
266}
267