Mercurial > hg > orthanc-stone
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 } |