디지털 장인정신

블로그 이미지
WebKit개발자의 혼자쓰는 블로그
스페로

Article Category

All (71)
Technology (47)
Creativity (19)
Private (4)

Recent Comment

Recent Trackback

Calendar

«   2018/10   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      

Archive

My Link

  • Total336,152
  • Today28
  • Yesterday46
  1. 2012.01.30
    Android GPU SoC 정리 (2)
  2. 2012.01.06
    Android에서의 eglSwapBuffer에서 일어나는 일
  3. 2010.05.08
    Android에 Picture Class는 무엇에 쓰는 물건일까?
  4. 2010.05.08
    Android에서 View를 bitmap으로 정장하는법 (1)
  5. 2010.05.08
    View의 Design Patten (1)
  6. 2010.05.05
    Google I/O 2009 - ...Make your Android UI Fast and Efficient
  7. 2010.05.04
    Android View.getDrawingCache() 분석 (2)
요즘 회사에서 GPU관련 일을 하고 있는데 안드로이드에서 아무리 최적화를 해도 IPhone 4S보다 1.5~2배 느리다. 그 이유는 바로 GPU 파워가 다르기 때문이다. 결론적으로 안드로이드 디바이스가 IPhone 4S의 GPU 성능을 앞서는 시점은 2012 1Q or 2Q가 아닐까 생각이된다.

현재 모바일 AP는 3강 체제라고 할 수 있다.
TI(Texas Instrument) OMAP : http://en.wikipedia.org/wiki/OMAP
삼성 Exynos : http://en.wikipedia.org/wiki/Hummingbird_Processor#Exynos_3110
퀄컴 Snapdragon : http://en.wikipedia.org/wiki/Snapdragon_(system_on_chip)

그리고 에플 A5 (made in 삼성) : http://ko.wikipedia.org/wiki/%EC%95%A0%ED%94%8C_A5

그럼 각 AP들이 GPU는 무엇을 쓸까? (CPU는 모두 ARM의 Cortex 시리즈를 쓴다)
TI : TI의 SGX 시리즈
Exynos : ARM의 Mali 시리즈
Snapdragon : 퀄컴이 AMD에서 인수해서 만들고 있는 Adreno 시리즈
A5 : TI의 SGX 543MP2

참고로 postfix로 붙는 MP2는 multi-processor 2라는 말이다. 즉 코어가 2개라는 뜻. MP4는 코어가 4개라는 뜻

2012년 1월 현재 빅3에서 나온 가장 최신의 AP를 탑제한 제품은 다음과 같다.
삼성 Galaxy Nexus : IT OMAP 4460 (45nm, 2*Cortex-A9@1.5GHz, PowerVR SGX 540 384MHz)
삼성 Galaxy 2 : Exynos 4210 (45nm, 2*Cortex-A9@1.4GHz, Mali-400 MP4)
 LG Optimus LTE : Snapdragon S3(MSM8660) (45nm, 2*Cortex-A9@1.5GHz, Adreno 220)

AnandTech의 벤치마크 결과를 보면 http://www.anandtech.com/show/5133/galaxy-nexus-ice-cream-sandwich-initial-performance
GLBenchmark 2.1 - Egypt - Offscreen
SGS2의 Mali-400 MP4는 A5의 SGX 543MP2의 절반의 성능이고
Galaxy Nexus의 PowerVR SGX 540는 다시 SGS2의 2/3의 성능을 보이고 있다.
아직 Adreno 220은 명함도 못내밀 수준인것 같다.
쉽게 말하면 IPhone 4S의 GPU가 아직까지는 슈퍼 짱이다.


그럼 빅3의 AP의 GPU가 A5의 SGX 543MP2를 따라잡는 차기 제품은 무엇일까?

1. 퀄컴 Snapdragon S4 MSM8960 : 28nm, 2*Krait@1.5GHz, Adreno 225 (http://gigglehd.com/zbxe/6509045)
발매시기 : 2012 1Q
GPU성능 : SGX 543MP2정도일것 같음
SoC 칩의 GPU 비교
Adreno 225
PowerVR SGX540
PowerVR SGX543
PowerVR SGX543MP2
Mali-400 MP4
GeForce ULP
GeForce++ (Kal-El)
SIMD 이름
-
USSE
USSE2
USSE2
Core
Core
Core
SIMD 대응 데이터 스트림의 양
8
4
4
8
4+1
8
12
각 SIMD의 MAD 수
4
2
4
4
4/2
1
?
총 MAD
32
8
16
32
18
8
?
연산 성능(GFLOPS)@200MHz
12.8
3.2
6.4
12.8
7.2
3.2
?
연산 성능(GFLOPS)@300MHz
19.2
4.8
9.6
19.2
10.8
4.8
?


발매 디바이스 예상 : LTE에 올인하고 있는 LG Optimus XXX LTE 정도가 아닐까?


2. 삼성 Exynos 4412 : 32nm, 1.5-1.8(rumored) GHz Quad-core ARM Cortex-A9, ARM Mali-T604
발매시기 : 2012 3~4월
GPU성능 : Mali-T604의 성능이 Mail 400MP4의 2.5배로 추정되고 있다. 즉 SGX 543MP2보다 조금 앞설것으로 예상된다.
 
출처 http://gamma0burst.tistory.com/369
Mali-T604 부터는 SGX540처럼 Vertex Shader와 Pixel Shader가 통합된다고 한다.

발매 디바이스 예상 : 겔러시S3 ??


3. TI OMAP : 45nm, 1.5-1.8 GHz dual-core ARM Cortex-A9, PowerVR SGX544 @ 384 MHz + dedicated 2D graphics core
발매시기 : 2012 2Q
GPU성능 : SGX 544MP가 아니라 그냥 SGX 544이다. A5의 SGX 543MP2와 코어를 직접 비교 한다고 해도 큰 차이가 없을 듯 하다. 즉 A5의 SGX 543MP2보다 두배정도 느리지 않을까란 생각
발매 디바이스 예상 : TI의 강점의 GPU인데 왜 SGX 543MP2를 탑제한 OMAP AP를 출시 않하는지 모르겠다. 당분간 OMAP은 시장에서 버림받을것 같다.

결론
2012 1Q~2Q에 Snapdragon과 Exynos의 다음세대가 나오면 IPhone 4S와 IPad 2는 CPU와 GPU 둘다 안드로이드에게 밀리게 될것이다.
6월에 IPhone 5가 출시될텐데 다시 안드로이드와 하드웨어 격차를 벌릴수 있을것 같지가 않다. (주관적인 제 느낌)


삼성 Exynos에 대한 불만 한가지.

삼성은 A5를 만들었으면서도 왜 Exynos에 SGX 543MP2를 안쓰고 Mali 400MP4를 고집하는지 모르겠다. 성능이 반밖에 안나오는데... 여러 기사에서는 가격문제라고 한다.

GPU를 intensive하게 쓰는 프로그램을 개발하는 개발자 입장에서 Mali 400MP4는 상당히 문제가 많은 GPU이다.
1. 안정적이지 않다 : 약간 무거운 shader를 쓰면서 index를 2만개쯤 그리라고 시키면 process가 죽는다.
2. 버그가 많다 : GLSL의 sqrt, pow가 특정 영역의 실수만 정확하게 계산을 한다. 등등 

참고자료
http://www.anandtech.com/show/5133/galaxy-nexus-ice-cream-sandwich-initial-performance 
http://nena.se/nenamark/view?version=2
http://gigglehd.com/zbxe/6509045
http://gamma0burst.tistory.com/361
http://gamma0burst.tistory.com/369
http://gamma0burst.tistory.com/351
http://en.wikipedia.org/wiki/OMAP
http://en.wikipedia.org/wiki/Hummingbird_Processor#Exynos_3110
http://en.wikipedia.org/wiki/Snapdragon_(system_on_chip)
http://ko.wikipedia.org/wiki/%EC%95%A0%ED%94%8C_A5
Trackback 2 and Comment 2
ICS 소스코드를 분석한 것이다.
'안드로이드 아나토미' 책의 도움을 많이 받았다. (하지만 소스가 그새(진저-ICS) 엄청 바뀌었네요.)

결론

1. 앱에서의 eglSwapBuffer와 SurfaceFliger의 eglSwapBuffer는 다른 라이브러리의 eglSwapBuffer이다.
2. 앱에서 eglSwapBuffer는 그려진 그림(ANativeWindowBuffer*)을 SurfaceFliger에 post한다.
3. SurfaceFliger의 eglSwapBuffer는 합성한 그림을 LCD에 뿌려준다.

앱에서는 libGLES_android static library(./frameworks/base/opengl/libagl2/)의 eglSwapBuffer을 쓴다.
- libGLES_android는 ./build/core/user_tags.mk 에서 링크한다. 아마 안드로이드 소프트웨어 스택의 '애플리케이션 프레임워크'의 빌드시 user_tags.mk가 include될 것이라 추정된다.

SurfaceFliger(./frameworks/base/services/surfaceflinger/) 에서는 libEGL, libGLESv1_CM (./frameworks/base/opengl/libs/)의 eglSwapBuffer을 쓴다.


 앱에서의 eglSwapBuffer 분석
- egl_window_surface_v2_t::swapBuffers (./frameworks/base/opengl/libagl2/src/egl.cpp)
-- buffer에 previousBuffer와 차이가 나는 영역을 복사한다.
-- nativeWindow->queueBuffer(nativeWindow, buffer); buffer를 binder IPC로 SurfaceTexture에게 보낸다.(아래 분석)
-- nativeWindow->dequeueBuffer(nativeWindow, &buffer) 로 FramebufferNativeWindow에게 다음 그림을 그릴 buffer를 요청한다. handle만 요청하는 것이지 heap을 요청하는 것은 아니다.
-- depth.data    = (GGLubyte*)malloc(depth.stride*depth.height*2); SurfaceFliger에는 depth 개념이 없으니깐 depth-buffer는 어플 process에서 malloc한다.
-- lock(buffer, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, &bits) GRALLOC은 GPU 밴더가 구현해야 하는 HAL API다. gpu가 쓸 메모리를 GRALLOC을 통해 얻어온다. 그 메모리 공간은 shared memory로 만드므로 SurfaceFliger도 copy없이 접근할 수 있다.  즉 그림을 그릴 공간을 앱의 process가 만든다. (일반적인 뷰는 SurfaceFliger가 공간을 잡아준다고 하네요 from 아나토미)


SurfaceFlinger와 어떻게 통신하는가? (nativeWindow->queueBuffer(nativeWindow, buffer) 이후 분석)
http://www.diagrammr.com/edit?key=dsLkHGlxN4B
SurfaceTextureClient(./framework/base/include/gui/SurfaceTextureClient.h)가 ANativeWindow(./system/core/include/system/window.h)이다.
그 방법이 굉장히 c스러운데, 상속 같은 걸 사용하는게 아니라 union같이 그냥 ANativeWindow의 byte들을 SurfaceTextureClient구조로 덥어쓴다. 둘 사이의 변환은 reinterpret_cast라는 무시무시한 방법을 이용한다.
ANativeWindow는 queueBuffer, dequeueBuffer 등의 함수포인터를 customizing point로 제공하는데 SurfaceTextureClient에서 모두 hooking하는식으로 구현한다.(./framework/base/libs/gui/SurfaceTextureClient.cpp)
즉 egl_window_surface_v2_t::swapBuffers에서 SurfaceTexture에 IPC를 통하여 buffer가 변경되었음을 notify해준다고 이해할 수 있다.
Layer가 바뀌었으니 어찌어찌(?) 하다보면 SurfaceFlinger::threadLoop() (./frameworks/base/services/surfaceflinger/) 가 실행된다. (어떻게인지 잘 모르겠음. Layer가 바뀌면서 inval을 SurfaceFlinger에게 날리고 SurfaceFlinger::threadLoop()가 깨어나는 것 같은데 정확한 코드를 못찾았음)
- waitForEvent(); inval 이벤트를 받고
- handleRepaint(); 그리고 (각 layer들을 OpenGL ES를 이용하여 compositing 한다)
- postFramebuffer(); eglSwapBuffer 때린다.

SurfaceFlinger가 앱이 GRALLOC으로 잡을 공간을 Texture로 만드는 부분 분석
- Layer::lockPageFlip(bool& recomputeVisibleRegions) : SurfaceFlinger가 Layer들을 업데이트 할때 불린다.
-- SurfaceTexture::updateTexImage() (./frameworks/base/libs/gui/SurfaceTexture.cpp)
--- SurfaceTexture::createImage(EGLDisplay dpy, ...) : EGLClientBuffer로 EGLImage를 만든다. EGLClientBuffer가 앱이 GRALLOC으로 잡은 buffer일 것이다.

안드로이드는 GPU밴더에게 비디오 메모리를 제어할 수 있도록 Gralloc(HAL interface) (./hardware/libhardware/include/hardware/gralloc.h)을 요구한다. (첨언. 안드로이드 HAL은 linux의 GPL을 피하기 위해 HAL을 유저레벨에서 동작시킨다.(from 아나토미))
Gralloc의 구현과 eglCreateImageKHR의 구현 모두 GPU밴더의 몫이다. GPU밴더가 미치지 않았다면 eglImage를 non copy로 만들수 있도록 최선을 다 했을 것이다.
Trackback 4 and Comment 0
결론은 당신의 View가 canvas를 채우기 위해 jni pass를 하는 연산이 많이 수행된다면 Picture로 한번 canvas를 감싸는것을 고려해 볼수 있다.

developer.android.com의 Picture 설명을 보면
A picture records drawing calls (via the canvas returned by beginRecording) and can then play them back (via picture.draw(canvas) or canvas.drawPicture). The picture's contents can also be written to a stream, and then later restored to a new picture (via writeToStream / createFromStream). For most content (esp. text, lines, rectangles), drawing a sequence from a picture can be faster than the equivalent API calls, since the picture performs its playback without incurring any java-call overhead.
JNI콜의 오버해드를 줄일수 있다고 나온다. 왜그럴까?

http://www.androidjavadoc.com/1.0_r1_src/android/graphics/class-use/Picture.html 를 보면 수많은 View중 굳이 WebView만이 Picture class를 이용해서 그림을 그린다. 그 이유는 WebView의 canvas에 그림을 채우기 위해 WebKit의 Render Object에 수많은 함수 콜을 해야 할 것이다. 그러면 jni의 overhead가 걸릴텐대 그래서 Picture를 쓴다.

어떻게 Picture가 jni의 overhead를 없에는지 설명해보자

우선 사용법부터 설명하면

보통은 View의 Draw를 overriding할때
Draw(canvas) {
super.onDraw(canvas);
}
일텐데 Picture를 사용하면
Draw(canvas) {
super.onDraw(mPicture.beginRecording(getWidth(), getHeight());
mPicture.endRecording();

canvas.drawPicture(mPicture);
}
라는 식으로 코딩을 하게 된다.

한마디로 Picture의 member variable canvas에 View의 child들을 순회하면서 그림을 그린뒤
Picture의 canvas를 대빵 View의 canvas가 받아서 한꺼번에 그리는 것이다.

중간에 중개자가 끼었는데 속도가 오히려 빨라진다? 가 이상하게 느껴질수 있는데.. jni의 overhead를 상쇄하므로써 속도에 이익을 얻는것이 결론이다.

Android의 모든 graphic library는 Skia library와 거이 1:1 매핑된다. 
한마디로 어떤 Class의 method를 쓰더라도  jni를 거쳐서 skia가 실행하는것이다.

Picture.beginRecording을 하면 jni밑에 C에서 SkPicture가 SkCanvas의 instance가 그림을 그릴 준비를 한다. 그 뒤 Picture.beginRecording에 그림을 그리면 java까지 올라올 필요 없이 C에서 skia로 그림을 그린다.. jni오버해드가 사라지는 지점이다. 그림을 다 그리면 java에서 Picture의 canvas를 가지고 그림을 그리는것이다.


결론은
1. jni오버해드를 줄이고 싶을때  Picture를 써라
2. View의 그림을 변형해서 여러번 그리고 싶을때 Picture를 써라 (가령 거울대칭으로 그림을 그리고 싶다 등등)


Trackback 4 and Comment 0
결론은 두가지 방법이 있다.
1. View의 canvas가 그림을 framebuffer가 아닌 bitmap에 그리게 하기
2. View.getDrawingBuffer() 이용하기

우선 성능 측면에서는 1번이 약간 앞선다. 두가지 방법의 메카니즘을 설명해보자

1. View의 canvas가 그림을 framebuffer가 아닌 bitmap에 그리게 하기

우선 사용법은

View를 상속받아  onDraw를 overriding하면 그림을 screen(framebuffer겠죠?)에 그리지 않고 bitmap에 그릴수 있다

public void onDraw(Canvas canvas){
canvas.setBitmap(bitmap);
bitmap.eraseColor(Color.WHITE);
super.onDraw(canvas);
}

위의 코드처럼 bitmap을 하나 잡아서 그것을 canvas에 장착시키면 그림을 bitmap에 그린다. screen에는 안그리므로 까만화면이 나올 뿐이다.

View ----------- canvas ----framebuffer
              Draw                     |---bitmap

즉 canvas 가 그리는 곳을 bitmap으로 살짝 틀어줌으로서 그림이 그려진 memory를 얻을수 있다.
참고로 Draw가 onDraw를 다시 콜한다. 지난 포스트를 참고하자.


2. View.getDrawingBuffer() 이용하기

우선 getDrawingCache의 내부구현을 보면
View.getDrawingCache
                canvas = softref.get(); //cavas를 softReference로 만들어놓고 쓸때 꺼냅니다. 
                bitmap = createBitmap(width, height, config); //이놈이 jni로 c로 가서 SkBitmap을 새로 alloc합니다. 가장 병목인 곳
canvas.setBitmap(bitmap);
bitmap.eraseColor(Color.WHITE);
draw(canvas);
즉 bitmap공간을 새로 잡고 그것을 별도로 사용하는 canvas에 붙여서 그림을 bitmap에 직접 그린다. 전자와 mechanism은 같지만 bitmap을 새로 alloc한다는 단점이 있다.

앞에 그림에서 앞에 몇가지 과정이 더붙은 모양세다

View ---------------------------------- View ----------- canvas ---- bitmap
          getDrawingCache -- canvas.setBitmap                        Draw 

원래 View의 그림도 그린다면 getDrawingCache다 View.draw를 콜하고 원래 그림을 그리는 쓰래드도 View.draw를 콜하므로 똑같은 그림을 그리기 위해 두번 연산하게 된다.



참고로
 - softReference란 cache를 만들때 java에서 잘 사용하는 기법인데, 오직 메모리가 모자를때 garbage collector에 의해 메모리가 해제됩니다. 즉 메모리를 많이 차지하면서 계속써야되는놈을 softReference로 만들어놓으면 out of memory로 app이 중지하는것을 방지할 수 있습니다.
 - Android의 canvas, bitmap, paint 등의 그림을 그리는 class들이 skia의 SkCanvas, SkBitmap, SkPaint들과 거의 1:1로 매핑되어있네요.
Trackback 0 and Comment 1
Android의 View에 대해 잘 설명한 글이 있어 퍼온다.

데이터 흐름을 설명하면
  • 어느 View에선가 invalidate()  호출
  • 대장 View.draw()
    • 대장 View.onDraw()  -> 자기를 그리고
    • 대장 View.dispatchDraw -> 자식을 콜한다
      • 자식이 layout이면 child.dispatchDraw
      • view이면 child.draw
      • 자식view에서도 제귀적으로 돈다
  • 여기쯤에서 invalidate()



http://som-itsolutions.blogspot.com/2009/06/composite-design-pattern-in-android.html

WEDNESDAY, JUNE 3, 2009

Composite Design Pattern in Android View and Widget

The Intent of this design pattern is stated in the GoF book as "Compose Objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly".

To explain it in a simpler fashion, let me give the same example as given in the GoF book. Suppose there is an object called Picture, a graphics object. This picture may consist of other pictures recursively as well as primitive objects like line, rectangle objects etc. All of these part objects which make the whole picture conform to the same Graphic interface. Hence to the client, a part object appears same as the whole picture consisted of other part objects. To draw a whole object, the client simply traverses through the whole picture and draws different parts.

The class diagram of the composite design pattern will look like the following:










The following participants take part in this design pattern:

Component - 

it declares the interface for the objects ( part as well as whole)
helps in managing the objects (adding, removing)

Leaf -

represents leaf objects which don't have any children
these are the primitive objects

Composite - 

defines behavior for components having children
stores the children

Client -

takes help of the Component interface to manipulate different objects

Its all about the theoretical side of the Composite Design Pattern. Now let us try to dissect the Android View and the Widget folders (which are available at \\base\core\java\android) to see how this design pattern has been implemented there.

In Android, the View class works as the Component class. However, the child management part (add component, remove component) has been moved to the Composite class which is the ViewGroup class. Actually the Add and Remove of a component has been declared in an interface called ViewManager and the ViewGroup implements that interface. Also the interface for a Composite object is declared as ViewParent interface and the ViewGroup (the Composite object) implements that as well.

The leaf classes like Button, ImageView etc are deduced either by directly subclassing the View (Component) or from the subclasses of the View (for example, the Button class is derived from TextView class which in turn is directly derived fron the View class). The Composite Class (ViewGroup) is deduced by directly subclassing the View and by implementing the two interfaces namely ViewParent (which defines the interface of a composite object) and ViewManager (which defines the interface from adding and removing components). 

As expected the getParent function which is needed to get the Parent of a component is put in the View class (the Component).

The Composite object (ViewGroup) has an array to hold its children.

The simplified version of the class diagram of the Android View and Widgets are as follows:



For simplicity i have not shown the two interfaces namely ViewManager and ViewParent.

Now let us consider the class diagram as presented in the beginning of this discussion. There is a function called Operation. In Android implementation, the onDraw function in the Component (View) plays this role. The 
DispatchDraw function (which is called when the children are to be drawn) in the composite (ViewGroup) object actually traverses through the list of the objects and calls draw on each of the child object. For simplicity, in the class diagram it is shown that the dispatchDraw function is directly calling the onDraw function. However it actually takes help of another function calleddrawChild which is called on each child object of the ViewGroup object.

This way we can say that Android View and Widgets are some sort of implementation of the Composite Design Pattern.
Trackback 5 and Comment 1

마지막 Q&A시간에 View를 offscreen에 그려서 texture로 쓰려면 어떻게 하는가에 대한 질문이 나오는데 getDrawingCache()를 쓰라고 한다..
방법이 그것 밖에 없는 것인가?
Trackback 0 and Comment 0
getDrawingCache가 어떻게 그림을 복사하는지 내부 mechanism을 살펴보자

우선 사용예제는
 webView.setDrawingCacheEnabled(true);
 webView.setDrawingCacheBackgroundColor(Color.WHITE);
 webView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_AUTO);
 webView.getDrawingCache(true)
결론은 c단에서 skia bitmap이 실제로 그려지는 device의 actual pixel memory에 바로 붙어서 그 시점의 memory를 카피하는 것이라 생각된다. 확신을 못하는 이유는 skia쪽 분석을 하다 막혔기 때문이다. 추후에 더 완벽한 분석을 하여 보강하겠다.


frameworks/base/core/java/android/view/View.java 를 보면 getDrawingCache() 가 buildDrawingCache를 콜하면서 일을 한다. buildDrawingCache는 자기가 전에 저장해 놓은 bitmap이 갱신이 않되었으면 그것을 쓰고 아니면 다음을 수행한다.
bitmap = Bitmap.createBitmap(width, height, quality);
이 bitmap을 reference에 저장하여(?) getDrawingCache()로 불러쓴다.
mDrawingCache = new SoftReference<Bitmap>(bitmap);


진짜 일을 하는 놈이 Bitmap.createBitmap 인걸로 밝혀졌으니 그놈은 무엇을 하는지 알아보자
frameworks/base/graphics/java/android/graphics/Bitmap.java 를 살펴보면
public static Bitmap createBitmap(int width, int height, Config config) {
    Bitmap bm = nativeCreate(null, 0, width, width, height, config.nativeInt, true);
    bm.eraseColor(0);    // start with black/transparent pixels
    return bm;
}
jni로 c 함수를 콜하는것을 알수 있다.
frameworks/base/core/jni/android/graphics/Bitmap.cpp를 살펴보자.
 static JNINativeMethod gBitmapMethods[] = {^M
     {   "nativeCreate",             "([IIIIIIZ)Landroid/graphics/Bitmap;",^M
         (void*)Bitmap_creator },^M
nativeCreate의 구현은 Bitmap_creator이다.
이런 안쪽 구현은 Skia로 해놨군.. 잘 모르니 이제부터 설명은 틀릴가능성이 농후하다....
 static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,^M
                               int offset, int stride, int width, int height,^M
                               SkBitmap::Config config, jboolean isMutable) {^M
//위에서 jColors는 null, offset은 0으로 넘어왔다는것을 기억하자
     if (width <= 0 || height <= 0) {^M
         doThrowIAE(env, "width and height must be > 0");^M
         return NULL;^M
     }^M
 ^M
     if (NULL != jColors) {^M
         size_t n = env->GetArrayLength(jColors);^M
         if (n < SkAbs32(stride) * (size_t)height) {^M
             doThrowAIOOBE(env);^M
             return NULL;^M
         }^M
     }^M
 ^M
     SkBitmap bitmap;^M
 ^M
     bitmap.setConfig(config, width, height);^M
//가장 중요한 부분. bitmap이 actual pixel memory에 access할 수 있게 셋팅하는 부분이다. 부차적으로 bitmap에 메모리 alloc하고 pixel환경(?)을 잡아주는것 같다. VM이 그 메모리를 관리(?)할수 있도록 셋팅해주는것 같은데... 
     if (!GraphicsJNI::setJavaPixelRef(env, &bitmap, NULL, true)) {^M
         return NULL;^M
     }^M
 ^M
//pixel color(?)를 잡아주는것 같은데 우리는  null이 넘어왔으므로 pass 
     if (jColors != NULL) {^M
         GraphicsJNI::SetPixels(env, jColors, offset, stride,^M
                                0, 0, width, height, bitmap);^M
     }^M
 ^M
//결국 return에서 일을 하는 것이군.. SkBitmap는 메모리만 다시 잡아주는듯...
     return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), isMutable,^M
                                      NULL);^M
 }^M
이번글에서 가장 중요한 GraphicsJNI::setJavaPixelRef를 보자
frameworks/base/core/jni/android/graphics/Graphics.cpp
augments가 ctable=NULL, reportSizeToVM = true로 넘어왔음을 기억하자
bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap,
                                  SkColorTable* ctable, bool reportSizeToVM) {
    Sk64 size64 = bitmap->getSize64();
    if (size64.isNeg() || !size64.is32()) {
        doThrow(env, "java/lang/IllegalArgumentException",
                     "bitmap size exceeds 32bits");
        return false;
    }

    size_t size = size64.get32();
    jlong jsize = size;  // the VM wants longs for the size
    if (reportSizeToVM) {
        //    SkDebugf("-------------- inform VM we've allocated %d bytes\n", size);
//VM에 메모리 관련 뭔가를 하는것 같은데 이번글과 긴밀하게 관련 없는것 같으니 패스
        bool r = env->CallBooleanMethod(gVMRuntime_singleton,
                                    gVMRuntime_trackExternalAllocationMethodID,
                                    jsize);
        if (GraphicsJNI::hasException(env)) {
            return false;
        }
        if (!r) {
            LOGE("VM won't let us allocate %zd bytes\n", size);
            doThrowOOME(env, "bitmap size exceeds VM budget");
            return false;
        }
    }
    // call the version of malloc that returns null on failure
//actual pixel memory에서 bitmap을 복사할 storage 공간을 잡는다.    
void* addr = sk_malloc_flags(size, 0);
    if (NULL == addr) {
        if (reportSizeToVM) {
            //        SkDebugf("-------------- inform VM we're releasing %d bytes which we couldn't allocate\n", size);
            // we didn't actually allocate it, so inform the VM
            env->CallVoidMethod(gVMRuntime_singleton,
                                 gVMRuntime_trackExternalFreeMethodID,
                                 jsize);
            if (!GraphicsJNI::hasException(env)) {
                doThrowOOME(env, "bitmap size too large for malloc");
            }
        }
        return false;
    }
//SkMallocPixel로 이미지를 가져올 storage인 addr을 감싼다. 내부적으로 어떻게 하는지는 모르겠지만 화면에 그려질 pixel memory를 addr에 복사하는것 같다. reportSizeToVM이 true이므로 AndroidPixelRef실행. 하지만 AndroidPixelRef의 구현은 VM이 정상인지(?) 체크만 하고 다시 SKMallocPixelRef를 콜한다. SkMallocPixelRef:SkPixelRef 관계이고 void* fStorage를 추가적으로 관리한다. 아무래도 내부적으로 어떻게인지는 모르겠지만 화면에 그려질 pixel memory를 fStorage에 카피하는것 같다.
    SkPixelRef* pr = reportSizeToVM ?
                        new AndroidPixelRef(env, addr, size, ctable) :
                        new SkMallocPixelRef(addr, size, ctable);
//SkPixelRef 을 최종적으로 그림을 담을 bitmap의 fPixelRef에 pr을 설정하고 fPixels에는 pr.pixels()를 통해 PixelRef의 void* fPixels를 지정한다.
    bitmap->setPixelRef(pr)->unref();
    // since we're already allocated, we lockPixels right away
    // HeapAllocator behaves this way too
//하일라이트다. actual pixel memory를 카피한다(어떻게인지는 모르겠음). SkBitmap.lockPixel() -> SkPixelRef.lockPixel() -> SkPixelRef.onLockPixels -> SkMallocPixelRef.onLockPixels 를 연쇄적으로 콜한다. SkMallocPixelRef.fStorage가 SkPixelRef.fPixels에 카피되고 그것이 다시 SkBitmap.fPixels에 카피된다.
  bitmap->lockPixels();
     return true;
 }
결국 그림을 복사하는것은 SkPixelRef가 담당한다. SkPixelRef이 이 포스트에 결론이다. 근데 mechanism을 잘 모르겠다. http://skia.googlecode.com/svn/trunk/docs/html/index.html 를 보면 

This class is the smart container for pixel memory, and is used with SkBitmap. A pixelref is installed into a bitmap, and then the bitmap can access the actual pixel memory by calling lockPixels/unlockPixels.

This class can be shared/accessed between multiple threads.

라고 설명을 한다. 즉 lockPixels이 그림을 복사해 준다는 것인데..

external/skia/src/core/SkBitmap.cpp 를 보면 과연 SkMallocPixelRef는 addr을 감싸기만 한다는것 을 알 수 있다.
SkMallocPixelRef::SkMallocPixelRef(void* storage, size_t size,
                                   SkColorTable* ctable) {
    SkASSERT(storage);
    fStorage = storage;
    fSize = size;
    fCTable = ctable;
    ctable->safeRef();
}      
가장 중요한 lockPixels를 분석하자
external/skia/src/core/SkBitmap.cpp
void SkBitmap::lockPixels() const {
    if (NULL != fPixelRef && 1 == ++fPixelLockCount) {
//SkPixelRef의 lockPixel을 콜한다.
        fPixelRef->lockPixels();
        this->updatePixelsFromRef();
    }
    SkDEBUGCODE(this->validate();)
}  

SkPixelRef의 lockPixels을 다시 부른다.
void SkPixelRef::lockPixels() { 
    SkAutoMutexAcquire  ac(*fMutex);
         
    if (1 == ++fLockCount) {
        fPixels = this->onLockPixels(&fColorTable);
    }
}   
SkPixelRef의 onLockPixels는 virtual이므로 자식인 SkMallocPixelRef의 onLockPixels를 보면
void* SkMallocPixelRef::onLockPixels(SkColorTable** ct) {
    *ct = fCTable;
    return fStorage;
}  
단순히 fStorage를 반환한다... 이럴수가 SkMallocPixelRef, SkBitmap, SkPixelRef사이에서 변수를 주고받기만 하다 끝나버렸다.  어디서 화면에 그려질 pixel memory를 가져오는지 알수가 없다. 다만 최종 소스는 fStorage임을 알 수 있을 뿐이다...


그러므로 frameworks/base/core/jni/android/graphics/Graphics.cpp 로 고고씽
변수개수가 약간 다른데 왜 그런지는 모르겠고,  결과인 bitmap을 jni를 통해 다시 Java로 돌려준다.

gBitmap_class는 android/graphics/Bitmap Class이고 gBitmap_constructorMethodID 는 생성자이다.
jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density)
{
    SkASSERT(bitmap != NULL);
    SkASSERT(NULL != bitmap->pixelRef());

    jobject obj = env->AllocObject(gBitmap_class);
    if (obj) {
        env->CallVoidMethod(obj, gBitmap_constructorMethodID,
                            (jint)bitmap, isMutable, ninepatch, density);
        if (hasException(env)) {
            obj = NULL;
        }
    }
    return obj;
}
한마디로 Bitmap(new SkBitmap(bitmap), isMutable=true, null, null); 을 콜한것인데... Java로 고고씽
 private Bitmap(int nativeBitmap, boolean isMutable, byte[] ninePatchChunk,
         int density) {
     if (nativeBitmap == 0) {
         throw new RuntimeException("internal error: native bitmap is 0");
     }
     // we delete this in our finalizer
     mNativeBitmap = nativeBitmap;
     mIsMutable = isMutable;
     mNinePatchChunk = ninePatchChunk;
     if (density >= 0) {
         mDensity = density;
     }
 }
c에서 jni를 통해 넘어온 bitmap을 wrapping해준다.




Trackback 0 and Comment 2