comparison Framework/Viewport/WebAssemblyViewport.cpp @ 1232:a28861abf888 broker

viewports for WebAssembly
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 09 Dec 2019 17:46:33 +0100
parents 3c9529edf5fd
children a4bb8c2dd211
comparison
equal deleted inserted replaced
1229:b9f2a111c5b9 1232:a28861abf888
19 **/ 19 **/
20 20
21 21
22 #include "WebAssemblyViewport.h" 22 #include "WebAssemblyViewport.h"
23 23
24 #include "../StoneException.h" 24 #include <Core/OrthancException.h>
25 25
26 #include <emscripten/html5.h> 26 #include <boost/make_shared.hpp>
27 27
28 namespace OrthancStone 28 namespace OrthancStone
29 { 29 {
30 WebAssemblyOpenGLViewport::WebAssemblyOpenGLViewport(const std::string& canvas) : 30 static void ConvertMouseEvent(PointerEvent& target,
31 WebAssemblyViewport(canvas), 31 const EmscriptenMouseEvent& source,
32 context_(canvas) 32 const ICompositor& compositor)
33 { 33 {
34 compositor_.reset(new OpenGLCompositor(context_, GetScene())); 34 int x = static_cast<int>(source.targetX);
35 RegisterContextCallbacks(); 35 int y = static_cast<int>(source.targetY);
36 } 36
37 37 switch (source.button)
38 WebAssemblyOpenGLViewport::WebAssemblyOpenGLViewport(const std::string& canvas, 38 {
39 boost::shared_ptr<Scene2D>& scene) : 39 case 0:
40 WebAssemblyViewport(canvas, scene), 40 target.SetMouseButton(MouseButton_Left);
41 context_(canvas) 41 break;
42 { 42
43 compositor_.reset(new OpenGLCompositor(context_, GetScene())); 43 case 1:
44 RegisterContextCallbacks(); 44 target.SetMouseButton(MouseButton_Middle);
45 } 45 break;
46 46
47 void WebAssemblyOpenGLViewport::UpdateSize() 47 case 2:
48 { 48 target.SetMouseButton(MouseButton_Right);
49 context_.UpdateSize(); // First read the size of the canvas 49 break;
50 50
51 if (compositor_.get() != NULL) 51 default:
52 { 52 target.SetMouseButton(MouseButton_None);
53 compositor_->Refresh(); // Then refresh the content of the canvas 53 break;
54 } 54 }
55 } 55
56 56 target.AddPosition(compositor.GetPixelCenterCoordinates(x, y));
57 /* 57 target.SetAltModifier(source.altKey);
58 typedef EM_BOOL (*em_webgl_context_callback)(int eventType, const void *reserved, void *userData); 58 target.SetControlModifier(source.ctrlKey);
59 59 target.SetShiftModifier(source.shiftKey);
60 EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED 60 }
61 61
62 EMSCRIPTEN_RESULT emscripten_set_webglcontextlost_callback( 62
63 const char *target, void *userData, EM_BOOL useCapture, em_webgl_context_callback callback) 63 class WebAssemblyViewport::WasmLock : public ILock
64 64 {
65 EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback( 65 private:
66 const char *target, void *userData, EM_BOOL useCapture, em_webgl_context_callback callback) 66 WebAssemblyViewport& that_;
67 67
68 */ 68 public:
69 69 WasmLock(WebAssemblyViewport& that) :
70 EM_BOOL WebAssemblyOpenGLViewport_OpenGLContextLost_callback( 70 that_(that)
71 int eventType, const void* reserved, void* userData) 71 {
72 { 72 }
73 ORTHANC_ASSERT(eventType == EMSCRIPTEN_EVENT_WEBGLCONTEXTLOST); 73
74 WebAssemblyOpenGLViewport* viewport = reinterpret_cast<WebAssemblyOpenGLViewport*>(userData); 74 virtual bool HasCompositor() const ORTHANC_OVERRIDE
75 return viewport->OpenGLContextLost(); 75 {
76 } 76 return that_.compositor_.get() != NULL;
77 77 }
78 EM_BOOL WebAssemblyOpenGLViewport_OpenGLContextRestored_callback( 78
79 int eventType, const void* reserved, void* userData) 79 virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE
80 { 80 {
81 ORTHANC_ASSERT(eventType == EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED); 81 if (that_.compositor_.get() == NULL)
82 WebAssemblyOpenGLViewport* viewport = reinterpret_cast<WebAssemblyOpenGLViewport*>(userData);
83 return viewport->OpenGLContextRestored();
84 }
85
86 void WebAssemblyOpenGLViewport::DisableCompositor()
87 {
88 compositor_.reset();
89 }
90
91 ICompositor& WebAssemblyOpenGLViewport::GetCompositor()
92 {
93 if (compositor_.get() == NULL)
94 {
95 // "HasCompositor()" should have been called
96 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
97 }
98 else
99 {
100 return *compositor_;
101 }
102 }
103
104 void WebAssemblyOpenGLViewport::Refresh()
105 {
106 try
107 {
108 if (HasCompositor())
109 { 82 {
110 GetCompositor().Refresh(); 83 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
111 } 84 }
112 else 85 else
113 { 86 {
114 // this block was added because of (perceived?) bugs in the 87 return *that_.compositor_;
115 // browser where the WebGL contexts are NOT automatically restored 88 }
116 // after being lost. 89 }
117 // the WebGL context has been lost. Sce 90
118 91 virtual ViewportController& GetController() ORTHANC_OVERRIDE
119 //LOG(ERROR) << "About to call WebAssemblyOpenGLContext::TryRecreate()."; 92 {
120 //LOG(ERROR) << "Before calling it, isContextLost == " << context_.IsContextLost(); 93 assert(that_.controller_);
121 94 return *that_.controller_;
122 if (!context_.IsContextLost()) 95 }
123 { 96
124 LOG(TRACE) << "Context restored!"; 97 virtual void Invalidate() ORTHANC_OVERRIDE
125 //LOG(ERROR) << "After calling it, isContextLost == " << context_.IsContextLost(); 98 {
126 RestoreCompositor(); 99 that_.Invalidate();
127 UpdateSize(); 100 }
101 };
102
103
104 EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData)
105 {
106 WebAssemblyViewport& that = *reinterpret_cast<WebAssemblyViewport*>(userData);
107
108 if (that.compositor_.get() != NULL &&
109 that.controller_ /* should always be true */)
110 {
111 that.Paint(*that.compositor_, *that.controller_);
112 }
113
114 return true;
115 }
116
117
118 EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
119 {
120 WebAssemblyViewport& that = *reinterpret_cast<WebAssemblyViewport*>(userData);
121
122 if (that.compositor_.get() != NULL)
123 {
124 that.UpdateSize(*that.compositor_);
125 that.Invalidate();
126 }
127
128 return true;
129 }
130
131
132 EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
133 {
134 WebAssemblyViewport& that = *reinterpret_cast<WebAssemblyViewport*>(userData);
135
136 LOG(INFO) << "mouse down: " << that.GetFullCanvasId();
137
138 if (that.compositor_.get() != NULL &&
139 that.interactor_.get() != NULL)
140 {
141 PointerEvent pointer;
142 ConvertMouseEvent(pointer, *mouseEvent, *that.compositor_);
143
144 that.controller_->HandleMousePress(*that.interactor_, pointer,
145 that.compositor_->GetCanvasWidth(),
146 that.compositor_->GetCanvasHeight());
147 that.Invalidate();
148 }
149
150 return true;
151 }
152
153
154 EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
155 {
156 WebAssemblyViewport& that = *reinterpret_cast<WebAssemblyViewport*>(userData);
157
158 if (that.compositor_.get() != NULL &&
159 that.controller_->HasActiveTracker())
160 {
161 PointerEvent pointer;
162 ConvertMouseEvent(pointer, *mouseEvent, *that.compositor_);
163 that.controller_->HandleMouseMove(pointer);
164 that.Invalidate();
165 }
166
167 return true;
168 }
169
170
171 EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
172 {
173 WebAssemblyViewport& that = *reinterpret_cast<WebAssemblyViewport*>(userData);
174
175 if (that.compositor_.get() != NULL)
176 {
177 PointerEvent pointer;
178 ConvertMouseEvent(pointer, *mouseEvent, *that.compositor_);
179 that.controller_->HandleMouseRelease(pointer);
180 that.Invalidate();
181 }
182
183 return true;
184 }
185
186
187 void WebAssemblyViewport::Invalidate()
188 {
189 emscripten_request_animation_frame(OnRequestAnimationFrame, this);
190 }
191
192
193 void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */)
194 {
195 if (compositor == NULL)
196 {
197 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
198 }
199 else
200 {
201 compositor_.reset(compositor);
202 }
203 }
204
205
206 WebAssemblyViewport::WebAssemblyViewport(const std::string& canvasId,
207 const Scene2D* scene) :
208 shortCanvasId_(canvasId),
209 fullCanvasId_("#" + canvasId),
210 interactor_(new DefaultViewportInteractor)
211 {
212 if (scene == NULL)
213 {
214 controller_ = boost::make_shared<ViewportController>();
215 }
216 else
217 {
218 controller_ = boost::make_shared<ViewportController>(*scene);
219 }
220
221 LOG(INFO) << "Initializing Stone viewport on HTML canvas: " << canvasId;
222
223 if (canvasId.empty() ||
224 canvasId[0] == '#')
225 {
226 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
227 "The canvas identifier must not start with '#'");
228 }
229
230 // Disable right-click on the canvas (i.e. context menu)
231 EM_ASM({
232 document.getElementById(UTF8ToString($0)).oncontextmenu = function(event) {
233 event.preventDefault();
128 } 234 }
129 } 235 },
130 } 236 canvasId.c_str() // $0
131 catch (const StoneException& e) 237 );
132 { 238
133 if (e.GetErrorCode() == ErrorCode_WebGLContextLost) 239 // It is not possible to monitor the resizing of individual
134 { 240 // canvas, so we track the full window of the browser
135 LOG(WARNING) << "Context is lost! Compositor will be disabled."; 241 emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, this, false, OnResize);
136 DisableCompositor(); 242
137 // we now need to wait for the "context restored" callback 243 emscripten_set_mousedown_callback(fullCanvasId_.c_str(), this, false, OnMouseDown);
138 } 244 emscripten_set_mousemove_callback(fullCanvasId_.c_str(), this, false, OnMouseMove);
139 else 245 emscripten_set_mouseup_callback(fullCanvasId_.c_str(), this, false, OnMouseUp);
140 { 246 }
141 throw; 247
142 } 248
143 } 249 IViewport::ILock* WebAssemblyViewport::Lock()
144 catch (...) 250 {
145 { 251 return new WasmLock(*this);
146 // something else nasty happened 252 }
147 throw; 253
148 } 254
149 } 255 void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor)
150 256 {
151 void WebAssemblyOpenGLViewport::RestoreCompositor() 257 if (interactor == NULL)
152 { 258 {
153 // the context must have been restored! 259 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
154 ORTHANC_ASSERT(!context_.IsContextLost());
155 if (compositor_.get() == NULL)
156 {
157 compositor_.reset(new OpenGLCompositor(context_, GetScene()));
158 } 260 }
159 else 261 else
160 { 262 {
161 LOG(WARNING) << "RestoreCompositor() called for \"" << GetCanvasIdentifier() << "\" while it was NOT lost! Nothing done."; 263 interactor_.reset(interactor);
162 } 264 }
163 }
164
165 bool WebAssemblyOpenGLViewport::OpenGLContextLost()
166 {
167 LOG(ERROR) << "WebAssemblyOpenGLViewport::OpenGLContextLost() for canvas: " << GetCanvasIdentifier();
168 DisableCompositor();
169 return true;
170 }
171
172 bool WebAssemblyOpenGLViewport::OpenGLContextRestored()
173 {
174 LOG(ERROR) << "WebAssemblyOpenGLViewport::OpenGLContextRestored() for canvas: " << GetCanvasIdentifier();
175
176 // maybe the context has already been restored by other means (the
177 // Refresh() function)
178 if (!HasCompositor())
179 {
180 RestoreCompositor();
181 UpdateSize();
182 }
183 return false;
184 }
185
186 void WebAssemblyOpenGLViewport::RegisterContextCallbacks()
187 {
188 #if 0
189 // DISABLED ON 2019-08-20 and replaced by external JS calls because I could
190 // not get emscripten API to work
191 // TODO: what's the impact of userCapture=true ?
192 const char* canvasId = GetCanvasIdentifier().c_str();
193 void* that = reinterpret_cast<void*>(this);
194 EMSCRIPTEN_RESULT status = EMSCRIPTEN_RESULT_SUCCESS;
195
196 //status = emscripten_set_webglcontextlost_callback(canvasId, that, true, WebAssemblyOpenGLViewport_OpenGLContextLost_callback);
197 //if (status != EMSCRIPTEN_RESULT_SUCCESS)
198 //{
199 // std::stringstream ss;
200 // ss << "Error while calling emscripten_set_webglcontextlost_callback for: \"" << GetCanvasIdentifier() << "\"";
201 // std::string msg = ss.str();
202 // LOG(ERROR) << msg;
203 // ORTHANC_ASSERT(false, msg.c_str());
204 //}
205
206 status = emscripten_set_webglcontextrestored_callback(canvasId, that, true, WebAssemblyOpenGLViewport_OpenGLContextRestored_callback);
207 if (status != EMSCRIPTEN_RESULT_SUCCESS)
208 {
209 std::stringstream ss;
210 ss << "Error while calling emscripten_set_webglcontextrestored_callback for: \"" << GetCanvasIdentifier() << "\"";
211 std::string msg = ss.str();
212 LOG(ERROR) << msg;
213 ORTHANC_ASSERT(false, msg.c_str());
214 }
215 LOG(TRACE) << "WebAssemblyOpenGLViewport::RegisterContextCallbacks() SUCCESS!!!";
216 #endif
217 }
218
219 WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvas) :
220 WebAssemblyViewport(canvas),
221 canvas_(canvas),
222 compositor_(GetScene(), 1024, 768)
223 {
224 }
225
226 WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvas,
227 boost::shared_ptr<Scene2D>& scene) :
228 WebAssemblyViewport(canvas, scene),
229 canvas_(canvas),
230 compositor_(GetScene(), 1024, 768)
231 {
232 }
233
234 void WebAssemblyCairoViewport::UpdateSize()
235 {
236 LOG(INFO) << "updating cairo viewport size";
237 double w, h;
238 emscripten_get_element_css_size(canvas_.c_str(), &w, &h);
239
240 /**
241 * Emscripten has the function emscripten_get_element_css_size()
242 * to query the width and height of a named HTML element. I'm
243 * calling this first to get the initial size of the canvas DOM
244 * element, and then call emscripten_set_canvas_size() to
245 * initialize the framebuffer size of the canvas to the same
246 * size as its DOM element.
247 * https://floooh.github.io/2017/02/22/emsc-html.html
248 **/
249 unsigned int canvasWidth = 0;
250 unsigned int canvasHeight = 0;
251
252 if (w > 0 ||
253 h > 0)
254 {
255 canvasWidth = static_cast<unsigned int>(boost::math::iround(w));
256 canvasHeight = static_cast<unsigned int>(boost::math::iround(h));
257 }
258
259 emscripten_set_canvas_element_size(canvas_.c_str(), canvasWidth, canvasHeight);
260 compositor_.UpdateSize(canvasWidth, canvasHeight);
261 }
262
263 void WebAssemblyCairoViewport::Refresh()
264 {
265 LOG(INFO) << "refreshing cairo viewport, TODO: blit to the canvans.getContext('2d')";
266 GetCompositor().Refresh();
267 } 265 }
268 } 266 }