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