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