1// SHE library 2// Copyright (C) 2012-2018 David Capello 3// 4// This file is released under the terms of the MIT license. 5// Read LICENSE.txt for more information. 6 7//#define DEBUG_UPDATE_RECTS 8 9#ifdef HAVE_CONFIG_H 10#include "config.h" 11#endif 12 13#include "she/skia/skia_window_osx.h" 14 15#include "base/log.h" 16#include "base/unique_ptr.h" 17#include "gfx/size.h" 18#include "she/event.h" 19#include "she/event_queue.h" 20#include "she/osx/view.h" 21#include "she/osx/window.h" 22#include "she/skia/skia_display.h" 23#include "she/skia/skia_surface.h" 24#include "she/system.h" 25 26#include "mac/SkCGUtils.h" 27 28#if SK_SUPPORT_GPU 29 30 #include "GrBackendSurface.h" 31 #include "GrContext.h" 32 #include "gl/GrGLDefines.h" 33 #include "gl/GrGLInterface.h" 34 #include "she/gl/gl_context_cgl.h" 35 #include "she/skia/skia_surface.h" 36 37#endif 38 39#include <iostream> 40 41namespace she { 42 43class SkiaWindow::Impl : public OSXWindowImpl { 44public: 45 Impl(EventQueue* queue, SkiaDisplay* display, 46 int width, int height, int scale) 47 : m_display(display) 48 , m_backend(Backend::NONE) 49#if SK_SUPPORT_GPU 50 , m_nsGL(nil) 51 , m_skSurface(nullptr) 52#endif 53 { 54 m_closing = false; 55 m_window = [[OSXWindow alloc] initWithImpl:this 56 width:width 57 height:height 58 scale:scale]; 59 } 60 61 ~Impl() { 62#if SK_SUPPORT_GPU 63 if (m_backend == Backend::GL) 64 detachGL(); 65#endif 66 } 67 68 gfx::Size clientSize() const { 69 return [m_window clientSize]; 70 } 71 72 gfx::Size restoredSize() const { 73 return [m_window restoredSize]; 74 } 75 76 int scale() const { 77 return [m_window scale]; 78 } 79 80 void setScale(int scale) { 81 [m_window setScale:scale]; 82 } 83 84 void setVisible(bool visible) { 85 if (visible) { 86 // Make the first OSXWindow as the main one. 87 [m_window makeKeyAndOrderFront:nil]; 88 89 // The main window can be changed only when the NSWindow 90 // is visible (i.e. when NSWindow::canBecomeMainWindow 91 // returns YES). 92 [m_window makeMainWindow]; 93 } 94 else { 95 [m_window close]; 96 } 97 } 98 99 void setTitle(const std::string& title) { 100 [m_window setTitle:[NSString stringWithUTF8String:title.c_str()]]; 101 } 102 103 void setMousePosition(const gfx::Point& position) { 104 [m_window setMousePosition:position]; 105 } 106 107 bool setNativeMouseCursor(NativeCursor cursor) { 108 return ([m_window setNativeMouseCursor:cursor] ? true: false); 109 } 110 111 bool setNativeMouseCursor(const she::Surface* surface, 112 const gfx::Point& focus, 113 const int scale) { 114 return ([m_window setNativeMouseCursor:surface 115 focus:focus 116 scale:scale] ? true: false); 117 } 118 119 void updateWindow(const gfx::Rect& bounds) { 120 @autoreleasepool { 121 int scale = this->scale(); 122 NSView* view = m_window.contentView; 123 [view setNeedsDisplayInRect: 124 NSMakeRect(bounds.x*scale, 125 view.frame.size.height - (bounds.y+bounds.h)*scale, 126 bounds.w*scale, 127 bounds.h*scale)]; 128 [view displayIfNeeded]; 129 } 130 } 131 132 void setTranslateDeadKeys(bool state) { 133 OSXView* view = (OSXView*)m_window.contentView; 134 [view setTranslateDeadKeys:(state ? YES: NO)]; 135 } 136 137 void* handle() { 138 return (__bridge void*)m_window; 139 } 140 141 // OSXWindowImpl impl 142 143 void onClose() override { 144 m_closing = true; 145 } 146 147 void onResize(const gfx::Size& size) override { 148 bool gpu = she::instance()->gpuAcceleration(); 149 (void)gpu; 150 151#if SK_SUPPORT_GPU 152 if (gpu && attachGL()) { 153 m_backend = Backend::GL; 154 } 155 else 156#endif 157 { 158#if SK_SUPPORT_GPU 159 detachGL(); 160#endif 161 m_backend = Backend::NONE; 162 } 163 164#if SK_SUPPORT_GPU 165 if (m_glCtx && m_display->isInitialized()) 166 createRenderTarget(size); 167#endif 168 169 m_display->resize(size); 170 } 171 172 void onDrawRect(const gfx::Rect& rect) override { 173 switch (m_backend) { 174 175 case Backend::NONE: 176 paintGC(rect); 177 break; 178 179#if SK_SUPPORT_GPU 180 case Backend::GL: 181 // TODO 182 if (m_nsGL) 183 [m_nsGL flushBuffer]; 184 break; 185#endif 186 } 187 } 188 189 void onWindowChanged() override { 190#if SK_SUPPORT_GPU 191 if (m_nsGL) 192 [m_nsGL setView:[m_window contentView]]; 193#endif 194 } 195 196private: 197#if SK_SUPPORT_GPU 198 bool attachGL() { 199 if (!m_glCtx) { 200 try { 201 base::UniquePtr<GLContext> ctx(new GLContextCGL); 202 if (!ctx->createGLContext()) 203 throw std::runtime_error("Cannot create CGL context"); 204 205 m_glInterfaces.reset(GrGLCreateNativeInterface()); 206 if (!m_glInterfaces || !m_glInterfaces->validate()) { 207 LOG(ERROR) << "OS: Cannot create GL interfaces\n"; 208 detachGL(); 209 return false; 210 } 211 212 m_glCtx.reset(ctx.get()); 213 ctx.release(); 214 215 m_grCtx.reset(GrContext::Create(kOpenGL_GrBackend, 216 (GrBackendContext)m_glInterfaces.get())); 217 218 m_nsGL = [[NSOpenGLContext alloc] 219 initWithCGLContextObj:static_cast<GLContextCGL*>(m_glCtx.get())->cglContext()]; 220 221 [m_nsGL setView:m_window.contentView]; 222 LOG("OS: Using CGL backend\n"); 223 } 224 catch (const std::exception& ex) { 225 LOG(ERROR) << "OS: Cannot create GL context: " << ex.what() << "\n"; 226 detachGL(); 227 return false; 228 } 229 } 230 return true; 231 } 232 233 void detachGL() { 234 if (m_nsGL) 235 m_nsGL = nil; 236 237 m_skSurface.reset(nullptr); 238 m_skSurfaceDirect.reset(nullptr); 239 m_grCtx.reset(nullptr); 240 m_glCtx.reset(nullptr); 241 } 242 243 void createRenderTarget(const gfx::Size& size) { 244 const int scale = this->scale(); 245 m_lastSize = size; 246 247 GrGLint buffer; 248 m_glInterfaces->fFunctions.fGetIntegerv(GR_GL_FRAMEBUFFER_BINDING, &buffer); 249 GrGLFramebufferInfo info; 250 info.fFBOID = (GrGLuint)buffer; 251 252 GrBackendRenderTarget 253 target(size.w, size.h, 254 m_glCtx->getSampleCount(), 255 m_glCtx->getStencilBits(), 256 kSkia8888_GrPixelConfig, 257 info); 258 259 SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType); 260 261 m_skSurface.reset(nullptr); // set m_skSurface comparing with the old m_skSurfaceDirect 262 m_skSurfaceDirect = SkSurface::MakeFromBackendRenderTarget( 263 m_grCtx.get(), target, 264 kBottomLeft_GrSurfaceOrigin, 265 nullptr, &props); 266 267 if (scale == 1 && m_skSurfaceDirect) { 268 LOG("OS: Using GL direct surface\n"); 269 m_skSurface = m_skSurfaceDirect; 270 } 271 else { 272 LOG("OS: Using double buffering\n"); 273 m_skSurface = 274 SkSurface::MakeRenderTarget( 275 m_grCtx.get(), 276 SkBudgeted::kYes, 277 SkImageInfo::Make(MAX(1, size.w / scale), 278 MAX(1, size.h / scale), 279 kN32_SkColorType, kOpaque_SkAlphaType); 280 m_glCtx->getSampleCount(), 281 nullptr); 282 } 283 284 if (!m_skSurface) 285 throw std::runtime_error("Error creating surface for main display"); 286 287 m_display->setSkiaSurface(new SkiaSurface(m_skSurface)); 288 289 if (m_nsGL) 290 [m_nsGL update]; 291 } 292 293#endif 294 295 void paintGC(const gfx::Rect& rect) { 296 if (!m_display->isInitialized()) 297 return; 298 299 if (rect.isEmpty()) 300 return; 301 302 NSRect viewBounds = m_window.contentView.bounds; 303 int scale = this->scale(); 304 305 SkiaSurface* surface = static_cast<SkiaSurface*>(m_display->getSurface()); 306 const SkBitmap& origBitmap = surface->bitmap(); 307 308 SkBitmap bitmap; 309 if (scale == 1) { 310 // Create a subset to draw on the view 311 if (!origBitmap.extractSubset( 312 &bitmap, SkIRect::MakeXYWH(rect.x, 313 (viewBounds.size.height-(rect.y+rect.h)), 314 rect.w, 315 rect.h))) 316 return; 317 } 318 else { 319 // Create a bitmap to draw the original one scaled. This is 320 // faster than doing the scaling directly in 321 // CGContextDrawImage(). This avoid a slow path where the 322 // internal macOS argb32_image_mark_RGB32() function is called 323 // (which is a performance hit). 324 if (!bitmap.tryAllocN32Pixels(rect.w, rect.h, true)) 325 return; 326 327 SkCanvas canvas(bitmap); 328 canvas.drawBitmapRect(origBitmap, 329 SkIRect::MakeXYWH(rect.x/scale, 330 (viewBounds.size.height-(rect.y+rect.h))/scale, 331 rect.w/scale, 332 rect.h/scale), 333 SkRect::MakeXYWH(0, 0, rect.w, rect.h), 334 nullptr); 335 } 336 337 @autoreleasepool { 338 NSGraphicsContext* gc = [NSGraphicsContext currentContext]; 339 CGContextRef cg = (CGContextRef)[gc graphicsPort]; 340 CGColorSpaceRef colorSpace = CGDisplayCopyColorSpace(CGMainDisplayID()); 341 CGImageRef img = SkCreateCGImageRefWithColorspace(bitmap, colorSpace); 342 if (img) { 343 CGRect r = CGRectMake(viewBounds.origin.x+rect.x, 344 viewBounds.origin.y+rect.y, 345 rect.w, rect.h); 346 347 CGContextSaveGState(cg); 348 CGContextSetInterpolationQuality(cg, kCGInterpolationNone); 349 CGContextDrawImage(cg, r, img); 350#ifdef DEBUG_UPDATE_RECTS 351 { 352 static int i = 0; 353 i = (i+1) % 8; 354 CGContextSetRGBStrokeColor(cg, 355 (i & 1 ? 1.0f: 0.0f), 356 (i & 2 ? 1.0f: 0.0f), 357 (i & 4 ? 1.0f: 0.0f), 1.0f); 358 CGContextStrokeRectWithWidth(cg, r, 2.0f); 359 } 360#endif 361 CGContextRestoreGState(cg); 362 CGImageRelease(img); 363 } 364 CGColorSpaceRelease(colorSpace); 365 } 366 } 367 368 SkiaDisplay* m_display; 369 Backend m_backend; 370 bool m_closing; 371 OSXWindow* m_window; 372#if SK_SUPPORT_GPU 373 base::UniquePtr<GLContext> m_glCtx; 374 sk_sp<const GrGLInterface> m_glInterfaces; 375 NSOpenGLContext* m_nsGL; 376 sk_sp<GrContext> m_grCtx; 377 sk_sp<SkSurface> m_skSurfaceDirect; 378 sk_sp<SkSurface> m_skSurface; 379 gfx::Size m_lastSize; 380#endif 381}; 382 383SkiaWindow::SkiaWindow(EventQueue* queue, SkiaDisplay* display, 384 int width, int height, int scale) 385 : m_impl(new Impl(queue, display, 386 width, height, scale)) 387{ 388} 389 390SkiaWindow::~SkiaWindow() 391{ 392 destroyImpl(); 393} 394 395void SkiaWindow::destroyImpl() 396{ 397 delete m_impl; 398 m_impl = nullptr; 399} 400 401int SkiaWindow::scale() const 402{ 403 if (m_impl) 404 return m_impl->scale(); 405 else 406 return 1; 407} 408 409void SkiaWindow::setScale(int scale) 410{ 411 if (m_impl) 412 m_impl->setScale(scale); 413} 414 415void SkiaWindow::setVisible(bool visible) 416{ 417 if (!m_impl) 418 return; 419 420 m_impl->setVisible(visible); 421} 422 423void SkiaWindow::maximize() 424{ 425} 426 427bool SkiaWindow::isMaximized() const 428{ 429 return false; 430} 431 432bool SkiaWindow::isMinimized() const 433{ 434 return false; 435} 436 437gfx::Size SkiaWindow::clientSize() const 438{ 439 if (!m_impl) 440 return gfx::Size(0, 0); 441 442 return m_impl->clientSize(); 443} 444 445gfx::Size SkiaWindow::restoredSize() const 446{ 447 if (!m_impl) 448 return gfx::Size(0, 0); 449 450 return m_impl->restoredSize(); 451} 452 453void SkiaWindow::setTitle(const std::string& title) 454{ 455 if (!m_impl) 456 return; 457 458 m_impl->setTitle(title); 459} 460 461void SkiaWindow::captureMouse() 462{ 463} 464 465void SkiaWindow::releaseMouse() 466{ 467} 468 469void SkiaWindow::setMousePosition(const gfx::Point& position) 470{ 471 if (m_impl) 472 m_impl->setMousePosition(position); 473} 474 475bool SkiaWindow::setNativeMouseCursor(NativeCursor cursor) 476{ 477 if (m_impl) 478 return m_impl->setNativeMouseCursor(cursor); 479 else 480 return false; 481} 482 483bool SkiaWindow::setNativeMouseCursor(const Surface* surface, 484 const gfx::Point& focus, 485 const int scale) 486{ 487 if (m_impl) 488 return m_impl->setNativeMouseCursor(surface, focus, scale); 489 else 490 return false; 491} 492 493void SkiaWindow::updateWindow(const gfx::Rect& bounds) 494{ 495 if (m_impl) 496 m_impl->updateWindow(bounds); 497} 498 499void SkiaWindow::setTranslateDeadKeys(bool state) 500{ 501 if (m_impl) 502 m_impl->setTranslateDeadKeys(state); 503} 504 505void* SkiaWindow::handle() 506{ 507 if (m_impl) 508 return (void*)m_impl->handle(); 509 else 510 return nullptr; 511} 512 513} // namespace she 514