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