servo/support/magicleap/Servo2D/code/src/Servo2D.cpp
bors-servo 4ead81717a
Auto merge of #22044 - asajeffrey:magicleap-less-egl-buffer-swapping, r=jdm
Reduce the amount of EGL buffer swapping the ML port is doing

<!-- Please describe your changes on the following line: -->

At the moment, we're swapping the EGL buffers every heartbeat, which produces a lot of flickering. We should only swap the buffers when needed.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes do not require tests because we have no way to test on ML

<!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.-->

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/22044)
<!-- Reviewable:end -->
2018-10-30 09:57:56 -04:00

248 lines
7 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <Servo2D.h>
#include <lumin/node/RootNode.h>
#include <lumin/node/QuadNode.h>
#include <lumin/ui/Cursor.h>
#include <ml_logging.h>
#include <scenesGen.h>
#include <SceneDescriptor.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include <glm/gtc/matrix_transform.hpp>
#include <string.h>
// The viewport dimensions (in px).
const int VIEWPORT_W = 500;
const int VIEWPORT_H = 500;
// The hidpi factor.
const float HIDPI = 1.0;
// The prism dimensions (in m).
const float PRISM_W = 0.5;
const float PRISM_H = 0.5;
const float PRISM_D = 0.5;
// A function which calls the ML logger, suitable for passing into Servo
typedef void (*MLLogger)(MLLogLevel lvl, char* msg);
void logger(MLLogLevel lvl, char* msg) {
if (MLLoggingLogLevelIsEnabled(lvl)) {
MLLoggingLog(lvl, ML_DEFAULT_LOG_TAG, msg);
}
}
// The functions Servo provides for hooking up to the ML.
// For the moment, this doesn't handle input events.
extern "C" ServoInstance init_servo(EGLContext, EGLSurface, EGLDisplay, MLLogger,
const char* url, int width, int height, float hidpi);
extern "C" void heartbeat_servo(ServoInstance);
extern "C" void cursor_servo(ServoInstance, float x, float y, bool triggered);
extern "C" void discard_servo(ServoInstance);
// Create a Servo2D instance
Servo2D::Servo2D() {
ML_LOG(Debug, "Servo2D Constructor.");
}
// Destroy a Servo 2D instance
Servo2D::~Servo2D() {
ML_LOG(Debug, "Servo2D Destructor.");
discard_servo(servo_);
servo_ = nullptr;
}
// The prism dimensions
const glm::vec3 Servo2D::getInitialPrismExtents() const {
return glm::vec3(PRISM_W, PRISM_H, PRISM_D);
}
// Create the prism for Servo
int Servo2D::createInitialPrism() {
prism_ = requestNewPrism(getInitialPrismExtents());
if (!prism_) {
ML_LOG(Error, "Servo2D Error creating default prism.");
return 1;
}
return 0;
}
// Initialize a Servo instance
int Servo2D::init() {
ML_LOG(Debug, "Servo2D Initializing.");
// Set up the prism
createInitialPrism();
lumin::ui::Cursor::SetScale(prism_, 0.03f);
instanceInitialScenes();
// Get the planar resource that holds the EGL context
lumin::RootNode* root_node = prism_->getRootNode();
if (!root_node) {
ML_LOG(Error, "Servo2D Failed to get root node");
abort();
return 1;
}
std::string content_node_id = Servo2D_exportedNodes::content;
content_node_ = lumin::QuadNode::CastFrom(prism_->findNode(content_node_id, root_node));
if (!content_node_) {
ML_LOG(Error, "Servo2D Failed to get content node");
abort();
return 1;
}
content_node_->setTriggerable(true);
lumin::ResourceIDType plane_id = prism_->createPlanarEGLResourceId();
if (!plane_id) {
ML_LOG(Error, "Servo2D Failed to create EGL resource");
abort();
return 1;
}
plane_ = static_cast<lumin::PlanarResource*>(prism_->getResource(plane_id));
if (!plane_) {
ML_LOG(Error, "Servo2D Failed to create plane");
abort();
return 1;
}
content_node_->setRenderResource(plane_id);
// Get the EGL context, surface and display.
EGLContext ctx = plane_->getEGLContext();
EGLSurface surf = plane_->getEGLSurface();
EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
// Hook into servo
servo_ = init_servo(ctx, surf, dpy, logger, "https://servo.org/", VIEWPORT_H, VIEWPORT_W, HIDPI);
if (!servo_) {
ML_LOG(Error, "Servo2D Failed to init servo instance");
abort();
return 1;
}
return 0;
}
int Servo2D::deInit() {
ML_LOG(Debug, "Servo2D Deinitializing.");
return 0;
}
lumin::Node* Servo2D::instanceScene(const SceneDescriptor& scene) {
// Load resources.
if (!prism_->loadResourceModel(scene.getResourceModelPath())) {
ML_LOG(Info, "No resource model loaded");
}
// Load a scene file.
std::string editorObjectModelName;
if (!prism_->loadObjectModel(scene.getSceneGraphPath(), editorObjectModelName)) {
ML_LOG(Error, "Servo2D Failed to load object model");
abort();
return nullptr;
}
// Add scene to this prism.
lumin::Node* newTree = prism_->createAll(editorObjectModelName);
if (!prism_->getRootNode()->addChild(newTree)) {
ML_LOG(Error, "Servo2D Failed to add newTree to the prism root node");
abort();
return nullptr;
}
return newTree;
}
void Servo2D::instanceInitialScenes() {
// Iterate over all the exported scenes
for (auto& exportedSceneEntry : scenes::exportedScenes ) {
// If this scene was marked to be instanced at app initialization, do it
const SceneDescriptor &sd = exportedSceneEntry.second;
if (sd.getInitiallyInstanced()) {
instanceScene(sd);
}
}
}
bool Servo2D::updateLoop(float fDelta) {
// Hook into servo
heartbeat_servo(servo_);
// Return true for your app to continue running, false to terminate the app.
return true;
}
bool Servo2D::eventListener(lumin::ServerEvent* event) {
// Dispatch based on event type
switch (event->getServerEventType()) {
case lumin::ServerEventType::kControlTouchPadInputEvent:
return touchpadEventListener(static_cast<lumin::ControlTouchPadInputEventData*>(event));
case lumin::ServerEventType::kKeyInputEvent:
return keyEventListener(static_cast<lumin::KeyInputEventData*>(event));
default:
return false;
}
}
glm::vec2 Servo2D::viewportCursorPosition() {
// Get the cursor position relative to the origin of the content node (in m)
glm::vec3 pos = lumin::ui::Cursor::GetPosition(prism_) - content_node_->getPrismPosition();
// Get the size of the content node (in m)
glm::vec2 sz = content_node_->getSize();
// Convert to a position in viewport px
float x = (pos.x / sz.x) * (float)VIEWPORT_W;
float y = (1 - pos.y / sz.y) * (float)VIEWPORT_H; // Sigh, invert the y coordinate
return glm::vec2(x, y);
}
bool Servo2D::pointInsideViewport(glm::vec2 pt) {
return (0 <= pt.x && 0 <= pt.y && pt.x <= VIEWPORT_W && pt.y <= VIEWPORT_H);
}
bool Servo2D::touchpadEventListener(lumin::ControlTouchPadInputEventData* event) {
// Only respond when the cursor is enabled
if (!lumin::ui::Cursor::IsEnabled(prism_)) {
return false;
}
// Only respond when the cursor is inside the viewport
glm::vec2 pos = viewportCursorPosition();
if (!pointInsideViewport(pos)) {
return false;
}
// Inform Servo of the trigger
cursor_servo(servo_, pos.x, pos.y, false);
return true;
}
bool Servo2D::keyEventListener(lumin::KeyInputEventData* event) {
// Only respond to trigger keys
if (event->keyCode() != lumin::input::KeyCodes::AKEYCODE_EX_TRIGGER) {
return false;
}
// Only respond when the cursor is enabled
if (!lumin::ui::Cursor::IsEnabled(prism_)) {
return false;
}
// Only respond when the cursor is inside the viewport
glm::vec2 pos = viewportCursorPosition();
if (!pointInsideViewport(pos)) {
return false;
}
// Inform Servo of the trigger
cursor_servo(servo_, pos.x, pos.y, true);
return true;
}