BufferedImage performance is poor on Microsoft Windows platforms, so fallback to the 1.2 implementation if the BI cannot be HW accelerated.  Also streamline some of the code by removing synchronized statements and making the method calls themselves synchronized.  Modification to the selector implementation to make it behave more like a unix selector

git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4880 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/java/com/tigervnc/network/SocketDescriptor.java b/java/com/tigervnc/network/SocketDescriptor.java
index f920731..e336a86 100644
--- a/java/com/tigervnc/network/SocketDescriptor.java
+++ b/java/com/tigervnc/network/SocketDescriptor.java
@@ -38,12 +38,14 @@
     try {
       channel = SocketChannel.open();
       channel.configureBlocking(false);
-      selector = Selector.open();
+      writeSelector = Selector.open();
+      readSelector = Selector.open();
     } catch (IOException e) {
       throw new Exception(e.toString());
     }
     try {
-      channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE );
+      channel.register(writeSelector, SelectionKey.OP_WRITE);
+      channel.register(readSelector, SelectionKey.OP_READ);
     } catch (java.nio.channels.ClosedChannelException e) {
       throw new Exception(e.toString());
     }
@@ -90,6 +92,12 @@
 
   synchronized public int select(int interestOps, Integer timeout) throws Exception {
     int n;
+    Selector selector;
+    if ((interestOps & SelectionKey.OP_READ) != 0) {
+      selector = readSelector;
+    } else {
+      selector = writeSelector;
+    }
     selector.selectedKeys().clear();
     try {
       if (timeout == null) {
@@ -105,20 +113,10 @@
           break;
         }
       }
-      if (n == 0)
-        return -1;
     } catch (java.io.IOException e) {
       throw new Exception(e.toString());
     }
-    Set keys = selector.selectedKeys();
-    Iterator iter = keys.iterator();
-    while (iter.hasNext()) {
-      SelectionKey key = (SelectionKey)iter.next();
-      if ((key.readyOps() & interestOps) != 0) {
-        return n;
-      }
-    }
-    return 0;
+    return n;
   }
 
   public int write(ByteBuffer buf) throws Exception {
@@ -198,22 +196,27 @@
     try {
       if (channel != null)
         channel.close();
-      if (selector != null)
-        selector.close();
+      if (readSelector != null)
+        readSelector.close();
+      if (writeSelector != null)
+        writeSelector.close();
       channel = channel_;
       channel.configureBlocking(false);
-      selector = Selector.open();
+      writeSelector = Selector.open();
+      readSelector = Selector.open();
     } catch (java.io.IOException e) {
       throw new Exception(e.toString());
     }
     try {
-      channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE );
+      channel.register(writeSelector, SelectionKey.OP_WRITE);
+      channel.register(readSelector, SelectionKey.OP_READ);
     } catch (java.nio.channels.ClosedChannelException e) {
       System.out.println(e.toString());
     }
   }
   
   protected SocketChannel channel;
-  protected Selector selector;
+  protected Selector writeSelector;
+  protected Selector readSelector;
 
 }
diff --git a/java/com/tigervnc/rfb/CMsgWriter.java b/java/com/tigervnc/rfb/CMsgWriter.java
index c4f4355..af7d137 100644
--- a/java/com/tigervnc/rfb/CMsgWriter.java
+++ b/java/com/tigervnc/rfb/CMsgWriter.java
@@ -24,7 +24,7 @@
 
   abstract public void writeClientInit(boolean shared);
 
-  public void writeSetPixelFormat(PixelFormat pf) 
+  synchronized public void writeSetPixelFormat(PixelFormat pf) 
   {
     startMsg(MsgTypes.msgTypeSetPixelFormat);                                 
     os.pad(3);
@@ -32,7 +32,7 @@
     endMsg();
   }
 
-  public void writeSetEncodings(int nEncodings, int[] encodings) 
+  synchronized public void writeSetEncodings(int nEncodings, int[] encodings) 
   {
     startMsg(MsgTypes.msgTypeSetEncodings);
     os.skip(1);
@@ -45,7 +45,7 @@
   // Ask for encodings based on which decoders are supported.  Assumes higher
   // encoding numbers are more desirable.
 
-  public void writeSetEncodings(int preferredEncoding, boolean useCopyRect) 
+  synchronized public void writeSetEncodings(int preferredEncoding, boolean useCopyRect) 
   {
     int nEncodings = 0;
     int[] encodings = new int[Encodings.encodingMax+3];
@@ -113,7 +113,7 @@
     writeSetEncodings(nEncodings, encodings);
   }
 
-  public void writeFramebufferUpdateRequest(Rect r, boolean incremental) 
+  synchronized public void writeFramebufferUpdateRequest(Rect r, boolean incremental) 
   {
     startMsg(MsgTypes.msgTypeFramebufferUpdateRequest);
     os.writeU8(incremental?1:0);
@@ -124,7 +124,7 @@
     endMsg();
   }
 
-  public void writeKeyEvent(int key, boolean down) 
+  synchronized public void writeKeyEvent(int key, boolean down) 
   {
     startMsg(MsgTypes.msgTypeKeyEvent);
     os.writeU8(down?1:0);
@@ -133,7 +133,7 @@
     endMsg();
   }
 
-  public void writePointerEvent(Point pos, int buttonMask) 
+  synchronized public void writePointerEvent(Point pos, int buttonMask) 
   {
     Point p = new Point(pos.x,pos.y);
     if (p.x < 0) p.x = 0;
@@ -148,7 +148,7 @@
     endMsg();
   }
 
-  public void writeClientCutText(String str, int len) 
+  synchronized public void writeClientCutText(String str, int len) 
   {
     startMsg(MsgTypes.msgTypeClientCutText);
     os.pad(3);
@@ -165,7 +165,7 @@
   abstract public void startMsg(int type);
   abstract public void endMsg();
 
-  public void setOutStream(OutStream os_) { os = os_; }
+  synchronized public void setOutStream(OutStream os_) { os = os_; }
 
   ConnParams getConnParams() { return cp; }
   OutStream getOutStream() { return os; }
diff --git a/java/com/tigervnc/rfb/CMsgWriterV3.java b/java/com/tigervnc/rfb/CMsgWriterV3.java
index 430c374..8f01f3c 100644
--- a/java/com/tigervnc/rfb/CMsgWriterV3.java
+++ b/java/com/tigervnc/rfb/CMsgWriterV3.java
@@ -26,20 +26,20 @@
 
   public CMsgWriterV3(ConnParams cp_, OutStream os_) { super(cp_, os_); }
 
-  public void writeClientInit(boolean shared) {
+  synchronized public void writeClientInit(boolean shared) {
     os.writeU8(shared?1:0);
     endMsg();
   }
 
-  public void startMsg(int type) {
+  synchronized public void startMsg(int type) {
     os.writeU8(type);
   }
 
-  public void endMsg() {
+  synchronized public void endMsg() {
     os.flush();
   }
 
-  public void writeSetDesktopSize(int width, int height,
+  synchronized public void writeSetDesktopSize(int width, int height,
                                   ScreenSet layout)
 	{
 	  if (!cp.supportsSetDesktopSize)
@@ -67,7 +67,7 @@
 	  endMsg();
 	}
 
-  public void writeFence(int flags, int len, byte[] data)
+  synchronized public void writeFence(int flags, int len, byte[] data)
   {
     if (!cp.supportsFence)
       throw new Exception("Server does not support fences");
@@ -87,7 +87,7 @@
     endMsg();
   }
   
-  public void writeEnableContinuousUpdates(boolean enable,
+  synchronized public void writeEnableContinuousUpdates(boolean enable,
                                            int x, int y, int w, int h)
   {
     if (!cp.supportsContinuousUpdates)
diff --git a/java/com/tigervnc/rfb/TightDecoder.java b/java/com/tigervnc/rfb/TightDecoder.java
index 7424457..9902097 100644
--- a/java/com/tigervnc/rfb/TightDecoder.java
+++ b/java/com/tigervnc/rfb/TightDecoder.java
@@ -249,16 +249,7 @@
 
     // Create an Image object from the JPEG data.
     Image jpeg = tk.createImage(netbuf);
-    tk.prepareImage(jpeg, -1, -1, null); 
-    synchronized(this) { 
-      while ((tk.checkImage(jpeg, -1, -1, null) & ImageObserver.ALLBITS) == 0) {
-        try {
-          this.wait(1);
-        } catch (InterruptedException e) {
-          throw new Exception("Error decoding JPEG data");
-        }
-      }
-    }
+    jpeg.setAccelerationPriority(1);
     handler.imageRect(r, jpeg);
     jpeg.flush();
   }
diff --git a/java/com/tigervnc/vncviewer/AWTPixelBuffer.java b/java/com/tigervnc/vncviewer/AWTPixelBuffer.java
new file mode 100644
index 0000000..6b74eee
--- /dev/null
+++ b/java/com/tigervnc/vncviewer/AWTPixelBuffer.java
@@ -0,0 +1,103 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright (C) 2011-2012 TigerVNC Team.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.awt.*;
+import java.awt.image.*;
+
+import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Exception;
+
+public class AWTPixelBuffer extends PlatformPixelBuffer
+{
+  public AWTPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) {
+    super(w, h, cc_, desktop_);
+  }
+
+  public void setPF(PixelFormat pf) {
+    super.setPF(pf);
+    if (source != null)
+      source.newPixels(data, cm, 0, width_);
+  }
+
+  public void updateColourMap() {
+    cm = new IndexColorModel(8, nColours, reds, greens, blues);
+    if (source != null)
+      source.newPixels(data, cm, 0, width_);
+  }
+  
+  // resize() resizes the image, preserving the image data where possible.
+  public void resize(int w, int h) {
+    if (w == width() && h == height()) return;
+
+    int rowsToCopy = h < height() ? h : height();
+    int copyWidth = w < width() ? w : width();
+    int[] oldData = data;
+
+    width_ = w;
+    height_ = h;
+
+    data = new int[width() * height()];
+    for (int i = 0; i < rowsToCopy; i++)
+      System.arraycopy(oldData, copyWidth * i, 
+                       data, width_ * i, copyWidth);
+
+    source = new MemoryImageSource(w, h, cm, data, 0, w);
+    source.setAnimated(true);
+    source.setFullBufferUpdates(false);
+    image = desktop.createImage(source);
+    source.newPixels(data, cm, 0, width_);
+  }
+
+  public void fillRect(int x, int y, int w, int h, int pix) {
+    super.fillRect(x, y, w, h, pix);
+    source.newPixels(x, y, w, h, true);
+  }
+
+  public void imageRect(int x, int y, int w, int h, Object pix) {
+    if (pix instanceof Image) {
+      PixelGrabber pg = 
+        new PixelGrabber((Image)pix, 0, 0, w, h, data, (width_ * y) + x, width_);
+      try {
+        pg.grabPixels(0);
+      } catch (InterruptedException e) {
+        vlog.error("Tight Decoding: Wrong JPEG data recieved");
+      }
+    } else {
+      for (int j = 0; j < h; j++)
+        System.arraycopy(pix, (w*j), data, width_ * (y + j) + x, w);
+    }
+    source.newPixels(x, y, w, h, true);
+  }
+
+  public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
+    super.copyRect(x, y, w, h, srcX, srcY);
+    source.newPixels(x, y, w, h, true);
+  }
+
+  public Image getImage() {
+    return (Image)image;
+  }
+
+  Image image;
+  MemoryImageSource source;
+
+  static LogWriter vlog = new LogWriter("AWTPixelBuffer");
+}
diff --git a/java/com/tigervnc/vncviewer/BIPixelBuffer.java b/java/com/tigervnc/vncviewer/BIPixelBuffer.java
new file mode 100644
index 0000000..cc19c1d
--- /dev/null
+++ b/java/com/tigervnc/vncviewer/BIPixelBuffer.java
@@ -0,0 +1,105 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * Copyright (C) 2011-2012 TigerVNC Team.
+ * 
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.vncviewer;
+
+import java.awt.*;
+import java.awt.image.*;
+
+import com.tigervnc.rfb.*;
+import com.tigervnc.rfb.Exception;
+
+public class BIPixelBuffer extends PlatformPixelBuffer
+{
+  public BIPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) {
+    super(w, h, cc_, desktop_);
+  }
+
+  // resize() resizes the image, preserving the image data where possible.
+  public void resize(int w, int h) {
+    if (w == width() && h == height()) return;
+
+    width_ = w;
+    height_ = h;
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+    GraphicsConfiguration gc = gd.getDefaultConfiguration();
+    image = gc.createCompatibleImage(w, h, Transparency.OPAQUE);
+    image.setAccelerationPriority(1);
+    image.createGraphics();
+  }
+
+  public void fillRect(int x, int y, int w, int h, int pix) {
+    Graphics2D graphics = (Graphics2D)image.getGraphics();
+    switch (format.depth) {
+    case 24:
+      graphics.setColor(new Color(pix)); 
+      graphics.fillRect(x, y, w, h); 
+      break;
+    default:
+      Color color = new Color((0xff << 24) | (cm.getRed(pix) << 16) |
+                              (cm.getGreen(pix) << 8) | (cm.getBlue(pix)));
+      graphics.setColor(color); 
+      graphics.fillRect(x, y, w, h); 
+      break;
+    }
+    graphics.dispose();
+  }
+
+  public void imageRect(int x, int y, int w, int h, Object pix) {
+    Graphics2D graphics = (Graphics2D)image.getGraphics();
+    Image img;
+    if (pix instanceof Image) {
+      img = (Image)pix;
+    } else {
+      img = tk.createImage(new MemoryImageSource(w, h, cm, (int[])pix, 0, w));
+      img.setAccelerationPriority(1);
+    }
+    boolean ret = tk.prepareImage(img, -1, -1, null);
+    if (!ret) {
+      while ((tk.checkImage(img, -1, -1, null) & ImageObserver.ALLBITS) == 0) {
+        synchronized (this) {
+          try {
+            this.wait(0, 10000);
+          } catch (InterruptedException e) {
+            throw new Exception("Error decoding JPEG data");
+          }
+        }
+      }
+    } 
+    graphics.drawImage(img, x, y, w, h, null); 
+    graphics.dispose();
+    img.flush();
+  }
+
+  public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
+    Graphics2D graphics = (Graphics2D)image.getGraphics();
+    graphics.copyArea(srcX, srcY, w, h, x - srcX, y - srcY);
+    graphics.dispose();
+  }
+
+  public Image getImage() {
+    return (Image)image;
+  }
+
+  BufferedImage image;
+
+  static LogWriter vlog = new LogWriter("BIPixelBuffer");
+}
diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java
index 061e6fb..0f95c69 100644
--- a/java/com/tigervnc/vncviewer/CConn.java
+++ b/java/com/tigervnc/vncviewer/CConn.java
@@ -115,7 +115,6 @@
 
   public void addChild(DesktopWindow child) {
     sp = new JScrollPane(child);
-    sp.setDoubleBuffered(true);
     child.setBackground(Color.BLACK);
     child.setOpaque(true);
     sp.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
@@ -426,9 +425,7 @@
       // We need fences to make extra update requests and continuous
       // updates "safe". See fence() for the next step.
       if (cp.supportsFence)
-        synchronized(this) {
-          writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext, 0, null);
-        }
+        writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext, 0, null);
 
       if (cp.supportsSetDesktopSize &&
           viewer.desktopSize.getValue() != null &&
@@ -460,9 +457,7 @@
         screen0.dimensions.br.x = width;
         screen0.dimensions.br.y = height;
 
-        synchronized(this) {
-          writer().writeSetDesktopSize(width, height, layout);
-        }
+        writer().writeSetDesktopSize(width, height, layout);
       }
 
       firstUpdate = false;
@@ -537,9 +532,7 @@
       // We handle everything synchronously so we trivially honor these modes
       flags = flags & (fenceTypes.fenceFlagBlockBefore | fenceTypes.fenceFlagBlockAfter);
   
-      synchronized(this) {
-        writer().writeFence(flags, len, data);
-      }
+      writer().writeFence(flags, len, data);
       return;
     }
   
@@ -551,9 +544,7 @@
         if (cp.supportsContinuousUpdates) {
           vlog.info("Enabling continuous updates");
           continuousUpdates = true;
-          synchronized(this) {
-            writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height);
-          }
+          writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height);
         }
       }
     } else {
@@ -574,9 +565,7 @@
       return;
 
     if (continuousUpdates)
-      synchronized(this) {
-        writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height);
-      }
+      writer().writeEnableContinuousUpdates(true, 0, 0, cp.width, cp.height);
 
     if ((cp.width == 0) && (cp.height == 0))
       return;
@@ -749,10 +738,8 @@
   
         pf.write(memStream);
   
-        synchronized(this) {
-          writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext,
-                              memStream.length(), (byte[])memStream.data());
-        }
+        writer().writeFence(fenceTypes.fenceFlagRequest | fenceTypes.fenceFlagSyncNext,
+                            memStream.length(), (byte[])memStream.data());
       } else {
         // New requests are sent out at the start of processing the last
         // one, so we cannot switch our internal format right now (doing so
@@ -763,9 +750,7 @@
 
       String str = pf.print();
       vlog.info("Using pixel format "+str);
-      synchronized (this) {
-        writer().writeSetPixelFormat(pf);
-      }
+      writer().writeSetPixelFormat(pf);
 
       formatChange = false;
     }
@@ -774,10 +759,8 @@
 
     if (forceNonincremental || !continuousUpdates) {
       pendingUpdate = true;
-      synchronized (this) {
-        writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height),
-                                                   !formatChange);
-      }
+      writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height),
+                                                 !formatChange);
     }
 
     forceNonincremental = false;
@@ -845,9 +828,7 @@
   }
 
   public void refresh() {
-    synchronized (this) {
-      writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height), false);
-    }
+    writer().writeFramebufferUpdateRequest(new Rect(0,0,cp.width,cp.height), false);
     pendingUpdate = true;
   }
 
@@ -1216,17 +1197,17 @@
   }
 
   // writeClientCutText() is called from the clipboard dialog
-  synchronized public void writeClientCutText(String str, int len) {
+  public void writeClientCutText(String str, int len) {
     if (state() != RFBSTATE_NORMAL) return;
     writer().writeClientCutText(str,len);
   }
 
-  synchronized public void writeKeyEvent(int keysym, boolean down) {
+  public void writeKeyEvent(int keysym, boolean down) {
     if (state() != RFBSTATE_NORMAL) return;
     writer().writeKeyEvent(keysym, down);
   }
 
-  synchronized public void writeKeyEvent(KeyEvent ev) {
+  public void writeKeyEvent(KeyEvent ev) {
     if (ev.getID() != KeyEvent.KEY_PRESSED && !ev.isActionKey())
       return;
 
@@ -1335,9 +1316,7 @@
       break;
     }
 
-    synchronized (this) {
-      writeModifiers(ev.getModifiers() & ~KeyEvent.ALT_MASK & ~KeyEvent.META_MASK);
-    }
+    writeModifiers(ev.getModifiers() & ~KeyEvent.ALT_MASK & ~KeyEvent.META_MASK);
 
     if (cp.width != desktop.scaledWidth || 
         cp.height != desktop.scaledHeight) {
@@ -1348,9 +1327,7 @@
       ev.translatePoint(sx - ev.getX(), sy - ev.getY());
     }
     
-    synchronized (this) {
-      writer().writePointerEvent(new Point(ev.getX(),ev.getY()), buttonMask);
-    }
+    writer().writePointerEvent(new Point(ev.getX(),ev.getY()), buttonMask);
 
     if (buttonMask == 0) writeModifiers(0);
   }
@@ -1369,11 +1346,9 @@
     for (int i=0;i<Math.abs(clicks);i++) {
       x = ev.getX();
       y = ev.getY();
-      synchronized(this) {
-        writer().writePointerEvent(new Point(x, y), buttonMask);
-        buttonMask = 0;
-        writer().writePointerEvent(new Point(x, y), buttonMask);
-      }
+      writer().writePointerEvent(new Point(x, y), buttonMask);
+      buttonMask = 0;
+      writer().writePointerEvent(new Point(x, y), buttonMask);
     }
     writeModifiers(0);
 
@@ -1400,9 +1375,7 @@
   private void checkEncodings() {
     if (encodingChange && (writer() != null)) {
       vlog.info("Using "+Encodings.encodingName(currentEncoding)+" encoding");
-      synchronized(this) {
-        writer().writeSetEncodings(currentEncoding, true);
-      }
+      writer().writeSetEncodings(currentEncoding, true);
       encodingChange = false;
     }
   }
diff --git a/java/com/tigervnc/vncviewer/DesktopWindow.java b/java/com/tigervnc/vncviewer/DesktopWindow.java
index aa01962..362c47a 100644
--- a/java/com/tigervnc/vncviewer/DesktopWindow.java
+++ b/java/com/tigervnc/vncviewer/DesktopWindow.java
@@ -55,7 +55,21 @@
   public DesktopWindow(int width, int height, PixelFormat serverPF, CConn cc_) {
     cc = cc_;
     setSize(width, height);
-    im = new PixelBufferImage(width, height, cc, this);
+    GraphicsEnvironment ge =
+      GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+    GraphicsConfiguration gc = gd.getDefaultConfiguration();
+    BufferCapabilities bufCaps = gc.getBufferCapabilities();
+    ImageCapabilities imgCaps = gc.getImageCapabilities();
+    if (bufCaps.isPageFlipping() || bufCaps.isMultiBufferAvailable() ||
+        imgCaps.isAccelerated()) {
+      vlog.debug("GraphicsDevice supports HW acceleration.");
+      setDoubleBuffered(false);
+      im = new BIPixelBuffer(width, height, cc, this);
+    } else {
+      vlog.debug("GraphicsDevice does not support HW acceleration.");
+      im = new AWTPixelBuffer(width, height, cc, this);
+    }
 
     cursor = new Cursor();
     cursorBacking = new ManagedPixelBuffer();
@@ -85,17 +99,14 @@
   // DesktopWindow has actually been made visible so that getGraphics() ought
   // to work.
 
-  synchronized public void initGraphics() { 
-    graphics = im.image.getGraphics();
-    prepareImage(im.image, scaledWidth, scaledHeight, this);
+  public void initGraphics() { 
+    synchronized(im) {
+      prepareImage(im.getImage(), scaledWidth, scaledHeight, null);
+    }
   }
 
   final public PixelFormat getPF() { return im.getPF(); }
 
-  synchronized public void setPF(PixelFormat pf) { 
-    im.setPF(pf); 
-  }
-
   public void setViewport(ViewportFrame viewport)
   {
     viewport.setChild(this);
@@ -214,9 +225,7 @@
     int h = cc.cp.height;
     hideLocalCursor();
     setSize(w, h);
-    synchronized (im) { 
-      im.resize(w, h);
-    }
+    im.resize(w, h);
   }
 
   final void drawInvalidRect() {
@@ -227,11 +236,7 @@
     int h = invalidBottom - y;
     invalidRect = false;
 
-    synchronized (im) {
-      graphics.setClip(x, y, w, h);
-      repaint(x, y, w, h);
-      graphics.setClip(0, 0, im.width(), im.height());
-    }
+    repaint(x, y, w, h);
   }
 
   final void invalidate(int x, int y, int w, int h) {
@@ -256,9 +261,7 @@
   final public void fillRect(int x, int y, int w, int h, int pix)
   {
     if (overlapsCursor(x, y, w, h)) hideLocalCursor();
-    synchronized (im) { 
-      im.fillRect(x, y, w, h, pix);
-    }
+    im.fillRect(x, y, w, h, pix);
     invalidate(x, y, w, h);
     if (softCursor == null)
       showLocalCursor();
@@ -267,9 +270,7 @@
   final public void imageRect(int x, int y, int w, int h,
                                            Object pix) {
     if (overlapsCursor(x, y, w, h)) hideLocalCursor();
-    synchronized (im) {
-      im.imageRect(x, y, w, h, pix);
-    }
+    im.imageRect(x, y, w, h, pix);
     invalidate(x, y, w, h);
     if (softCursor == null)
       showLocalCursor();
@@ -279,9 +280,7 @@
                                           int srcX, int srcY) {
     if (overlapsCursor(x, y, w, h) || overlapsCursor(srcX, srcY, w, h))
       hideLocalCursor();
-    synchronized (im) {
-      im.copyRect(x, y, w, h, srcX, srcY);
-    }
+    im.copyRect(x, y, w, h, srcX, srcY);
     invalidate(x, y, w, h);
   }
 
@@ -361,9 +360,9 @@
     g2.setRenderingHint(RenderingHints.KEY_RENDERING, 
                         RenderingHints.VALUE_RENDER_QUALITY);
     if (cc.cp.width == scaledWidth && cc.cp.height == scaledHeight) {
-      g2.drawImage(im.image, 0, 0, null);
+      g2.drawImage(im.getImage(), 0, 0, null);
     } else {
-      g2.drawImage(im.image, 0, 0, scaledWidth, scaledHeight, null);  
+      g2.drawImage(im.getImage(), 0, 0, scaledWidth, scaledHeight, null);  
     }
   }
   
@@ -403,14 +402,12 @@
       // - Render the cursor!
       if (e.getX() != cursorPosX || e.getY() != cursorPosY) {
         hideLocalCursor();
-        synchronized(im) {
-          if (e.getX() >= 0 && e.getX() < im.width() &&
-              e.getY() >= 0 && e.getY() < im.height()) {
-            cursorPosX = e.getX();
-            cursorPosY = e.getY();
-            if (softCursor == null)
-              showLocalCursor();
-          }
+        if (e.getX() >= 0 && e.getX() < im.width() &&
+            e.getY() >= 0 && e.getY() < im.height()) {
+          cursorPosX = e.getX();
+          cursorPosY = e.getY();
+          if (softCursor == null)
+            showLocalCursor();
         }
       }
     }
@@ -527,8 +524,7 @@
   CConn cc;
 
   // access to the following must be synchronized:
-  PixelBufferImage im;
-  Graphics graphics;
+  PlatformPixelBuffer im;
   Thread setColourMapEntriesTimerThread;
 
   Cursor cursor;
diff --git a/java/com/tigervnc/vncviewer/PixelBufferImage.java b/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
similarity index 60%
rename from java/com/tigervnc/vncviewer/PixelBufferImage.java
rename to java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
index e77f460..f5844d5 100644
--- a/java/com/tigervnc/vncviewer/PixelBufferImage.java
+++ b/java/com/tigervnc/vncviewer/PlatformPixelBuffer.java
@@ -17,11 +17,6 @@
  * USA.
  */
 
-//
-// PixelBufferImage is an PixelBuffer which also acts as an ImageProducer.
-// Currently it only supports 8-bit colourmapped pixel format.
-//
-
 package com.tigervnc.vncviewer;
 
 import java.awt.*;
@@ -30,9 +25,9 @@
 
 import com.tigervnc.rfb.*;
 
-public class PixelBufferImage extends PixelBuffer
+abstract public class PlatformPixelBuffer extends PixelBuffer
 {
-  public PixelBufferImage(int w, int h, CConn cc_, DesktopWindow desktop_) {
+  public PlatformPixelBuffer(int w, int h, CConn cc_, DesktopWindow desktop_) {
     cc = cc_;
     desktop = desktop_;
     PixelFormat nativePF = getNativePF();
@@ -45,30 +40,7 @@
   }
 
   // resize() resizes the image, preserving the image data where possible.
-  public void resize(int w, int h) {
-    if (w == width() && h == height()) return;
-
-    width_ = w;
-    height_ = h;
-    switch (format.depth) {
-    case  3: 
-      // Fall-through to depth 8
-    case  6: 
-      // Fall-through to depth 8
-    case 8:
-      image = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_INDEXED);
-      break;
-    default:
-      GraphicsEnvironment ge =
-        GraphicsEnvironment.getLocalGraphicsEnvironment();
-      GraphicsDevice gd = ge.getDefaultScreenDevice();
-      GraphicsConfiguration gc = gd.getDefaultConfiguration();
-      image = gc.createCompatibleImage(w, h, Transparency.OPAQUE);
-      break;
-    }
-    image.setAccelerationPriority(1);
-    graphics = image.createGraphics();
-  }
+  abstract public void resize(int w, int h);
 
   public PixelFormat getNativePF() {
     PixelFormat pf;
@@ -96,34 +68,7 @@
     return pf;
   }
 
-  public void fillRect(int x, int y, int w, int h, int pix) {
-    switch (format.depth) {
-    case 24:
-      graphics.setColor(new Color(pix)); 
-      graphics.fillRect(x, y, w, h); 
-      break;
-    default:
-      Color color = new Color((0xff << 24) | (cm.getRed(pix) << 16) |
-                              (cm.getGreen(pix) << 8) | (cm.getBlue(pix)));
-      graphics.setColor(color); 
-      graphics.fillRect(x, y, w, h); 
-      break;
-    }
-  }
-
-  public void imageRect(int x, int y, int w, int h, Object pix) {
-    if (pix instanceof java.awt.Image) {
-      graphics.drawImage((Image)pix, x, y, w, h, null); 
-    } else {
-      Image img = tk.createImage(new MemoryImageSource(w, h, cm, (int[])pix, 0, w));
-      graphics.drawImage(img, x, y, w, h, null); 
-      img.flush();
-    }
-  }
-
-  public void copyRect(int x, int y, int w, int h, int srcX, int srcY) {
-    graphics.copyArea(srcX, srcY, w, h, x - srcX, y - srcY);
-  }
+  abstract public void imageRect(int x, int y, int w, int h, Object pix);
 
   // setColourMapEntries() changes some of the entries in the colourmap.
   // However these settings won't take effect until updateColourMap() is
@@ -147,11 +92,11 @@
     cm = new IndexColorModel(8, nColours, reds, greens, blues);
   }
 
-  private static Toolkit tk = java.awt.Toolkit.getDefaultToolkit();
+  protected static Toolkit tk = Toolkit.getDefaultToolkit();
 
-  Graphics2D graphics;
-  BufferedImage image;
-  ImageConsumer ic;
+  abstract public Image getImage();
+
+  protected Image image;
 
   int nColours;
   byte[] reds;
@@ -160,5 +105,5 @@
 
   CConn cc;
   DesktopWindow desktop;
-  static LogWriter vlog = new LogWriter("PixelBufferImage");
+  static LogWriter vlog = new LogWriter("PlatformPixelBuffer");
 }