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