[출처]
https://plus.google.com/+LeonardoYongUkKim/posts/SW2EmXSYJu9
http://dalinaum-kr.tumblr.com/post/70647507316/glsurfaceview-1
http://dalinaum-kr.tumblr.com/post/70648262152/glsurfaceview-2
http://dalinaum-kr.tumblr.com/post/71404014315/glsurfaceview-3
이 글은 예전에 구글 플러스에 올렸던 글을 다시 정리하며 시작합니다.
예전에 2개 커밋에 대해 분석한 것을 먼저 옮기고 그 후에 새로운 커밋에 대해서도 정리하려 합니다.
저는 GLSurfaceView를 사용하며 버그도 많이 만나보고 당해왔던(?)것 들도 많습니다.
그래서 GLSurfaceView를 조금 더 잘 파악하기 위해 커밋 단위로 분석해 보려 합니다.
오늘의 타겟입니다.
commit 29e0bd2f5a80fdfe0e5b482a1df86363afcecbfa
Author: Mathias Agopian
GLSurfaceView defaults to 888 instead of 565
이번 커밋부터 565가 아닌 888으로 바뀐 것입니다.
픽셀 포맷을 말할때는 보통 RGBA나 RGB를 이야기하는데요.
여기에서 세자리만 이야기한 것은 RGB만 따지는 것입니다.
RGB가 각각 565에서 888로 되었다는 것이지요.
@@-968,13+968,13 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
}
/**
- * This class will choose a RGB_565 surface with
+ * This class will choose a RGB_888 surface with
* or without a depth buffer.
*
*/
private class SimpleEGLConfigChooser extends ComponentSizeChooser {
public SimpleEGLConfigChooser(boolean withDepthBuffer) {
- super(5, 6, 5, 0, withDepthBuffer ? 16 : 0, 0);
+ super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
}
}
코드를 보시면 EGLConfigChooser에서 5,6,7를 선택하던 것을 8,8,8로 선택하는 것을 볼 수 있습니다.
아주 간단한 변경이죠. 이제 용량을 위해서 저 화질 그래픽을 사용하려면 별도로 픽셀 포맷을 바꿔줘야 하게 되었습니다.
setRender가 호출되기 전에 EGLConfigChooser를 설정하지 않으면 기본으로 SImpleEGLConfigChooser가 선택되게 되는데요.
이 인스턴스는 mEGLConfigChooser에 설정되어 GLSurfaceView가 픽셀 포맷으로 활용하게 됩니다.
이번 목표입니다.
commit cee059dc764a57860da6b1574952089680b5ddc6
Author: Jack Palevich
Date: Mon Apr 16 12:25:36 2012 -0700
Improve GLSurfaceView Pausing.
이번 커밋은 GLSurfaceView가 onPause에서 가끔 발생하던 문제를 잡은 것입니다.
먼저 문제가 있던 코드를 보겠습니다.
if (mHaveEglSurface && mPaused) {
stopEglSurfaceLocked();
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
boolean preserveEglContextOnPause = view == null ?
false : view.mPreserveEGLContextOnPause;
if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
stopEglContextLocked();
}
if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {
mEglHelper.finish();
}
}
GLSurfaceView는 EGLSurface를 가지고 있다면 GLSurfaceView가 onPause로 진입하는데 문제는 없는데 그렇지 않은 경우에는 문제가 되었습니다.
여러 GLSurfaceView를 빠르게 전환한다면 EGLSurface를 제대로 생성하지 못한 채 다른 뷰로 넘어갈 수 있습니다.
그때 GLSurfaceView들은 검은 화면을 만나게 되고 뷰를 끄지 않고서는 문제를 해결할 수 없습니다. 끔찍한 일이죠.
Jack Palevich는 아래와 같이 고쳤습니다.
if (pausing && mHaveEglSurface) {
stopEglSurfaceLocked();
}
if (pausing && mHaveEglContext) {
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
boolean preserveEglContextOnPause = view == null ?
false : view.mPreserveEGLContextOnPause;
if (!preserveEglContextOnPause || sGLThreadManager.shouldReleaseEGLContextWhenPausing()) {
stopEglContextLocked();
}
}
if (pausing) {
if (sGLThreadManager.shouldTerminateEGLWhenPausing()) {
mEglHelper.finish();
}
}
이제 각 if 절이 제대로 EGLSurface와 EGLContext를 정리합니다.
이 패치는 사실 아주 중요한 패치입니다.
젤리빈 이전의 버전에서 여러 GLSurfaceView를 사용하기 원한다면 이 패치 이후의 GLSurfaceView를 복사해서 사용하는 것이 좋습니다.
그렇지 않으면 검은 화면을 볼 수 있습니다.
GLSurfaceView에는 플랫폼 API 호출이 일부 있는데 그것을 제외하는 일은 쉬운 일이니 직접해보세요.
오늘 살펴볼 커밋도 Jack Palevich의 커밋입니다.
commit ea62b95e7c562e8d0052441d8a0d7de6d919320f
Author: Jack Palevich
Date: Mon Mar 18 22:44:58 2013 -0700
Fix createSurface / eglCreateWindowSurface race.
Previously we could have returned from createSurface on the main thread
before calling eglCreateWindowSurface on the GLThread. That could lead
to a problem because the surface could be destroyed before
eglCreateWindowSurface got a chance to run.
Bug: 8328715
Change-Id: I273149f7d4b165abbe218a91ff54083f3f498cb0
이 패치를 보기 이전에 간단히 배경 설명을 드리겠습니다.
GLSurfaceView는 내부에 GLThread를 가지고 있고 이 스레드는 내부적으로 이중 무한 루프를 돌리도록 구성되어 있습니다.
이 무한 루프들은 CPU 자원을 적절히 소모히기 위해 notifyll과 wait를 통해 자원을 통제하고 양보하도록 되어있습니다.
무한 루프 내부의 무한 루프가 다른 작업을 하기 전에 먼저 돌게 되는데요.
외부에서 종료를 원할 때, 처리할 이벤트가 있을 때, 그릴 준비가 완료되었을 때 밖으로 나갑니다.
진짜 그릴 수 있을 때만 내부의 이중 루프를 벗어나 단일 루프가 반복되며 그림을 그리는 것이지요.
GLSurfaceView가 그려지기 위해서는 문맥을 담은 EGLContext와 그릴 평면인 EGLSurface와 그려질 EglDisplay 등이 필요합니다.
EGLSurface는 GLSurfaceView에서 오래된 골치거리였던 것 같습니다.
@@ -1445,6 +1445,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
Log.i("GLThread", "waiting tid=" + getId()
+ " mHaveEglContext: " + mHaveEglContext
+ " mHaveEglSurface: " + mHaveEglSurface
+ + " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface
+ " mPaused: " + mPaused
+ " mHasSurface: " + mHasSurface
+ " mSurfaceIsBad: " + mSurfaceIsBad
로그를 추가했네요. 넘어갑시다.
@@ -1468,8 +1469,14 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
if (LOG_SURFACE) {
Log.w("GLThread", "egl createSurface");
}
- if (!mEglHelper.createSurface()) {
+ if (mEglHelper.createSurface()) {
synchronized(sGLThreadManager) {
+ mFinishedCreatingEglSurface = true;
+ sGLThreadManager.notifyAll();
+ }
+ } else {
+ synchronized(sGLThreadManager) {
+ mFinishedCreatingEglSurface = true;
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
이전에는 mEglHelper.createSurface가 실패했을 때만 mSurfaceIsBad를 true로 체크하고 notifyAll을 했더 것을 볼 수 있습니다.
mEglHelper.createSurface는 EGLSurface를 얻어옵니다.
SurfaceIsBad가 true인 경우에는 readyToDraw 메서드가 실패하고 렌더링을 취소합니다.
이전과의 차이는 성공했을 때도 sGLThreadManager.notifyAll()을 호출 하여 sGLThreadManager.wait()를 호출하고 있던 스레드를 깨웁니다.
그럼 sGLThreadManager.wait()를 호출하고 잠드는 메서드에 무언가 변화가 있을 것입니다. 아래를 보세요.
@@ -1595,8 +1602,11 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
Log.i("GLThread", "surfaceCreated tid=" + getId());
}
mHasSurface = true;
+ mFinishedCreatingEglSurface = false;
sGLThreadManager.notifyAll();
- while((mWaitingForSurface) && (!mExited)) {
+ while (mWaitingForSurface
+ && !mFinishedCreatingEglSurface
+ && !mExited) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
위의 코드는 GLThread의 surfaceCreated() 메서드입니다.
해당 메서드는 GLSurfaceView 내부의 surfaceCreated에 의해 호출되죠. mFinishedCreatingEglSurface가 추가되었습니다.
mFinishedCreatingEglSurface가 true가 될 때까지 기다리게 변경된 코드입니다.
변경된 코드에서는 EGLSurface가 생성되거나 실패되는게 확실해져야 surfaceCreated가 종료됩니다.
!mExited가 있기 때문에 객체가 종료되는 경우에도 종료될 것으로 볼 수 있습니다.
이전까지는 EGLSurface의 생성이 처리되지 않아도 surfaceCreated가 호출되었음을 예상해 볼 수 있습니다.
주석을 보면 EGLSurface가 생성되기전에 surface가 파괴되는 경우에 문제가 되는 것을 알 수 있습니다.
surface는 GLSurfaceView가 가지고 있던 것이라고 보시면 되는데 쉽게 보아서 GLSurfaceView가 종료되었다고 생각하시면 됩니다.
@@ -1735,6 +1745,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback
private boolean mWaitingForSurface;
private boolean mHaveEglContext;
private boolean mHaveEglSurface;
+ private boolean mFinishedCreatingEglSurface;
private boolean mShouldReleaseEglContext;
private int mWidth;
private int mHeight;
mFinishedCreatingEglSurface를 사용하기 위해 선언한 부분입니다.
'IT_Programming > Android_Java' 카테고리의 다른 글
[Android] Webview 에서 이미지 파일 다운로드 하기. (0) | 2015.05.07 |
---|---|
[펌_Android] 시스템 설정의 폰트크기 에 따라 웹뷰(WebView)의 레이아웃이 변경되는 경우 (Lollipop 이슈) (0) | 2015.05.07 |
Custom DEBUG용 KEYSTORE 만들기 (0) | 2015.04.20 |
ANDROID API 18에서 STARTFOREGROUND() (0) | 2015.04.20 |
어떤 KEYSTORE로 SIGN되었는지 확인하는 방법 (0) | 2015.04.20 |