2007년 10월 13일 토요일

Java 3D 회전 & 2D 메모리 버퍼

최근에 Java에서 3D그래픽 처리를 하기 위해서 예전에 C로 만들어 두었던 3D소스를 Java로 포팅했다.
Java와 C의 문법이 아주 비슷하기 때문에 몇 가지만 수정하니 바로 동작 되었다.



3D 회전 코드

double cos_th, sin_th, cos_pi, sin_pi, cam_rr;
int gx, gy;

void setCamera (double th, double pi, double r) {
    cos_th = Math.cos (-th);
    sin_th = Math.sin (-th);
    cos_pi = Math.cos (-pi);
    sin_pi = Math.sin (-pi);
    cam_rr = r;
}

void rotPoint (double x, double y, double z) {
    double x1, x2, y1, z1;
   
    x1 = -cos_th*x - sin_th*y;
    y1 = -sin_th*x + cos_th*y;
    z1 = -cos_pi*z - sin_pi*x1;
    x2 = -sin_pi*z + cos_pi*x1;
    x = cam_rr - x2;
    y = y1 / x;
    z = z1 / x;
    gx = (int)(y);
    gy = (int)(z);
}


메모리 버퍼에 그림을 그리고 보여주는 코드

int bitmap[];
BufferedImage bi;

void init (int width, int height) {
    bi = new BufferedImage (width, htight, BufferedImage.TYPE_INT_RGB);
    bitmap = new int[width*height];
}

void draw (Graphics g, int x, int width, int y, int height) {
   
    // Modify the values in the pixels array at (0, 0, width, height)
   
    bi.setRGB (0, 0, width, height, bitmap, 0, width);
    g.drawImage (bi, x, y, null);
}

코드의 성능은 200*200 크기에서는 110FPS, 1400*1000 에서는 4.8FPS.
3D 계산이 많기는 하지만 너무나 큰 차이가 나서 어디가 문제인지 찾아보니 bi.setRGB() 와 g.drawImage() 를 빼면 200*200 크기에서는 200FPS, 1400*1000 에서는 28FPS.
그림을 실제 화면으로 뿌려 주는데 83% 의 시간을 차지하고 있다.


개선된 메모리 버퍼에 그림을 그리고 보여주는 코드

int bitmap[];
MemoryImageSource source;
Image image;

void init (int width, int height) {
    bitmap = new int[width*height];
    source = new MemoryImageSource (width, height, bitmap, 0, width);
    source.setAnimated (true);
    image = createImage (source);
}

void draw (Graphics g, int x, int width, int y, int height) {
   
    // Modify the values in the pixels array at (0, 0, width, height)
   
    source.newPixels (0, 0, width, height);
    g.drawImage (image, x, y, null);
}

코드의 성능은 200*200 크기에서는 170FPS. 1400*1000 에서는 14.2FPS 로 296% 증가.

2007년 10월 8일 월요일

Java Thread & Swing

Java 에서 일반적인 방식으로 GUI 를 다루는것은 안전하다. 그러나 GUI 에 영향을 주는 명령을 수행하는 thread 를 사용하거나 표준적인 event 가 아닌 다른 event 에 응답해서 이미 스크린상에 보여지고 있는 GUI 를 조작할 경우에는 다음 규칙을 지켜야 한다.

규칙: Swing component 가 realized 되고 있다면 그 component 에 영향을 주거나 의존하는 모든 코드들은 event-dispatching thread 안에서 실행 되어야 한다. (일부 thread-safe 한 API 도 있다)

realized 란. 다음 세 명령어에 의해 이루어 진다.
[ setVisible(true), show, pack ]

예외1. repaint(), revalidate() thread 에서 안전하다.
예외2. Listener리스트는 수정은 thread 에서 안전하다.


Event-Dispatching Thread 에서 코드를 실행하는 방법
GUI 초기화가 이루어진 이후의 대부분의 작업은 자동적으로 event-dispatching thread 에서 일어난다. 일단 GUI가 realized 되면, 버튼을 누르거나 마우스를 클릭하는 등의 이벤트가 프로그램의 대부분을 구성하게 되는데, 이러한 이벤트들은 항상 event-dispatching thread 에서 다루어진다.

그러나 통신 프로그램이나 thread 방식의 프로그램은 GUI가 가시화 된 이후에 non-event-driven GUI 작업을 수행할 때도 있다.

SwingUtilities 클래스는 event-dispatching thread 안에서 코드를 실행하도록 돕는 두개의 메소드를 제공한다.


invokeLater
이 메소드는 일부 코드가 event-dispatching thread 에서 실행될것을 요청한다. 이 메소드의 결과는 코드가 실행되기를 기다리지 않고 즉시 반환된다. 보류중인 모든 AWT 이벤트가 처리된 뒤에 실행된다.

Runnable InvokeTest = new Runnable() {
    public void run() {
        System.out.println("Thread " + Thread.currentThread());
    }
};
SwingUtilities.invokeLater(InvokeTest);


invokeAndWait
invodeLater 와 비슷한 기능을 한다. 다른점이라면 이 메소드는 코드가 실행되는동안 기다린다는 것이다. 이 메소드는 EventDispatchThread로 부터 호출하면 안된다. 이 메소드보다 invokeLater 메소드를 추천한다.

Runnable InvokeTest = new Runnable() {
    public void run() {
        System.out.println("Thread " + Thread.currentThread());
    }
};
try {
    SwingUtilities.invokeAndWait(InvokeTest);
} catch (Exception e) {
    e.printStackTrace();
}