Cloning 60FPS game into four screen

Viewed 173

I have multiple games running on 60 frame per second rate. I need to capture each frame and clone into four different screen with specific filter.

Software will work on dual screen monitor where primary and secondary screen will be responsible for running game and displaying four filter view respectively.

Limitation on applying filter:

  • Filter will work on image only.
  • Filter is written in java which almost impossible to rewrite.

So I am applying logic something like below:

import java.awt.*;    
import javax.swing.ImageIcon;
import javax.swing.JFrame;    
import com.sun.jna.platform.win32.GDI32Util;
import com.sun.jna.platform.win32.WinDef.HWND;

import java.awt.image.BufferedImage;

public class MultiFrameApplet implements Runnable
{     
    public JFrame currentFrame = null;
    public PanelPaint currentCanvas = null; 
    public BufferedImage screenshotImage = null;
    com.sun.jna.platform.win32.User32 user32 = null;
    HWND hwnd = null;
    Thread th = null;
    String CurrentFrameText = "";
    private long lastTime;
    private double fps; //could be int or long for integer values
    public MultiFrameApplet(int filtertype)
    { 
        main(null,filtertype);      
    }     

    public void main(String[] argv,int filtertype) 
    {   
        try
        {
           GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
           GraphicsDevice[] gd = ge.getScreenDevices();

           //First Screen
           GraphicsConfiguration gcFirst = gd[0].getDefaultConfiguration();
           Toolkit toolkit = Toolkit.getDefaultToolkit();
           if (toolkit == null) 
           {
               return;
           }       
           Rectangle screenRectFirst = gcFirst.getBounds();
           Insets screenInsetsFirst = toolkit.getScreenInsets(gcFirst);
           screenRectFirst.x = screenInsetsFirst.left;
           screenRectFirst.y = screenInsetsFirst.top;

           Robot robot = new Robot(gcFirst.getDevice());

           //Second Screen
           GraphicsConfiguration gcSecond = gd[1].getDefaultConfiguration();               
           Rectangle screenRectSecond = gcSecond.getBounds();
           Insets screenInsetsSecond = Toolkit.getDefaultToolkit().getScreenInsets(gcSecond);       
           Rectangle effectiveScreenArea = new Rectangle();

           /*Remove start bar area*/
           effectiveScreenArea.x = screenRectSecond.x + screenInsetsSecond.left;
           effectiveScreenArea.y = screenRectSecond.y + screenInsetsSecond.top;
           effectiveScreenArea.height = screenRectSecond.height - screenInsetsSecond.top - screenInsetsSecond.bottom;        
           effectiveScreenArea.width = screenRectSecond.width - screenInsetsSecond.left - screenInsetsSecond.right;

           //Scaling will decide capture image needs to shrink or not.!
           double xscaling = 0;
           double yscaling = 0;

           screenshotImage = robot.createScreenCapture(screenRectFirst);

           int differenceWidth = screenRectSecond.width / screenRectFirst.width;
           int differenceheight = screenRectSecond.height / screenRectFirst.height;
           xscaling = differenceWidth / 2.0;
           yscaling = differenceheight / 2.0;   

            yscaling = yscaling - 0.018;

           currentFrame =  new JFrame(); 

           currentFrame.setSize((int)effectiveScreenArea.width/2, (int)effectiveScreenArea.height/2); 
           if(filtertype == 0)
           {
               currentFrame.setLocation(effectiveScreenArea.x, effectiveScreenArea.y); 
               currentFrame.setTitle("First");
               CurrentFrameText = "First";
           }
           else if(filtertype == 1)
           {
               currentFrame.setLocation(effectiveScreenArea.x + ((int)effectiveScreenArea.width/2), effectiveScreenArea.y); 
               currentFrame.setTitle("Second");
               CurrentFrameText = "Second";
           }
           else if(filtertype == 2)
           {
               currentFrame.setLocation(effectiveScreenArea.x + ((int)effectiveScreenArea.width/2), effectiveScreenArea.y); 
               currentFrame.setTitle("Third");
               CurrentFrameText = "Third";
           } 
           else if(filtertype == 3)
           {
               currentFrame.setLocation(effectiveScreenArea.x + ((int)effectiveScreenArea.width/2),effectiveScreenArea.y + ((int)effectiveScreenArea.height/2)); 
               currentFrame.setTitle("Forth");
               CurrentFrameText = "Forth";
           } 


           currentCanvas = new PanelPaint((int)effectiveScreenArea.width/2,(int)effectiveScreenArea.height/2,xscaling,yscaling,CurrentFrameText); 
           currentCanvas.xpos = (int)effectiveScreenArea.width/2;
           currentCanvas.ypos = (int)effectiveScreenArea.height/2;
           currentFrame.getContentPane().add(currentCanvas); 


           currentFrame.setUndecorated(true);        
           currentFrame.setVisible(true);       

            user32 = com.sun.jna.platform.win32.User32.INSTANCE;
           hwnd = user32.GetDesktopWindow();

           th = new Thread(this);
           th.start();     

        }
        catch (AWTException e) 
        {
           // TODO Auto-generated catch block
           e.printStackTrace();
        }
        catch (Exception e) 
        { 
            // TODO Auto-generated catch block
             e.printStackTrace();

        }
    }       
    public void close()
    { 
        currentFrame.dispose();
        currentFrame = null;
        currentCanvas.close();
        currentCanvas = null;   
        screenshotImage = null;
        user32 = null;
        hwnd = null;
        th = null;
    }

    @Override
    public void run() 
    {
        while(true)
        {
            lastTime = System.nanoTime();
            screenshotImage  = GDI32Util.getScreenshot(hwnd);       
            ///screenshotImage = screenshotImage.Convert();   //Place where filter applied
            currentCanvas.setImg(screenshotImage,fps);
            java.awt.EventQueue.invokeLater(currentCanvas::repaint); 
            fps = 1000000000.0 / (System.nanoTime() - lastTime); //one second(nano) divided by amount of time it takes for one frame to finish
            lastTime = System.nanoTime();
        }
    }

} 

@SuppressWarnings("serial")
class PanelPaint extends javax.swing.JPanel
{
    public int xpos = 0;
    public int ypos = 0;
    BufferedImage img = null;
    java.awt.Graphics2D gc  = null;
    Font currentFont = null;
    double fps = 0;
    String CurrentFrameText;
    PanelPaint(int xpos,int ypos,double sx,double sy,String argCurrentFrameText)
    {
        img = new BufferedImage(xpos, ypos, BufferedImage.TYPE_INT_ARGB);
        gc = img.createGraphics();
        gc.scale(sx, sy);
        currentFont = new Font("default", Font.BOLD, 30);
        CurrentFrameText =  argCurrentFrameText;    
        gc.setFont(currentFont);
        gc.setColor(Color.RED);

    }
    @Override
    public Dimension getPreferredSize()
    {
        return new Dimension(xpos, ypos);
    }

    public void setImg(BufferedImage img,double argfps)
    {
        gc.drawImage(img, 0, 0, null);
        gc.drawString(CurrentFrameText + ": " + (int)fps, 25, 25);
        fps = argfps;
    }

    @Override
    public void paint(java.awt.Graphics g)
    {
        g.drawImage(img, 0, 0, null);   
    }
    public void close()
    { 
        img = null;
        gc  = null;
        currentFont = null;
    }
}

Above is slower for high contrast images and taking time for "g.drawImage(img, 0, 0, null);" code. Can performance for draw image can be improve?

1 Answers

You can use an OpenGL fragment shader to filter and then blit the source image as a texture to the 4 screens as screen quads (orthonormal projections onto a quad poly).

This is by far the optimal solution to your requirement. If your game is OpenGL based, it will be even faster since you wont have to use the Robot package as an intermediate - you can simply render the game texture directly (image conversion not required).

This will also allow full control over the filter in the fragment shader. See ShaderToy.com for many excellent filter shaders.

Related