Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » Standard Widget Toolkit (SWT) » Painting on a GC at a decent speed
Painting on a GC at a decent speed [message #461468] Sun, 25 September 2005 14:28 Go to next message
Eclipse UserFriend
Originally posted by: mihnea.balta.digitalsummoners.ro

I need to blit an image generated as an array of integers (xRGB pixels)
onto a SWT control at interactive rates. By reading the docs, I only
found this way to do it:

public void paintControl(PaintEvent e){
/* ... */

Backbuffer b = editor.PaintMap();

if( (backBufferData == null) || (backBufferData.width != b.width) ||
(backBufferData.height != b.height) )
{
backBufferData = new ImageData(b.width, b.height, 32, palette);
}

for(int i=0; i<b.height; ++i){
backBufferData.setPixels(0, i, b.width, b.surface,
(i+b.oy)*b.extWidth+b.ox);
}

Image img = new Image(null, backBufferData);
e.gc.drawImage(img, 0, 0, b.width, b.height, viewport.x, viewport.y,
viewport.width, viewport.height);
img.dispose();

/* ... */
}

This code is horribly slow on an AMD64 3000+ running Windows XP. By
profiling it, I was extremely surprised to find out that
editor.PaintMap() (which does the actual work of creating the image) is
the *3rd* in the list of hotspots, with a mere 12% of the time. The
constructor of Image takes 72% of the time, and setPixels() takes 14%
(because it's called many times).

Constructing an Image and destroying it at each redraw seemed like a bad
ideea from the start, but I can't find any other way of blitting the
array of pixels onto the GC. Since my array is already in the format
required by the device, please tell me there's a way to blit it directly
onto the GC. I've looked at the sources for ImageData, GC and Image and
the code does an insane amount of work to turn the format that I provide
into exactly the same format. I don't care about platform independence
and/or device format independence, I care about speed. As a reference,
this is the C++/MFC equivalent of what I need:

#define IMG_WIDTH 1280
#define IMG_HEIGHT 1024

CDrawingTestDlg::CDrawingTestDlg(CWnd* pParent) :
CDialog(CDrawingTestDlg::IDD, pParent)
{
CDC screenDC;
screenDC.CreateDC("DISPLAY", 0, 0, 0);
srcDC.CreateCompatibleDC(&screenDC);
srcBitmap.CreateCompatibleBitmap(&screenDC, IMG_WIDTH, IMG_HEIGHT);
srcDC.SelectObject(srcBitmap);
}

void CDrawingTestDlg::OnPaint()
{
CPaintDC paintDC(this);
// backBuffer comes from somewhere else, and backBuffer.surface is an
// array of IMG_HEIGHT*IMG_WIDTH ints
srcBitmap.SetBitmapBits(IMG_HEIGHT*IMG_WIDTH*sizeof(unsigned int),
backBuffer.surface);
paintDC.StretchBlt(0, 0, IMG_WIDTH, IMG_HEIGHT, &srcDC, 0, 0,
IMG_WIDTH, IMG_HEIGHT, SRCCOPY);
}

This code runs at > 50 fps on my machine with a constant backbuffer
surface, which is more than I need and way less than what I could get
with SWT. I use StretchBlt because sometimes I need to magnify the image
(I never blit on a smaller target than the backbuffer, so that's not
going to be a performance issue).

Thanks in advance,
Mihnea
Re: Painting on a GC at a decent speed [message #461594 is a reply to message #461468] Mon, 26 September 2005 23:08 Go to previous messageGo to next message
Steve Northover is currently offline Steve NorthoverFriend
Messages: 1636
Registered: July 2009
Senior Member
If you don't care about running on platforms other than Windows, why not
write a JNI method that does the C code you provided?

"Mihnea Balta" <mihnea.balta@digitalsummoners.ro> wrote in message
news:dh6c6k$1od$1@news.eclipse.org...
> I need to blit an image generated as an array of integers (xRGB pixels)
> onto a SWT control at interactive rates. By reading the docs, I only
> found this way to do it:
>
> public void paintControl(PaintEvent e){
> /* ... */
>
> Backbuffer b = editor.PaintMap();
>
> if( (backBufferData == null) || (backBufferData.width != b.width) ||
> (backBufferData.height != b.height) )
> {
> backBufferData = new ImageData(b.width, b.height, 32, palette);
> }
>
> for(int i=0; i<b.height; ++i){
> backBufferData.setPixels(0, i, b.width, b.surface,
> (i+b.oy)*b.extWidth+b.ox);
> }
>
> Image img = new Image(null, backBufferData);
> e.gc.drawImage(img, 0, 0, b.width, b.height, viewport.x, viewport.y,
> viewport.width, viewport.height);
> img.dispose();
>
> /* ... */
> }
>
> This code is horribly slow on an AMD64 3000+ running Windows XP. By
> profiling it, I was extremely surprised to find out that
> editor.PaintMap() (which does the actual work of creating the image) is
> the *3rd* in the list of hotspots, with a mere 12% of the time. The
> constructor of Image takes 72% of the time, and setPixels() takes 14%
> (because it's called many times).
>
> Constructing an Image and destroying it at each redraw seemed like a bad
> ideea from the start, but I can't find any other way of blitting the
> array of pixels onto the GC. Since my array is already in the format
> required by the device, please tell me there's a way to blit it directly
> onto the GC. I've looked at the sources for ImageData, GC and Image and
> the code does an insane amount of work to turn the format that I provide
> into exactly the same format. I don't care about platform independence
> and/or device format independence, I care about speed. As a reference,
> this is the C++/MFC equivalent of what I need:
>
> #define IMG_WIDTH 1280
> #define IMG_HEIGHT 1024
>
> CDrawingTestDlg::CDrawingTestDlg(CWnd* pParent) :
> CDialog(CDrawingTestDlg::IDD, pParent)
> {
> CDC screenDC;
> screenDC.CreateDC("DISPLAY", 0, 0, 0);
> srcDC.CreateCompatibleDC(&screenDC);
> srcBitmap.CreateCompatibleBitmap(&screenDC, IMG_WIDTH, IMG_HEIGHT);
> srcDC.SelectObject(srcBitmap);
> }
>
> void CDrawingTestDlg::OnPaint()
> {
> CPaintDC paintDC(this);
> // backBuffer comes from somewhere else, and backBuffer.surface is an
> // array of IMG_HEIGHT*IMG_WIDTH ints
> srcBitmap.SetBitmapBits(IMG_HEIGHT*IMG_WIDTH*sizeof(unsigned int),
> backBuffer.surface);
> paintDC.StretchBlt(0, 0, IMG_WIDTH, IMG_HEIGHT, &srcDC, 0, 0,
> IMG_WIDTH, IMG_HEIGHT, SRCCOPY);
> }
>
> This code runs at > 50 fps on my machine with a constant backbuffer
> surface, which is more than I need and way less than what I could get
> with SWT. I use StretchBlt because sometimes I need to magnify the image
> (I never blit on a smaller target than the backbuffer, so that's not
> going to be a performance issue).
>
> Thanks in advance,
> Mihnea
Re: Painting on a GC at a decent speed [message #461715 is a reply to message #461468] Wed, 28 September 2005 08:11 Go to previous messageGo to next message
Eclipse UserFriend
Originally posted by: jan.vonbargen.web.de

I think it is a bad idea to try to send imagedata for an animation using the jni interface - it is too slow for it. What exactly do you want to do ? (What is the data in this Buffer ?)

If you have a look into the OS.java in eclipse, then you might recognize many functions you already know from C. Maybe you can do the whole imageprocessing just by using these methods ? But be aware that it is not portable in any way ...

As a hint (that's the way I do it): write the program in classic c using Gdi (Mr. Petzold and the Microsoft Platform SDK were always helpful) - no MFC - and if this works as expected you can translate it easily to java. Maybe you have to add some methods to the OS.java or some subclass and implement them in C. And if you need some extended graphics functions: try the Gdi+ extension of swt ?

Curious as we all are: Please give us a short note on how you did it and if it was successful !
Re: Painting on a GC at a decent speed [message #462009 is a reply to message #461715] Wed, 05 October 2005 01:32 Go to previous messageGo to next message
Steve Northover is currently offline Steve NorthoverFriend
Messages: 1636
Registered: July 2009
Senior Member
> imagedata for an animation using the jni interface

Not necessarily. You can use the faster JNI object accessor methods (the
names escape me - something including the word "critical") that don't copy
the data. That's what we do when we pass out large buffers (providing of
course, we know the JNI code doesn't call back into Java).

"Jan von Bargen" <jan.vonbargen@web.de> wrote in message
news:27830818.1127895136085.JavaMail.root@cp1.javalobby.org...
> I think it is a bad idea to try to send imagedata for an animation using
the jni interface - it is too slow for it. What exactly do you want to do ?
(What is the data in this Buffer ?)
>
> If you have a look into the OS.java in eclipse, then you might recognize
many functions you already know from C. Maybe you can do the whole
imageprocessing just by using these methods ? But be aware that it is not
portable in any way ...
>
> As a hint (that's the way I do it): write the program in classic c using
Gdi (Mr. Petzold and the Microsoft Platform SDK were always helpful) - no
MFC - and if this works as expected you can translate it easily to java.
Maybe you have to add some methods to the OS.java or some subclass and
implement them in C. And if you need some extended graphics functions: try
the Gdi+ extension of swt ?
>
> Curious as we all are: Please give us a short note on how you did it and
if it was successful !
Re: Painting on a GC at a decent speed [message #462217 is a reply to message #461715] Sat, 08 October 2005 09:57 Go to previous message
Eclipse UserFriend
Originally posted by: mihnea.balta.digitalsummoners.ro

This is a multi-part message in MIME format.
--------------010609030803020500040304
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Jan von Bargen wrote:
> If you have a look into the OS.java in eclipse, then you might
> recognize many functions you already know from C.

I finally managed to make it work at a decent speed, after quite a bit
of frustration.

I looked into OS.java before coming to the newsgroup, but I couldn't
find SetDIBits(), nor SetBitmapBits(). I tried adding them, but I
couldn't figure out how to build swt.jar afterwards (I managed to add
the functions to swt-win32-3138.dll). The ant scripts provided with the
source distribution don't build it, which contributed to the frustration.

Following your suggestion, I looked again in OS.java for something I
could use, and I found CreateDIBSection(). Then I looked into
Image.java, to see how SWT uses it, and I was particularily amused by
the implementation of OS.MoveMemory() that takes an int, an int[] and a
size, casts the first int to a pointer and copies the int[] in it. All
this combined with the fact that CreateDIBSection() takes an array of
one int and puts the pointer to the memory, casted to an int, as element
0 of that array. Funny way of overcoming the limitations of a crippled
language...

When I finally though I've seen the light, I ran into one more problem:
I want to blit a sub-rect of my back buffer onto the control, since the
back buffer has a border that's not supposed to be visible (guard band
clipping). Java wouldn't let me take pointers inside an array, but
luckily StretchBlt() knows how to blit a sub-rect of the source DC into
a sub-rect of the destination DC.

> Curious as we all are: Please give us a short note on how you did it
> and if it was successful !

I attached the final solution (look inside the paintControl() method).
The application is a map editor for my pet project mobile phone game.
I'm painting on an isometric tile map (diamond-shaped tiles) and with
the attached code, painting is real time on a 1260x791 canvas (which
occurs when the editor is maximized in 1280x1024). It "feels" 5-10 times
faster than the code I first posted (I didn't waste time to make
accurate timings). It's still funny to see how the CPU time is spent
inside the paint function:
- map drawing routine (editor.PaintMap()) ~ 42%
- OS.StretchBlt() ~ 30%
- OS.MoveMemory() ~ 27%

While this is way better than 12% in PaintMap() and the rest out the
window, I wasn't expecting to see OS.MoveMemory() there. However, since
the performance is satisfactory now, I'm not going to investigate further.

PS: there was an old saying: "if it doesn't have pointers, you can't
make games in it". Seems to hold true to this day. :)

--------------010609030803020500040304
Content-Type: text/plain;
name="MapCanvas.java"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="MapCanvas.java"

package com.digitalsummoners.icicle;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.internal.win32.BITMAPINFOHEADER;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.internal.win32.TCHAR;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Slider;

import com.digitalsummoners.avalanch.Backbuffer;
import com.digitalsummoners.avalanch.Rectangle;

public class MapCanvas extends Canvas {

private ImageData backBufferData;
private PaletteData palette;
private int x0, y0;
private MapEditor editor;
private Rectangle vpLimits;
private Rectangle viewport;
private float zoom;
private Slider horizSlider;
private Slider vertSlider;
private Point forcedVpSize;
private Point renderedSize;

private int srcDC;
private int srcBitmap;
private int srcBitmapWidth, srcBitmapHeight;
private int srcBitsPtr;

private class Painter implements PaintListener {
public void paintControl(PaintEvent e){
Point ctrlSize = getSize();
if(editor == null){
e.gc.fillRectangle(0, 0, ctrlSize.x, ctrlSize.y);
return;
}

Rectangle r = new Rectangle();
r.x = x0;
r.y = y0;
r.width = renderedSize.x;
r.height = renderedSize.y;
Backbuffer b = editor.PaintMap(r);

if( (srcBitmap == -1) || (srcBitmapWidth != b.extWidth) || (srcBitmapHeight != b.extHeight) ){
if(srcBitmap != -1)
OS.DeleteObject(srcBitmap);

BITMAPINFOHEADER bmiHeader = new BITMAPINFOHEADER();
bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
bmiHeader.biWidth = b.extWidth;
bmiHeader.biHeight = -b.extHeight; // fuck bitmaps stored upside down. what the fuck was m$ thinking?
bmiHeader.biPlanes = 1;
bmiHeader.biBitCount = 32;
bmiHeader.biCompression = OS.BI_RGB;
bmiHeader.biSizeImage = 0;
bmiHeader.biXPelsPerMeter = bmiHeader.biYPelsPerMeter = 0;
bmiHeader.biClrUsed = bmiHeader.biClrImportant = 0;
byte[] bmi = new byte[BITMAPINFOHEADER.sizeof];
OS.MoveMemory(bmi, bmiHeader, BITMAPINFOHEADER.sizeof);

int[] srcBits = new int[1];
srcBitmap = OS.CreateDIBSection(0, bmi, OS.DIB_RGB_COLORS, srcBits, 0, 0);
srcBitsPtr = srcBits[0];
srcBitmapWidth = b.extWidth;
srcBitmapHeight = b.extHeight;
OS.SelectObject(srcDC, srcBitmap);
}

OS.MoveMemory(srcBitsPtr, b.surface, b.extHeight*b.extWidth*4);
OS.StretchBlt(e.gc.handle, viewport.x, viewport.y, viewport.width, viewport.height, srcDC,
b.ox, b.oy, b.width, b.height, OS.SRCCOPY);

e.gc.fillRectangle(0, 0, ctrlSize.x, viewport.y);
e.gc.fillRectangle(0, viewport.y, viewport.x, viewport.y+viewport.height);
e.gc.fillRectangle(viewport.x+viewport.width, viewport.y, ctrlSize.x, viewport.y+viewport.height);
e.gc.fillRectangle(0, viewport.y+viewport.height, ctrlSize.x, ctrlSize.y);
}
}

private class ControlEvents implements ControlListener{
public void controlMoved(ControlEvent e){

}
public void controlResized(ControlEvent e){
OnSize();
}
}

private class ScrollKeyEvents implements KeyListener{
public void keyPressed(KeyEvent e){
int x = x0;
int y = y0;
int dist;
if( (e.stateMask & SWT.SHIFT) != 0)
dist = 10;
else
dist = 1;
switch(e.keyCode){
case SWT.ARROW_UP:
y -= dist;
break;
case SWT.ARROW_DOWN:
y += dist;
break;
case SWT.PAGE_UP:
y -= 10*dist;
break;
case SWT.PAGE_DOWN:
y += 10*dist;
break;
case SWT.ARROW_LEFT:
x -= dist;
break;
case SWT.ARROW_RIGHT:
x += dist;
break;
default:
return;
}
SetOrigin(x, y);
}

public void keyReleased(KeyEvent e){

}
}

public MapCanvas(MapEditor ed, Composite parent) {
super(parent, SWT.NO_BACKGROUND);

TCHAR driver = new TCHAR(0, "SCREEN", true);
TCHAR device = new TCHAR(0, "", true);

int screenDC = OS.CreateDC(driver, device, 0, 0);
srcDC = OS.CreateCompatibleDC(screenDC);
OS.DeleteDC(screenDC);
srcBitmap = -1;

addPaintListener(new Painter());
palette = new PaletteData(0xff0000, 0xff00, 0xff);
palette.redShift = -16;
palette.greenShift = -8;
backBufferData = null;
x0 = 0;
y0 = 0;
editor = ed;
viewport = new Rectangle(0, 0, 0, 0);
horizSlider = null;
vertSlider = null;
zoom = 1.0f;
forcedVpSize = new Point(0, 0);
renderedSize = new Point(0, 0);

addControlListener(new ControlEvents());
addKeyListener(new ScrollKeyEvents());
setFocus();
}

public void Dispose(){
OS.DeleteDC(srcDC);
if(srcBitmap != -1)
OS.DeleteObject(srcBitmap);
dispose();
}

public void SetSliders(Slider h, Slider v){
horizSlider = h;
vertSlider = v;
}

public void SetXOrigin(int x){
if(x < vpLimits.x)
x = vpLimits.x;
if(x > vpLimits.width - renderedSize.x + vpLimits.x)
x = vpLimits.width - renderedSize.x + vpLimits.x;
x0 = x;
}

public void SetYOrigin(int y){
if(y < vpLimits.y)
y = vpLimits.y;
if(y > vpLimits.height - renderedSize.y + vpLimits.y)
y = vpLimits.height - renderedSize.y + vpLimits.y;
y0 = y;
}

public Point GetOrigin(){
return new Point(x0, y0);
}

public void SetOrigin(int x, int y){
int origx0 = x0;
int origy0 = y0;
SetXOrigin(x);
SetYOrigin(y);
if( (x0 == origx0) && (y0 == origy0) )
return;
if(horizSlider.isEnabled())
horizSlider.setSelection(x0);
if(vertSlider.isEnabled())
vertSlider.setSelection(y0);
redraw();
update();
}

public void SetZoom(float fact){
if(zoom == fact)
return;
zoom = fact;
OnSize();
}

public float GetZoom(){
return zoom;
}

public void SetViewSize(int width, int height){
if( (forcedVpSize.x == width) && (forcedVpSize.y == height) )
return;
forcedVpSize.x = width;
forcedVpSize.y = height;
OnSize();
}

private int AdjustScrollBar(Slider bar, int min, int totalSize, int visibleSize){
int ret;

if(totalSize > visibleSize){
float oldSliderPos = 0;
if(bar.isEnabled())
oldSliderPos = (bar.getSelection() - bar.getMinimum()) / (bar.getMaximum() - bar.getMinimum());
bar.setEnabled(true);
bar.setMinimum(min);
bar.setMaximum(totalSize - visibleSize + min);
int newSliderPos = (int)(oldSliderPos * (totalSize - visibleSize) + min);
bar.setSelection(newSliderPos);
ret = newSliderPos;
}
else{
bar.setEnabled(false);
ret = min;
}

return ret;
}

public void OnSize(){
Point ctrlSize = getSize();

// get forced or unconstrained viewport size
if( (forcedVpSize.x != 0) && (forcedVpSize.y != 0) ){
viewport.width = (int)(forcedVpSize.x * zoom);
if(viewport.width > ctrlSize.x)
viewport.width = ctrlSize.x;
viewport.height = (int)(forcedVpSize.y * zoom);
if(viewport.height > ctrlSize.y)
viewport.height = ctrlSize.y;
}
else{
viewport.width = ctrlSize.x;
viewport.height = ctrlSize.y;
}

// compute total rendered size
vpLimits = editor.GetViewportLimits();

// compute rendered size
renderedSize.x = (int)(viewport.width / zoom);
if(renderedSize.x > vpLimits.width)
renderedSize.x = vpLimits.width;
renderedSize.y = (int)(viewport.height / zoom);
if(renderedSize.y > vpLimits.height)
renderedSize.y = vpLimits.height;

// compute viewport size again from the rendered size
viewport.width = (int)(renderedSize.x * zoom);
viewport.height = (int)(renderedSize.y * zoom);

// center
viewport.x = (ctrlSize.x - viewport.width) / 2;
viewport.y = (ctrlSize.y - viewport.height) / 2;

// adjust the scrollbars
x0 = AdjustScrollBar(horizSlider, vpLimits.x, vpLimits.width, renderedSize.x);
y0 = AdjustScrollBar(vertSlider, vpLimits.y, vpLimits.height, renderedSize.y);

redraw();
update();
}

public Point GetMapRelativeCoords(int x, int y){
x -= viewport.x;
y -= viewport.y;
if( (x < 0) || (y < 0) || (x >= viewport.width) || (y >= viewport.height) )
return null;
x = (int)(x / zoom) + x0;
y = (int)(y / zoom) + y0;
return new Point(x, y);
}

public Point GetMapCell(int x, int y){
Point p = GetMapRelativeCoords(x, y);
if(p == null)
return null;

return editor.tileSet.GetMapCell(p.x, p.y);
}
}

--------------010609030803020500040304--
Previous Topic:Drawing a grey-scale image
Next Topic:what is the best way to reload a widget to update it with new data
Goto Forum:
  


Current Time: Tue Dec 12 10:39:14 GMT 2017

Powered by FUDForum. Page generated in 0.01759 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software