| /* | 
 |  * Copyright 2015 Google Inc. | 
 |  * | 
 |  * Use of this source code is governed by a BSD-style license that can be | 
 |  * found in the LICENSE file. | 
 |  */ | 
 |  | 
 | #import "SkSampleUIView.h" | 
 |  | 
 | //#define SKGL_CONFIG         kEAGLColorFormatRGB565 | 
 | #define SKGL_CONFIG         kEAGLColorFormatRGBA8 | 
 |  | 
 | #define FORCE_REDRAW | 
 |  | 
 | #include "SkCanvas.h" | 
 | #include "SkCGUtils.h" | 
 | #include "SkSurface.h" | 
 | #include "SampleApp.h" | 
 |  | 
 | #if SK_SUPPORT_GPU | 
 | //#define USE_GL_1 | 
 | #define USE_GL_2 | 
 |  | 
 | #include "gl/GrGLInterface.h" | 
 | #include "GrContext.h" | 
 | #include "SkGpuDevice.h" | 
 | #endif | 
 |  | 
 | class SkiOSDeviceManager : public SampleWindow::DeviceManager { | 
 | public: | 
 |     SkiOSDeviceManager(GLint layerFBO) { | 
 | #if SK_SUPPORT_GPU | 
 |         fCurContext = NULL; | 
 |         fCurIntf = NULL; | 
 |         fCurRenderTarget = NULL; | 
 |         fMSAASampleCount = 0; | 
 |         fLayerFBO = layerFBO; | 
 | #endif | 
 |         fBackend = SkOSWindow::kNone_BackEndType; | 
 |     } | 
 |      | 
 |     virtual ~SkiOSDeviceManager() { | 
 | #if SK_SUPPORT_GPU | 
 |         SkSafeUnref(fCurContext); | 
 |         SkSafeUnref(fCurIntf); | 
 |         SkSafeUnref(fCurRenderTarget); | 
 | #endif | 
 |     } | 
 |      | 
 |     void setUpBackend(SampleWindow* win, int msaaSampleCount, bool deepColor) override { | 
 |         SkASSERT(SkOSWindow::kNone_BackEndType == fBackend); | 
 |          | 
 |         fBackend = SkOSWindow::kNone_BackEndType; | 
 |          | 
 | #if SK_SUPPORT_GPU | 
 |         switch (win->getDeviceType()) { | 
 |             case SampleWindow::kRaster_DeviceType: | 
 |                 break; | 
 |             // these guys use the native backend | 
 |             case SampleWindow::kGPU_DeviceType: | 
 |                 fBackend = SkOSWindow::kNativeGL_BackEndType; | 
 |                 break; | 
 |             default: | 
 |                 SkASSERT(false); | 
 |                 break; | 
 |         } | 
 |         SkOSWindow::AttachmentInfo info; | 
 |         bool result = win->attach(fBackend, msaaSampleCount, false, &info); | 
 |         if (!result) { | 
 |             SkDebugf("Failed to initialize GL"); | 
 |             return; | 
 |         } | 
 |         fMSAASampleCount = msaaSampleCount; | 
 |          | 
 |         SkASSERT(NULL == fCurIntf); | 
 |         switch (win->getDeviceType()) { | 
 |             case SampleWindow::kRaster_DeviceType: | 
 |                 fCurIntf = NULL; | 
 |                 break; | 
 |             case SampleWindow::kGPU_DeviceType: | 
 |                 fCurIntf = GrGLCreateNativeInterface(); | 
 |                 break; | 
 |             default: | 
 |                 SkASSERT(false); | 
 |                 break; | 
 |         } | 
 |          | 
 |         SkASSERT(NULL == fCurContext); | 
 |         if (SkOSWindow::kNone_BackEndType != fBackend) { | 
 |             fCurContext = GrContext::Create(kOpenGL_GrBackend, | 
 |                                             (GrBackendContext) fCurIntf); | 
 |         } | 
 |          | 
 |         if ((NULL == fCurContext || NULL == fCurIntf) && | 
 |             SkOSWindow::kNone_BackEndType != fBackend) { | 
 |             // We need some context and interface to see results if we're using a GL backend | 
 |             SkSafeUnref(fCurContext); | 
 |             SkSafeUnref(fCurIntf); | 
 |             SkDebugf("Failed to setup 3D"); | 
 |             win->release(); | 
 |         } | 
 | #endif // SK_SUPPORT_GPU | 
 |         // call windowSizeChanged to create the render target | 
 |         this->windowSizeChanged(win); | 
 |     } | 
 |      | 
 |     void tearDownBackend(SampleWindow *win) override { | 
 | #if SK_SUPPORT_GPU | 
 |         SkSafeUnref(fCurContext); | 
 |         fCurContext = NULL; | 
 |          | 
 |         SkSafeUnref(fCurIntf); | 
 |         fCurIntf = NULL; | 
 |          | 
 |         SkSafeUnref(fCurRenderTarget); | 
 |         fCurRenderTarget = NULL; | 
 | #endif | 
 |         win->release(); | 
 |         fBackend = SampleWindow::kNone_BackEndType; | 
 |     } | 
 |  | 
 |     SkSurface* createSurface(SampleWindow::DeviceType dType, SampleWindow* win) override{ | 
 | #if SK_SUPPORT_GPU | 
 |         if (SampleWindow::IsGpuDeviceType(dType) && fCurContext) { | 
 |             SkSurfaceProps props(win->getSurfaceProps()); | 
 |             return SkSurface::MakeRenderTargetDirect(fCurRenderTarget, | 
 |                                                      sk_ref_sp(win->info().colorSpace()), | 
 |                                                      &props).release(); | 
 |         } | 
 | #endif | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     virtual void publishCanvas(SampleWindow::DeviceType dType, | 
 |                                SkCanvas* canvas, | 
 |                                SampleWindow* win) override { | 
 | #if SK_SUPPORT_GPU | 
 |         if (NULL != fCurContext) { | 
 |             fCurContext->flush(); | 
 |         } | 
 | #endif | 
 |         win->present(); | 
 |     } | 
 |      | 
 |     void windowSizeChanged(SampleWindow* win) override { | 
 | #if SK_SUPPORT_GPU | 
 |         if (NULL != fCurContext) { | 
 |             SkOSWindow::AttachmentInfo info; | 
 |  | 
 |             win->attach(fBackend, fMSAASampleCount, false, &info); | 
 |              | 
 |             glBindFramebuffer(GL_FRAMEBUFFER, fLayerFBO); | 
 |             GrBackendRenderTargetDesc desc; | 
 |             desc.fWidth = SkScalarRoundToInt(win->width()); | 
 |             desc.fHeight = SkScalarRoundToInt(win->height()); | 
 |             desc.fConfig = kSkia8888_GrPixelConfig; | 
 |             desc.fRenderTargetHandle = fLayerFBO; | 
 |             desc.fSampleCnt = info.fSampleCount; | 
 |             desc.fStencilBits = info.fStencilBits; | 
 |  | 
 |             SkSafeUnref(fCurRenderTarget); | 
 |             fCurRenderTarget = fCurContext->textureProvider()->wrapBackendRenderTarget(desc); | 
 |         } | 
 | #endif | 
 |     } | 
 |      | 
 |     GrContext* getGrContext() override { | 
 | #if SK_SUPPORT_GPU | 
 |         return fCurContext; | 
 | #else | 
 |         return NULL; | 
 | #endif | 
 |     } | 
 |      | 
 |     GrRenderTarget* getGrRenderTarget() override { | 
 | #if SK_SUPPORT_GPU | 
 |         return fCurRenderTarget; | 
 | #else | 
 |         return NULL; | 
 | #endif | 
 |     } | 
 |  | 
 |     int getColorBits() override { | 
 |         return 24; | 
 |     } | 
 |  | 
 |     bool isUsingGL() const { return SkOSWindow::kNone_BackEndType != fBackend; } | 
 |      | 
 | private: | 
 |     | 
 | #if SK_SUPPORT_GPU | 
 |     GrContext*              fCurContext; | 
 |     const GrGLInterface*    fCurIntf; | 
 |     GrRenderTarget*         fCurRenderTarget; | 
 |     int                     fMSAASampleCount; | 
 |     GLint                   fLayerFBO; | 
 | #endif | 
 |      | 
 |     SkOSWindow::SkBackEndTypes fBackend; | 
 |      | 
 |     typedef SampleWindow::DeviceManager INHERITED; | 
 | }; | 
 |  | 
 | //////////////////////////////////////////////////////////////////////////////// | 
 | @implementation SkSampleUIView | 
 |  | 
 | @synthesize fTitle, fRasterLayer, fGLLayer; | 
 |  | 
 | #include "SkApplication.h" | 
 | #include "SkEvent.h" | 
 | #include "SkWindow.h" | 
 |  | 
 | struct FPSState { | 
 |     static const int FRAME_COUNT = 60; | 
 |      | 
 |     CFTimeInterval fNow0, fNow1; | 
 |     CFTimeInterval fTime0, fTime1, fTotalTime; | 
 |     int fFrameCounter; | 
 |     SkString str; | 
 |     FPSState() { | 
 |         fTime0 = fTime1 = fTotalTime = 0; | 
 |         fFrameCounter = 0; | 
 |     } | 
 |      | 
 |     void startDraw() { | 
 |         fNow0 = CACurrentMediaTime(); | 
 |     } | 
 |      | 
 |     void endDraw() { | 
 |         fNow1 = CACurrentMediaTime(); | 
 |     } | 
 |      | 
 |     void flush(SkOSWindow* hwnd) { | 
 |         CFTimeInterval now2 = CACurrentMediaTime(); | 
 |          | 
 |         fTime0 += fNow1 - fNow0; | 
 |         fTime1 += now2 - fNow1; | 
 |          | 
 |         if (++fFrameCounter == FRAME_COUNT) { | 
 |             CFTimeInterval totalNow = CACurrentMediaTime(); | 
 |             fTotalTime = totalNow - fTotalTime; | 
 |              | 
 |             //SkMSec ms0 = (int)(1000 * fTime0 / FRAME_COUNT); | 
 |             //SkMSec msTotal = (int)(1000 * fTotalTime / FRAME_COUNT); | 
 |             //str.printf(" ms: %d [%d], fps: %3.1f", msTotal, ms0, | 
 |             //           FRAME_COUNT / fTotalTime); | 
 |             str.printf(" fps:%3.1f", FRAME_COUNT / fTotalTime); | 
 |             hwnd->setTitle(NULL); | 
 |             fTotalTime = totalNow; | 
 |             fTime0 = fTime1 = 0; | 
 |             fFrameCounter = 0; | 
 |         } | 
 |     } | 
 | }; | 
 |  | 
 | static FPSState gFPS; | 
 |  | 
 | #define FPS_StartDraw() gFPS.startDraw() | 
 | #define FPS_EndDraw()   gFPS.endDraw() | 
 | #define FPS_Flush(wind) gFPS.flush(wind) | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | - (id)initWithDefaults { | 
 |     if (self = [super initWithDefaults]) { | 
 |         fRedrawRequestPending = false; | 
 |         fFPSState = new FPSState; | 
 |          | 
 | #ifdef USE_GL_1 | 
 |         fGL.fContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; | 
 | #else | 
 |         fGL.fContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; | 
 | #endif | 
 |          | 
 |         if (!fGL.fContext || ![EAGLContext setCurrentContext:fGL.fContext]) | 
 |         { | 
 |             [self release]; | 
 |             return nil; | 
 |         } | 
 |          | 
 |         // Create default framebuffer object. The backing will be allocated for the current layer in -resizeFromLayer | 
 |         glGenFramebuffers(1, &fGL.fFramebuffer); | 
 |         glBindFramebuffer(GL_FRAMEBUFFER, fGL.fFramebuffer); | 
 |          | 
 |         glGenRenderbuffers(1, &fGL.fRenderbuffer); | 
 |         glGenRenderbuffers(1, &fGL.fStencilbuffer); | 
 |          | 
 |         glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer); | 
 |         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fGL.fRenderbuffer); | 
 |          | 
 |         glBindRenderbuffer(GL_RENDERBUFFER, fGL.fStencilbuffer); | 
 |         glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fGL.fStencilbuffer); | 
 |          | 
 |         self.fGLLayer = [CAEAGLLayer layer]; | 
 |         fGLLayer.bounds = self.bounds; | 
 |         fGLLayer.anchorPoint = CGPointMake(0, 0); | 
 |         fGLLayer.opaque = TRUE; | 
 |         [self.layer addSublayer:fGLLayer]; | 
 |         fGLLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: | 
 |                                        [NSNumber numberWithBool:NO], | 
 |                                        kEAGLDrawablePropertyRetainedBacking, | 
 |                                        SKGL_CONFIG, | 
 |                                        kEAGLDrawablePropertyColorFormat, | 
 |                                        nil]; | 
 |          | 
 |         self.fRasterLayer = [CALayer layer]; | 
 |         fRasterLayer.anchorPoint = CGPointMake(0, 0); | 
 |         fRasterLayer.opaque = TRUE; | 
 |         [self.layer addSublayer:fRasterLayer]; | 
 |          | 
 |         NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"onOrderIn", | 
 |                                            [NSNull null], @"onOrderOut", | 
 |                                            [NSNull null], @"sublayers", | 
 |                                            [NSNull null], @"contents", | 
 |                                            [NSNull null], @"bounds", | 
 |                                            nil]; | 
 |         fGLLayer.actions = newActions; | 
 |         fRasterLayer.actions = newActions; | 
 |         [newActions release]; | 
 |          | 
 |         // rebuild argc and argv from process info | 
 |         NSArray* arguments = [[NSProcessInfo processInfo] arguments]; | 
 |         int argc = [arguments count]; | 
 |         char** argv = new char*[argc]; | 
 |         for (int i = 0; i < argc; ++i) { | 
 |             NSString* arg = [arguments objectAtIndex:i]; | 
 |             int strlen = [arg lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; | 
 |             argv[i] = new char[strlen+1]; | 
 |             [arg getCString:argv[i] maxLength:strlen+1 encoding:NSUTF8StringEncoding]; | 
 |         } | 
 |          | 
 |         fDevManager = new SkiOSDeviceManager(fGL.fFramebuffer); | 
 |         fWind = new SampleWindow(self, argc, argv, fDevManager); | 
 |  | 
 |         fWind->resize(self.frame.size.width, self.frame.size.height); | 
 |          | 
 |         for (int i = 0; i < argc; ++i) { | 
 |             delete [] argv[i]; | 
 |         } | 
 |         delete [] argv; | 
 |     } | 
 |     return self; | 
 | } | 
 |  | 
 | - (void)dealloc { | 
 |     delete fDevManager; | 
 |     delete fFPSState; | 
 |     self.fRasterLayer = nil; | 
 |     self.fGLLayer = nil; | 
 |     [fGL.fContext release]; | 
 |     [super dealloc]; | 
 | } | 
 |  | 
 | - (void)layoutSubviews { | 
 |     int W, H; | 
 |      | 
 |     // Allocate color buffer backing based on the current layer size | 
 |     glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer); | 
 |     [fGL.fContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:fGLLayer]; | 
 |      | 
 |     glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &fGL.fWidth); | 
 |     glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &fGL.fHeight); | 
 |      | 
 |     glBindRenderbuffer(GL_RENDERBUFFER, fGL.fStencilbuffer); | 
 |     glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, fGL.fWidth, fGL.fHeight); | 
 |      | 
 |     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | 
 |         NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER)); | 
 |     } | 
 |      | 
 |     if (fDevManager->isUsingGL()) { | 
 |         W = fGL.fWidth; | 
 |         H = fGL.fHeight; | 
 |         CGRect rect = CGRectMake(0, 0, W, H); | 
 |         fGLLayer.bounds = rect; | 
 |     } | 
 |     else { | 
 |         CGRect rect = self.bounds; | 
 |         W = (int)CGRectGetWidth(rect); | 
 |         H = (int)CGRectGetHeight(rect); | 
 |         fRasterLayer.bounds = rect; | 
 |     } | 
 |      | 
 |     printf("---- layoutSubviews %d %d\n", W, H); | 
 |     fWind->resize(W, H); | 
 |     fWind->inval(NULL); | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | - (void)drawWithCanvas:(SkCanvas*)canvas { | 
 |     fRedrawRequestPending = false; | 
 |     fFPSState->startDraw(); | 
 |     fWind->draw(canvas); | 
 |     fFPSState->endDraw(); | 
 | #ifdef FORCE_REDRAW | 
 |     fWind->inval(NULL); | 
 | #endif | 
 |     fFPSState->flush(fWind); | 
 | } | 
 |  | 
 | - (void)drawInGL { | 
 |     // This application only creates a single context which is already set current at this point. | 
 |     // This call is redundant, but needed if dealing with multiple contexts. | 
 |     [EAGLContext setCurrentContext:fGL.fContext]; | 
 |      | 
 |     // This application only creates a single default framebuffer which is already bound at this point. | 
 |     // This call is redundant, but needed if dealing with multiple framebuffers. | 
 |     glBindFramebuffer(GL_FRAMEBUFFER, fGL.fFramebuffer); | 
 |      | 
 |     GLint scissorEnable; | 
 |     glGetIntegerv(GL_SCISSOR_TEST, &scissorEnable); | 
 |     glDisable(GL_SCISSOR_TEST); | 
 |     glClearColor(0,0,0,0); | 
 |     glClear(GL_COLOR_BUFFER_BIT); | 
 |     if (scissorEnable) { | 
 |         glEnable(GL_SCISSOR_TEST); | 
 |     } | 
 |     glViewport(0, 0, fGL.fWidth, fGL.fHeight); | 
 |      | 
 |     | 
 |     SkAutoTUnref<SkSurface> surface(fWind->createSurface()); | 
 |     SkCanvas* canvas = surface->getCanvas(); | 
 |  | 
 |     // if we're not "retained", then we have to always redraw everything. | 
 |     // This call forces us to ignore the fDirtyRgn, and draw everywhere. | 
 |     // If we are "retained", we can skip this call (as the raster case does) | 
 |     fWind->forceInvalAll(); | 
 |  | 
 |     [self drawWithCanvas:canvas]; | 
 |  | 
 |     // This application only creates a single color renderbuffer which is already bound at this point. | 
 |     // This call is redundant, but needed if dealing with multiple renderbuffers. | 
 |     glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer); | 
 |     [fGL.fContext presentRenderbuffer:GL_RENDERBUFFER]; | 
 | } | 
 |  | 
 | - (void)drawInRaster { | 
 |     SkAutoTUnref<SkSurface> surface(fWind->createSurface()); | 
 |     SkCanvas* canvas = surface->getCanvas(); | 
 |     [self drawWithCanvas:canvas]; | 
 |     CGImageRef cgimage = SkCreateCGImageRef(fWind->getBitmap()); | 
 |     fRasterLayer.contents = (id)cgimage; | 
 |     CGImageRelease(cgimage); | 
 | } | 
 |  | 
 | - (void)forceRedraw { | 
 |     if (fDevManager->isUsingGL()) | 
 |         [self drawInGL]; | 
 |     else  | 
 |         [self drawInRaster]; | 
 | } | 
 |  | 
 | /////////////////////////////////////////////////////////////////////////////// | 
 |  | 
 | - (void)setSkTitle:(const char *)title { | 
 |     NSString* text = [NSString stringWithUTF8String:title]; | 
 |     if ([text length] > 0) | 
 |         self.fTitle = text; | 
 |      | 
 |     if (fTitleItem && fTitle) { | 
 |         fTitleItem.title = [NSString stringWithFormat:@"%@%@", fTitle,  | 
 |                             [NSString stringWithUTF8String:fFPSState->str.c_str()]]; | 
 |     } | 
 | } | 
 |  | 
 | - (void)postInvalWithRect:(const SkIRect*)r { | 
 |     if (!fRedrawRequestPending) { | 
 |         fRedrawRequestPending = true; | 
 |         bool gl = fDevManager->isUsingGL(); | 
 |         [CATransaction begin]; | 
 |         [CATransaction setAnimationDuration:0]; | 
 |         fRasterLayer.hidden = gl; | 
 |         fGLLayer.hidden = !gl; | 
 |         [CATransaction commit]; | 
 |         if (gl) { | 
 |             [self performSelector:@selector(drawInGL) withObject:nil afterDelay:0]; | 
 |         } | 
 |         else { | 
 |             [self performSelector:@selector(drawInRaster) withObject:nil afterDelay:0]; | 
 |             [self setNeedsDisplay]; | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | - (void)getAttachmentInfo:(SkOSWindow::AttachmentInfo*)info { | 
 |     glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer); | 
 |     glGetRenderbufferParameteriv(GL_RENDERBUFFER, | 
 |                                  GL_RENDERBUFFER_STENCIL_SIZE, | 
 |                                  &info->fStencilBits); | 
 |     glGetRenderbufferParameteriv(GL_RENDERBUFFER, | 
 |                                  GL_RENDERBUFFER_SAMPLES_APPLE, | 
 |                                  &info->fSampleCount); | 
 | } | 
 |  | 
 | @end |