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 jme3test.post;
34
35import com.jme3.app.SimpleApplication;
36import com.jme3.material.Material;
37import com.jme3.math.ColorRGBA;
38import com.jme3.math.FastMath;
39import com.jme3.math.Quaternion;
40import com.jme3.math.Vector3f;
41import com.jme3.post.SceneProcessor;
42import com.jme3.renderer.Camera;
43import com.jme3.renderer.RenderManager;
44import com.jme3.renderer.ViewPort;
45import com.jme3.renderer.queue.RenderQueue;
46import com.jme3.scene.Geometry;
47import com.jme3.scene.shape.Box;
48import com.jme3.system.AppSettings;
49import com.jme3.system.JmeContext.Type;
50import com.jme3.texture.FrameBuffer;
51import com.jme3.texture.Image.Format;
52import com.jme3.texture.Texture2D;
53import com.jme3.util.BufferUtils;
54import com.jme3.util.Screenshots;
55import java.awt.Color;
56import java.awt.Dimension;
57import java.awt.Graphics;
58import java.awt.Graphics2D;
59import java.awt.event.WindowAdapter;
60import java.awt.event.WindowEvent;
61import java.awt.image.BufferedImage;
62import java.nio.ByteBuffer;
63import javax.swing.JFrame;
64import javax.swing.JPanel;
65import javax.swing.SwingUtilities;
66
67/**
68 * This test renders a scene to an offscreen framebuffer, then copies
69 * the contents to a Swing JFrame. Note that some parts are done inefficently,
70 * this is done to make the code more readable.
71 */
72public class TestRenderToMemory extends SimpleApplication implements SceneProcessor {
73
74    private Geometry offBox;
75    private float angle = 0;
76
77    private FrameBuffer offBuffer;
78    private ViewPort offView;
79    private Texture2D offTex;
80    private Camera offCamera;
81    private ImageDisplay display;
82
83    private static final int width = 800, height = 600;
84
85    private final ByteBuffer cpuBuf = BufferUtils.createByteBuffer(width * height * 4);
86    private final byte[] cpuArray = new byte[width * height * 4];
87    private final BufferedImage image = new BufferedImage(width, height,
88                                            BufferedImage.TYPE_4BYTE_ABGR);
89
90    private class ImageDisplay extends JPanel {
91
92        private long t;
93        private long total;
94        private int frames;
95        private int fps;
96
97        @Override
98        public void paintComponent(Graphics gfx) {
99            super.paintComponent(gfx);
100            Graphics2D g2d = (Graphics2D) gfx;
101
102            if (t == 0)
103                t = timer.getTime();
104
105//            g2d.setBackground(Color.BLACK);
106//            g2d.clearRect(0,0,width,height);
107
108            synchronized (image){
109                g2d.drawImage(image, null, 0, 0);
110            }
111
112            long t2 = timer.getTime();
113            long dt = t2 - t;
114            total += dt;
115            frames ++;
116            t = t2;
117
118            if (total > 1000){
119                fps = frames;
120                total = 0;
121                frames = 0;
122            }
123
124            g2d.setColor(Color.white);
125            g2d.drawString("FPS: "+fps, 0, getHeight() - 100);
126        }
127    }
128
129    public static void main(String[] args){
130        TestRenderToMemory app = new TestRenderToMemory();
131        app.setPauseOnLostFocus(false);
132        AppSettings settings = new AppSettings(true);
133        settings.setResolution(1, 1);
134        app.setSettings(settings);
135        app.start(Type.OffscreenSurface);
136    }
137
138    public void createDisplayFrame(){
139        SwingUtilities.invokeLater(new Runnable(){
140            public void run(){
141                JFrame frame = new JFrame("Render Display");
142                display = new ImageDisplay();
143                display.setPreferredSize(new Dimension(width, height));
144                frame.getContentPane().add(display);
145                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
146                frame.addWindowListener(new WindowAdapter(){
147                    public void windowClosed(WindowEvent e){
148                        stop();
149                    }
150                });
151                frame.pack();
152                frame.setLocationRelativeTo(null);
153                frame.setResizable(false);
154                frame.setVisible(true);
155            }
156        });
157    }
158
159    public void updateImageContents(){
160        cpuBuf.clear();
161        renderer.readFrameBuffer(offBuffer, cpuBuf);
162
163        synchronized (image) {
164            Screenshots.convertScreenShot(cpuBuf, image);
165        }
166
167        if (display != null)
168            display.repaint();
169    }
170
171    public void setupOffscreenView(){
172        offCamera = new Camera(width, height);
173
174        // create a pre-view. a view that is rendered before the main view
175        offView = renderManager.createPreView("Offscreen View", offCamera);
176        offView.setBackgroundColor(ColorRGBA.DarkGray);
177        offView.setClearFlags(true, true, true);
178
179        // this will let us know when the scene has been rendered to the
180        // frame buffer
181        offView.addProcessor(this);
182
183        // create offscreen framebuffer
184        offBuffer = new FrameBuffer(width, height, 1);
185
186        //setup framebuffer's cam
187        offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);
188        offCamera.setLocation(new Vector3f(0f, 0f, -5f));
189        offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
190
191        //setup framebuffer's texture
192//        offTex = new Texture2D(width, height, Format.RGBA8);
193
194        //setup framebuffer to use renderbuffer
195        // this is faster for gpu -> cpu copies
196        offBuffer.setDepthBuffer(Format.Depth);
197        offBuffer.setColorBuffer(Format.RGBA8);
198//        offBuffer.setColorTexture(offTex);
199
200        //set viewport to render to offscreen framebuffer
201        offView.setOutputFrameBuffer(offBuffer);
202
203        // setup framebuffer's scene
204        Box boxMesh = new Box(Vector3f.ZERO, 1,1,1);
205        Material material = assetManager.loadMaterial("Interface/Logo/Logo.j3m");
206        offBox = new Geometry("box", boxMesh);
207        offBox.setMaterial(material);
208
209        // attach the scene to the viewport to be rendered
210        offView.attachScene(offBox);
211    }
212
213    @Override
214    public void simpleInitApp() {
215        setupOffscreenView();
216        createDisplayFrame();
217    }
218
219    @Override
220    public void simpleUpdate(float tpf){
221        Quaternion q = new Quaternion();
222        angle += tpf;
223        angle %= FastMath.TWO_PI;
224        q.fromAngles(angle, 0, angle);
225
226        offBox.setLocalRotation(q);
227        offBox.updateLogicalState(tpf);
228        offBox.updateGeometricState();
229    }
230
231    public void initialize(RenderManager rm, ViewPort vp) {
232    }
233
234    public void reshape(ViewPort vp, int w, int h) {
235    }
236
237    public boolean isInitialized() {
238        return true;
239    }
240
241    public void preFrame(float tpf) {
242    }
243
244    public void postQueue(RenderQueue rq) {
245    }
246
247    /**
248     * Update the CPU image's contents after the scene has
249     * been rendered to the framebuffer.
250     */
251    public void postFrame(FrameBuffer out) {
252        updateImageContents();
253    }
254
255    public void cleanup() {
256    }
257
258
259}
260