1 #include "RNSkDomView.h"
2 #include "DrawingContext.h"
3 
4 #include <chrono>
5 #include <utility>
6 
7 #pragma clang diagnostic push
8 #pragma clang diagnostic ignored "-Wdocumentation"
9 
10 #include <SkFont.h>
11 
12 #pragma clang diagnostic pop
13 
14 namespace RNSkia {
15 
RNSkDomRenderer(std::function<void ()> requestRedraw,std::shared_ptr<RNSkPlatformContext> context)16 RNSkDomRenderer::RNSkDomRenderer(std::function<void()> requestRedraw,
17                                  std::shared_ptr<RNSkPlatformContext> context)
18     : RNSkRenderer(requestRedraw), _platformContext(std::move(context)),
19       _renderLock(std::make_shared<std::timed_mutex>()),
20       _touchCallbackLock(std::make_shared<std::timed_mutex>()),
21       _renderTimingInfo("SKIA/RENDER") {}
22 
~RNSkDomRenderer()23 RNSkDomRenderer::~RNSkDomRenderer() {
24   if (_root != nullptr) {
25     _root->dispose();
26     _root = nullptr;
27   }
28 }
29 
tryRender(std::shared_ptr<RNSkCanvasProvider> canvasProvider)30 bool RNSkDomRenderer::tryRender(
31     std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
32   // If we have touches we need to call the touch callback as well
33   if (_currentTouches.size() > 0) {
34     callOnTouch();
35   }
36 
37   // We render on the main thread
38   if (_renderLock->try_lock()) {
39     // If we have a Dom Node we can render directly on the main thread
40     if (_root != nullptr) {
41       canvasProvider->renderToCanvas(std::bind(
42           &RNSkDomRenderer::renderCanvas, this, std::placeholders::_1,
43           canvasProvider->getScaledWidth(), canvasProvider->getScaledHeight()));
44     }
45 
46     _renderLock->unlock();
47     return true;
48   } else {
49     return false;
50   }
51 }
52 
renderImmediate(std::shared_ptr<RNSkCanvasProvider> canvasProvider)53 void RNSkDomRenderer::renderImmediate(
54     std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
55   auto prevDebugOverlay = getShowDebugOverlays();
56   setShowDebugOverlays(false);
57   canvasProvider->renderToCanvas(std::bind(
58       &RNSkDomRenderer::renderCanvas, this, std::placeholders::_1,
59       canvasProvider->getScaledWidth(), canvasProvider->getScaledHeight()));
60   setShowDebugOverlays(prevDebugOverlay);
61 }
62 
setRoot(std::shared_ptr<JsiDomRenderNode> node)63 void RNSkDomRenderer::setRoot(std::shared_ptr<JsiDomRenderNode> node) {
64   std::lock_guard<std::mutex> lock(_rootLock);
65   if (_root != nullptr) {
66     _root->dispose();
67     _root = nullptr;
68   }
69   _root = node;
70 }
71 
setOnTouchCallback(std::shared_ptr<jsi::Function> onTouchCallback)72 void RNSkDomRenderer::setOnTouchCallback(
73     std::shared_ptr<jsi::Function> onTouchCallback) {
74   _touchCallback = onTouchCallback;
75 }
76 
renderCanvas(SkCanvas * canvas,float scaledWidth,float scaledHeight)77 void RNSkDomRenderer::renderCanvas(SkCanvas *canvas, float scaledWidth,
78                                    float scaledHeight) {
79   _renderTimingInfo.beginTiming();
80 
81   auto pd = _platformContext->getPixelDensity();
82 
83   canvas->save();
84   canvas->scale(pd, pd);
85 
86   if (_drawingContext == nullptr) {
87     auto paint = std::make_shared<SkPaint>();
88     paint->setAntiAlias(true);
89     _drawingContext = std::make_shared<DrawingContext>(paint);
90 
91     _drawingContext->setRequestRedraw([weakSelf = weak_from_this()]() {
92       auto self = weakSelf.lock();
93       if (self) {
94         self->_requestRedraw();
95       }
96     });
97   }
98 
99   _drawingContext->setScaledWidth(scaledWidth);
100   _drawingContext->setScaledHeight(scaledHeight);
101 
102   // Update canvas before drawing
103   _drawingContext->setCanvas(canvas);
104 
105   try {
106     // Ask the root node to render to the provided canvas
107     std::lock_guard<std::mutex> lock(_rootLock);
108     if (_root != nullptr) {
109       _root->commitPendingChanges();
110       _root->render(_drawingContext.get());
111       _root->resetPendingChanges();
112     }
113   } catch (std::runtime_error err) {
114     _platformContext->raiseError(err);
115   } catch (jsi::JSError err) {
116     _platformContext->raiseError(err);
117   } catch (...) {
118     _platformContext->raiseError(
119         std::runtime_error("Error rendering the Skia view."));
120   }
121 
122   renderDebugOverlays(canvas);
123 
124   canvas->restore();
125 
126   _renderTimingInfo.stopTiming();
127 }
128 
updateTouches(std::vector<RNSkTouchInfo> & touches)129 void RNSkDomRenderer::updateTouches(std::vector<RNSkTouchInfo> &touches) {
130   std::lock_guard<std::mutex> lock(_touchMutex);
131   // Add timestamp
132   auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
133                 std::chrono::system_clock::now().time_since_epoch())
134                 .count();
135 
136   for (size_t i = 0; i < touches.size(); i++) {
137     touches.at(i).timestamp = ms;
138   }
139   _currentTouches.push_back(std::move(touches));
140 }
141 
callOnTouch()142 void RNSkDomRenderer::callOnTouch() {
143 
144   if (_touchCallback == nullptr) {
145     return;
146   }
147 
148   if (_touchCallbackLock->try_lock()) {
149 
150     {
151       std::lock_guard<std::mutex> lock(_touchMutex);
152       _touchesCache.clear();
153       _touchesCache.reserve(_currentTouches.size());
154       for (size_t i = 0; i < _currentTouches.size(); ++i) {
155         _touchesCache.push_back(_currentTouches.at(i));
156       }
157       _currentTouches.clear();
158     }
159 
160     // We have an onDraw method - use it to render since we don't have a
161     // DOM-node yet.
162     _platformContext->runOnJavascriptThread([weakSelf = weak_from_this()]() {
163       auto self = weakSelf.lock();
164       if (self) {
165         jsi::Runtime &runtime = *self->_platformContext->getJsRuntime();
166         // Set up touches
167         auto size = self->_touchesCache.size();
168         auto ops = jsi::Array(runtime, size);
169         for (size_t i = 0; i < size; i++) {
170           auto cur = self->_touchesCache.at(i);
171           auto curSize = cur.size();
172           auto touches = jsi::Array(runtime, curSize);
173           for (size_t n = 0; n < curSize; n++) {
174             auto touchObj = jsi::Object(runtime);
175             auto t = cur.at(n);
176             touchObj.setProperty(runtime, "x", t.x);
177             touchObj.setProperty(runtime, "y", t.y);
178             touchObj.setProperty(runtime, "force", t.force);
179             touchObj.setProperty(runtime, "type", static_cast<double>(t.type));
180             touchObj.setProperty(runtime, "timestamp",
181                                  static_cast<double>(t.timestamp) / 1000.0);
182             touchObj.setProperty(runtime, "id", static_cast<double>(t.id));
183             touches.setValueAtIndex(runtime, n, touchObj);
184           }
185           ops.setValueAtIndex(runtime, i, touches);
186         }
187         // Call on touch callback
188         self->_touchCallback->call(runtime, ops, 1);
189       }
190       self->_touchCallbackLock->unlock();
191     });
192   } else {
193     // We'll try next time - schedule a new redraw
194     _requestRedraw();
195   }
196 }
197 
renderDebugOverlays(SkCanvas * canvas)198 void RNSkDomRenderer::renderDebugOverlays(SkCanvas *canvas) {
199   if (!getShowDebugOverlays()) {
200     return;
201   }
202   auto renderAvg = _renderTimingInfo.getAverage();
203   auto fps = _renderTimingInfo.getFps();
204 
205   // Build string
206   std::ostringstream stream;
207   stream << "render: " << renderAvg << "ms"
208          << " fps: " << fps;
209 
210   std::string debugString = stream.str();
211 
212   // Set up debug font/paints
213   auto font = SkFont();
214   font.setSize(14);
215   auto paint = SkPaint();
216   paint.setColor(SkColors::kRed);
217   canvas->drawSimpleText(debugString.c_str(), debugString.size(),
218                          SkTextEncoding::kUTF8, 8, 18, font, paint);
219 }
220 
221 } // namespace RNSkia
222