1 #include <utility>
2 
3 #include "RNSkJsView.h"
4 
5 namespace RNSkia {
6 
RNSkJsRenderer(std::function<void ()> requestRedraw,std::shared_ptr<RNSkPlatformContext> context)7 RNSkJsRenderer::RNSkJsRenderer(std::function<void()> requestRedraw,
8                                std::shared_ptr<RNSkPlatformContext> context)
9     : RNSkRenderer(requestRedraw),
10       _jsiCanvas(std::make_shared<JsiSkCanvas>(context)),
11       _platformContext(context),
12       _infoObject(std::make_shared<RNSkInfoObject>()),
13       _jsDrawingLock(std::make_shared<std::timed_mutex>()),
14       _gpuDrawingLock(std::make_shared<std::timed_mutex>()),
15       _jsTimingInfo("SKIA/JS"), _gpuTimingInfo("SKIA/GPU") {}
16 
tryRender(std::shared_ptr<RNSkCanvasProvider> canvasProvider)17 bool RNSkJsRenderer::tryRender(
18     std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
19   // We render on the javascript thread.
20   if (_jsDrawingLock->try_lock()) {
21     _platformContext->runOnJavascriptThread(
22         [weakSelf = weak_from_this(), canvasProvider]() {
23           auto self = weakSelf.lock();
24           if (self) {
25             self->performDraw(canvasProvider);
26           }
27         });
28     return true;
29   } else {
30 #ifdef DEBUG
31     _jsTimingInfo.markSkipped();
32 #endif
33     return false;
34   }
35 }
36 
renderImmediate(std::shared_ptr<RNSkCanvasProvider> canvasProvider)37 void RNSkJsRenderer::renderImmediate(
38     std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
39   std::chrono::milliseconds ms =
40       std::chrono::duration_cast<std::chrono::milliseconds>(
41           std::chrono::system_clock::now().time_since_epoch());
42   canvasProvider->renderToCanvas([&](SkCanvas *canvas) {
43     // Create jsi canvas
44     auto jsiCanvas = std::make_shared<JsiSkCanvas>(_platformContext);
45     jsiCanvas->setCanvas(canvas);
46 
47     drawInJsiCanvas(std::move(jsiCanvas), canvasProvider->getScaledWidth(),
48                     canvasProvider->getScaledHeight(), ms.count() / 1000);
49   });
50 }
51 
setDrawCallback(std::shared_ptr<jsi::Function> drawCallback)52 void RNSkJsRenderer::setDrawCallback(
53     std::shared_ptr<jsi::Function> drawCallback) {
54   _drawCallback = drawCallback;
55 }
56 
getInfoObject()57 std::shared_ptr<RNSkInfoObject> RNSkJsRenderer::getInfoObject() {
58   return _infoObject;
59 }
60 
performDraw(std::shared_ptr<RNSkCanvasProvider> canvasProvider)61 void RNSkJsRenderer::performDraw(
62     std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
63   // Start timing
64   _jsTimingInfo.beginTiming();
65 
66   // Record the drawing operations on the JS thread so that we can
67   // move the actual drawing onto the render thread later
68   SkPictureRecorder recorder;
69   SkRTreeFactory factory;
70   SkCanvas *canvas =
71       recorder.beginRecording(canvasProvider->getScaledWidth(),
72                               canvasProvider->getScaledHeight(), &factory);
73 
74   _jsiCanvas->setCanvas(canvas);
75 
76   // Get current milliseconds
77   std::chrono::milliseconds ms =
78       std::chrono::duration_cast<std::chrono::milliseconds>(
79           std::chrono::system_clock::now().time_since_epoch());
80 
81   try {
82     // Perform the javascript drawing
83     drawInJsiCanvas(_jsiCanvas, canvasProvider->getScaledWidth(),
84                     canvasProvider->getScaledHeight(), ms.count() / 1000.0);
85 
86   } catch (...) {
87     _jsTimingInfo.stopTiming();
88     _jsDrawingLock->unlock();
89     throw;
90   }
91 
92   // Finish drawing operations
93   auto p = recorder.finishRecordingAsPicture();
94 
95   _jsiCanvas->setCanvas(nullptr);
96 
97   // Calculate duration
98   _jsTimingInfo.stopTiming();
99 
100   if (_gpuDrawingLock->try_lock()) {
101 
102     // Post drawing message to the render thread where the picture recorded
103     // will be sent to the GPU/backend for rendering to screen.
104     auto gpuLock = _gpuDrawingLock;
105     _platformContext->runOnRenderThread([weakSelf = weak_from_this(),
106                                          p = std::move(p), gpuLock,
107                                          canvasProvider]() {
108       auto self = weakSelf.lock();
109       if (self) {
110         // Draw the picture recorded on the real GPU canvas
111         self->_gpuTimingInfo.beginTiming();
112 
113         canvasProvider->renderToCanvas(
114             [p = std::move(p)](SkCanvas *canvas) { canvas->drawPicture(p); });
115 
116         self->_gpuTimingInfo.stopTiming();
117       }
118       // Unlock GPU drawing
119       gpuLock->unlock();
120     });
121   } else {
122 #ifdef DEBUG
123     _gpuTimingInfo.markSkipped();
124 #endif
125     // Request a new redraw since the last frame was skipped.
126     _requestRedraw();
127   }
128 
129   // Unlock JS drawing
130   _jsDrawingLock->unlock();
131 }
132 
callJsDrawCallback(std::shared_ptr<JsiSkCanvas> jsiCanvas,int width,int height,double timestamp)133 void RNSkJsRenderer::callJsDrawCallback(std::shared_ptr<JsiSkCanvas> jsiCanvas,
134                                         int width, int height,
135                                         double timestamp) {
136 
137   if (_drawCallback == nullptr) {
138     return;
139   }
140 
141   // Reset timing info
142   _jsTimingInfo.reset();
143   _gpuTimingInfo.reset();
144 
145   auto runtime = _platformContext->getJsRuntime();
146 
147   // Update info parameter
148   _infoObject->beginDrawOperation(width, height, timestamp);
149 
150   // Set up arguments array
151   std::vector<jsi::Value> args(2);
152   args[0] = jsi::Object::createFromHostObject(*runtime, jsiCanvas);
153   args[1] = jsi::Object::createFromHostObject(*runtime, _infoObject);
154 
155   // To be able to call the drawing function we'll wrap it once again
156   _drawCallback->call(*runtime, static_cast<const jsi::Value *>(args.data()),
157                       static_cast<size_t>(2));
158 
159   // Reset touches
160   _infoObject->endDrawOperation();
161 
162   // Draw debug overlays
163   if (getShowDebugOverlays()) {
164 
165     // Display average rendering timer
166     auto jsAvg = _jsTimingInfo.getAverage();
167     // auto jsFps = _jsTimingInfo.getFps();
168 
169     auto gpuAvg = _gpuTimingInfo.getAverage();
170     // auto gpuFps = _gpuTimingInfo.getFps();
171 
172     auto total = jsAvg + gpuAvg;
173 
174     // Build string
175     std::ostringstream stream;
176     stream << "js: " << jsAvg << "ms gpu: " << gpuAvg << "ms "
177            << " total: " << total << "ms";
178 
179     std::string debugString = stream.str();
180 
181     // Set up debug font/paints
182     auto font = SkFont();
183     font.setSize(14);
184     auto paint = SkPaint();
185     paint.setColor(SkColors::kRed);
186     jsiCanvas->getCanvas()->drawSimpleText(
187         debugString.c_str(), debugString.size(), SkTextEncoding::kUTF8, 8, 18,
188         font, paint);
189   }
190 }
191 
drawInJsiCanvas(std::shared_ptr<JsiSkCanvas> jsiCanvas,int width,int height,double time)192 void RNSkJsRenderer::drawInJsiCanvas(std::shared_ptr<JsiSkCanvas> jsiCanvas,
193                                      int width, int height, double time) {
194 
195   // Call the draw drawCallback and perform js based drawing
196   auto skCanvas = jsiCanvas->getCanvas();
197   if (_drawCallback != nullptr && skCanvas != nullptr) {
198     // Make sure to scale correctly
199     auto pd = _platformContext->getPixelDensity();
200     skCanvas->clear(SK_ColorTRANSPARENT);
201     skCanvas->save();
202     skCanvas->scale(pd, pd);
203 
204     // Call draw function.
205     callJsDrawCallback(jsiCanvas, width / pd, height / pd, time);
206 
207     skCanvas->restore();
208   }
209 }
210 
211 } // namespace RNSkia
212