diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index ad26b1249b..c797ef05c7 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -37,7 +37,6 @@ include(../dep_deng1.pri) include(../dep_shell.pri) include(../dep_gui.pri) include(../dep_appfw.pri) -include(../dep_rift.pri) # Definitions ---------------------------------------------------------------- @@ -414,9 +413,7 @@ DENG_HEADERS += \ include/ui/ui2_main.h \ include/ui/ui_main.h \ include/ui/ui_panel.h \ - include/ui/vrwindowtransform.h \ include/ui/windowsystem.h \ - include/ui/windowtransform.h \ include/ui/zonedebug.h \ include/updater.h \ include/updater/downloaddialog.h \ @@ -722,7 +719,6 @@ SOURCES += \ src/ui/ui2_main.cpp \ src/ui/ui_main.cpp \ src/ui/ui_panel.cpp \ - src/ui/vrwindowtransform.cpp \ src/ui/widgetactions.cpp \ src/ui/widgets/busywidget.cpp \ src/ui/widgets/consolecommandwidget.cpp \ @@ -741,7 +737,6 @@ SOURCES += \ src/ui/widgets/profilepickerwidget.cpp \ src/ui/widgets/taskbarwidget.cpp \ src/ui/windowsystem.cpp \ - src/ui/windowtransform.cpp \ src/ui/zonedebug.cpp \ src/updater/downloaddialog.cpp \ src/updater/processcheckdialog.cpp \ diff --git a/doomsday/client/include/gl/gl_main.h b/doomsday/client/include/gl/gl_main.h index 277d9c5cd5..5a3fe24075 100644 --- a/doomsday/client/include/gl/gl_main.h +++ b/doomsday/client/include/gl/gl_main.h @@ -314,6 +314,8 @@ void GL_CalcLuminance(uint8_t const *buffer, int width, int height, int comps, colorpaletteid_t paletteId, float *brightX, float *brightY, struct ColorRawf_s *color, float *lumSize); +void DGL_AssertNotInPrimitive(void); + // Console commands. D_CMD(UpdateGammaRamp); diff --git a/doomsday/client/include/render/vr.h b/doomsday/client/include/render/vr.h index 229c502fc1..dc8f37e2de 100644 --- a/doomsday/client/include/render/vr.h +++ b/doomsday/client/include/render/vr.h @@ -21,116 +21,29 @@ #define CLIENT_RENDER_VR_H #include "dd_types.h" -#include "de/Vector" +#include -namespace VR { +de::VRConfig &vrCfg(); -/// Menu of stereoscopic 3D modes available. Oculus Rift is the star player, but there -/// are many other options. -/// The order shown here determines the integer value in the console. -/// @todo - Add an additional console command to support symbolic versions of these mode settings -enum Stereo3DMode { - MODE_MONO = 0, - MODE_GREEN_MAGENTA, - MODE_RED_CYAN, - MODE_LEFT, - MODE_RIGHT, - MODE_TOP_BOTTOM, // 5 - MODE_SIDE_BY_SIDE, - MODE_PARALLEL, - MODE_CROSSEYE, - MODE_OCULUS_RIFT, - MODE_ROW_INTERLEAVED, // 10 // NOT IMPLEMENTED YET - MODE_COLUMN_INTERLEAVED, // NOT IMPLEMENTED YET - MODE_CHECKERBOARD, // NOT IMPLEMENTED YET - MODE_QUAD_BUFFERED, - // - MODE_MAX_3D_MODE_PLUS_ONE -}; +namespace VR +{ + /// (UNUSED) Distance from player character to weapon sprite, in map units + extern float weaponDistance; +} +/** + * Register VR console variables. + */ +void VR_ConsoleRegister(); -class RiftState { -public: - RiftState(); - - // Use screen size instead of resolution in case non-square pixels? - float aspect() const {return 0.5f * hScreenSize() / vScreenSize();} - const de::Vector4f& chromAbParam() const {return m_chromAbParam;} - float distortionScale() const; - float fovX() const; // in degrees - float fovY() const; // in degrees - const de::Vector4f& hmdWarpParam() const {return m_hmdWarpParam;} - float hScreenSize() const {return m_screenSize[0];} - float lensSeparationDistance() const {return m_lensSeparationDistance;} - bool loadRiftParameters(); - const de::Vector2f& screenSize() const {return m_screenSize;} - float vScreenSize() const {return m_screenSize[1];} - -private: - de::Vector2f m_screenSize; - float m_lensSeparationDistance; - de::Vector4f m_hmdWarpParam; - de::Vector4f m_chromAbParam; - float m_eyeToScreenDistance; -}; - -extern RiftState riftState; - -// Console variables -Stereo3DMode mode(); ///< Currently active Stereo3DMode index -bool modeNeedsStereoGLFormat(Stereo3DMode mode); -float riftFovX(); ///< Horizontal field of view in Oculus Rift in degrees -float riftLatency(); ///< Estimated head-motion->photons latency, in seconds - -extern float ipd; ///< Interpupillary distance in meters -extern float playerHeight; ///< Human player's real world height in meters -extern float dominantEye; ///< Kludge for aim-down-weapon-sight modes -extern byte swapEyes; ///< When true, inverts stereoscopic effect - -// Variables below are global, but not user visible // - -// Unlike most 3D modes, Oculus Rift typically uses no frustum shift. -// (or if we did, it would be different and complicated) -extern bool applyFrustumShift; - -// local viewpoint relative eye position in map units, -// VR::eyeShift is ordinarily set from VR::getEyeShift() -extern float eyeShift; - -extern float hudDistance; // Distance from player character to screen, in map units (not used in Rift mode, because it's used by frustum shift) -extern float weaponDistance; // (UNUSED) Distance from player character to weapon sprite, in map units - -extern int riftFramebufferSamples; // Multisampling used in unwarped Rift framebuffer - -/// @param eye: -1 means left eye, +1 means right eye -/// @return viewpoint eye shift in map units -float getEyeShift(float eye); - -// Register console variables -void consoleRegister(); - -// Head tracking API - -// True if Oculus Rift is enabled and can report head orientation. -bool hasHeadOrientation(); - -// Called to allow head orientation to change again. -void allowHeadOrientationUpdate(); - -void updateHeadOrientation(); - -// Returns current pitch, roll, yaw angles, in radians. If no head tracking is available, -// the returned values are not valid. -de::Vector3f getHeadOrientation(); - -// To release memory and resources when done, for tidiness. -void deleteOculusTracker(); - -void setRiftLatency(float latency); - -// Load Oculus Rift parameters via Rift SDK -bool loadRiftParameters(); +/** + * Returns the horizontal field of view in Oculus Rift in degrees. + */ +float VR_RiftFovX(); -} // namespace VR +/** + * Load Oculus Rift parameters via Rift SDK. + */ +bool VR_LoadRiftParameters(); #endif // CLIENT_RENDER_VR_H diff --git a/doomsday/client/include/ui/clientwindow.h b/doomsday/client/include/ui/clientwindow.h index 9a849b8f53..6a3cb3cada 100644 --- a/doomsday/client/include/ui/clientwindow.h +++ b/doomsday/client/include/ui/clientwindow.h @@ -22,6 +22,7 @@ #define CLIENT_CLIENTWINDOW_H #include +#include #include #include "ui/clientrootwidget.h" @@ -54,6 +55,7 @@ class AlertDialog; * Top-level window that contains a libdeng2 UI widgets. @ingroup gui */ class ClientWindow : public de::PersistentCanvasWindow, + public de::BaseWindow, DENG2_OBSERVES(de::Canvas, GLInit), DENG2_OBSERVES(de::Canvas, GLResize) { @@ -164,6 +166,11 @@ class ClientWindow : public de::PersistentCanvasWindow, void canvasGLDraw(de::Canvas &); void canvasGLResized(de::Canvas &); + // Implements BaseWindow: + de::Vector2f windowContentSize(); + de::Canvas &windowCanvas(); + void drawWindowContent(); + static ClientWindow &main(); public slots: diff --git a/doomsday/client/src/dd_main.cpp b/doomsday/client/src/dd_main.cpp index 17be63a9e1..7344ecae8c 100644 --- a/doomsday/client/src/dd_main.cpp +++ b/doomsday/client/src/dd_main.cpp @@ -2374,7 +2374,7 @@ int DD_GetInteger(int ddvalue) return (int) GL_PrepareLSTexture(LST_DYNAMIC); case DD_USING_HEAD_TRACKING: - return VR::mode() == VR::MODE_OCULUS_RIFT && VR::hasHeadOrientation(); + return vrCfg().mode() == VRConfig::OculusRift && vrCfg().oculusRift().isReady(); #endif case DD_NUMLUMPS: diff --git a/doomsday/client/src/gl/dgl_common.cpp b/doomsday/client/src/gl/dgl_common.cpp index 10362ce289..5d46a22bfc 100644 --- a/doomsday/client/src/gl/dgl_common.cpp +++ b/doomsday/client/src/gl/dgl_common.cpp @@ -333,9 +333,8 @@ void GL_SetVSync(dd_bool on) #ifdef WIN32 wglSwapIntervalEXT(on? 1 : 0); -#endif -#ifdef MACOSX +#elif defined(MACOSX) { // Tell CGL to wait for vertical refresh. CGLContextObj context = CGLGetCurrentContext(); @@ -346,6 +345,9 @@ void GL_SetVSync(dd_bool on) CGLSetParameter(context, kCGLCPSwapInterval, params); } } + +#elif defined(Q_WS_X11) + setXSwapInterval(on? 1 : 0); #endif } diff --git a/doomsday/client/src/gl/dgl_draw.cpp b/doomsday/client/src/gl/dgl_draw.cpp index 635ca5de27..a5aba3a77a 100644 --- a/doomsday/client/src/gl/dgl_draw.cpp +++ b/doomsday/client/src/gl/dgl_draw.cpp @@ -297,6 +297,11 @@ DENG_EXTERN_C void DGL_Begin(dglprimtype_t mode) DGL_QUAD_STRIP ? GL_QUAD_STRIP : GL_QUADS); } +void DGL_AssertNotInPrimitive(void) +{ + DENG_ASSERT(!inPrim); +} + #undef DGL_End DENG_EXTERN_C void DGL_End(void) { diff --git a/doomsday/client/src/gl/gl_main.cpp b/doomsday/client/src/gl/gl_main.cpp index 3a33d3f9cf..b0eda5529d 100644 --- a/doomsday/client/src/gl/gl_main.cpp +++ b/doomsday/client/src/gl/gl_main.cpp @@ -104,11 +104,11 @@ static void videoVsyncChanged() { if(novideo || !WindowSystem::hasMain()) return; -#if defined(WIN32) || defined(MACOSX) +//#if defined(WIN32) || defined(MACOSX) GL_SetVSync(Con_GetByte("vid-vsync") != 0); -#else - WindowSystem::main().updateCanvasFormat(); -#endif +//#else +// WindowSystem::main().updateCanvasFormat(); +//#endif } void GL_Register() @@ -192,7 +192,7 @@ void GL_DoUpdate() // Blit screen to video. ClientWindow::main().swapBuffers( - VR::modeNeedsStereoGLFormat(VR::mode())? gl::SwapStereoBuffers : gl::SwapMonoBuffer); + vrCfg().needsStereoGLFormat()? gl::SwapStereoBuffers : gl::SwapMonoBuffer); // We will arrive here always at the same time in relation to the displayed // frame: it is a good time to update the mouse state. @@ -340,6 +340,8 @@ dd_bool GL_EarlyInit() // Initialize the renderer into a 2D state. GL_Init2DState(); + GL_SetVSync(true); // will be overridden from vid-vsync + initGLOk = true; return true; } @@ -562,9 +564,9 @@ Matrix4f GL_GetProjectionMatrix() // We're assuming pixels are squares. float aspect = viewpw / (float) viewph; - if (VR::mode() == VR::MODE_OCULUS_RIFT) + if (vrCfg().mode() == VRConfig::OculusRift) { - aspect = VR::riftState.aspect(); + aspect = vrCfg().oculusRift().aspect(); // A little trigonometry to apply aspect ratio to angles float x = tan(0.5 * de::degreeToRadian(Rend_FieldOfView())); yfov = de::radianToDegree(2.0 * atan2(x/aspect, 1.0f)); @@ -586,15 +588,15 @@ Matrix4f GL_GetProjectionMatrix() * applies the viewpoint shift. */ float frustumShift = 0; - if (VR::applyFrustumShift) + if (vrCfg().frustumShift()) { - frustumShift = VR::eyeShift * glNearClip / VR::hudDistance; + frustumShift = vrCfg().eyeShift() * glNearClip / vrCfg().screenDistance(); } return Matrix4f::frustum(-fW - frustumShift, fW - frustumShift, -fH, fH, glNearClip, glFarClip) * - Matrix4f::translate(Vector3f(-VR::eyeShift, 0, 0)) * + Matrix4f::translate(Vector3f(-vrCfg().eyeShift(), 0, 0)) * Matrix4f::scale(Vector3f(1, 1, -1)); } diff --git a/doomsday/client/src/gl/sys_opengl.cpp b/doomsday/client/src/gl/sys_opengl.cpp index 128564de28..4ec06bc3f3 100644 --- a/doomsday/client/src/gl/sys_opengl.cpp +++ b/doomsday/client/src/gl/sys_opengl.cpp @@ -419,6 +419,12 @@ void Sys_GLPrintExtensions(void) printExtensions(QString((char const *) ((GLubyte const *(__stdcall *)(HDC))wglGetExtensionsStringARB)(wglGetCurrentDC())).split(" ", QString::SkipEmptyParts)); } #endif + +#ifdef Q_WS_X11 + // List GLX extensions. + LOG_GL_MSG(" Extensions (GLX):"); + printExtensions(QString(getGLXExtensionsString()).split(" ", QString::SkipEmptyParts)); +#endif } dd_bool Sys_GLCheckError() diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index 7c684b28a2..cd7e2fd7e9 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -291,14 +291,16 @@ static void unlinkMobjLumobjs() } } -static void fieldOfViewChanged() { - if (VR::mode() == VR::MODE_OCULUS_RIFT) { - if (Con_GetFloat("rend-vr-rift-fovx") != fieldOfView) +static void fieldOfViewChanged() +{ + if(vrCfg().mode() == VRConfig::OculusRift) + { + if(Con_GetFloat("rend-vr-rift-fovx") != fieldOfView) Con_SetFloat("rend-vr-rift-fovx", fieldOfView); } else { - if (Con_GetFloat("rend-vr-nonrift-fovx") != fieldOfView) + if(Con_GetFloat("rend-vr-nonrift-fovx") != fieldOfView) Con_SetFloat("rend-vr-nonrift-fovx", fieldOfView); } } @@ -427,7 +429,7 @@ void Rend_Register() LensFx_Register(); fx::Vignette::consoleRegister(); fx::LensFlares::consoleRegister(); - VR::consoleRegister(); + VR_ConsoleRegister(); } static void reportWallSectionDrawn(Line &line) @@ -484,7 +486,7 @@ bool Rend_IsMTexDetails() float Rend_FieldOfView() { - if (VR::mode() == VR::MODE_OCULUS_RIFT) + if(vrCfg().mode() == VRConfig::OculusRift) { // fieldOfView = VR::riftFovX(); // Update for culling // return VR::riftFovX(); @@ -538,9 +540,9 @@ Matrix4f Rend_GetModelViewMatrix(int consoleNum, bool useAngles) * these values and is syncing with them independently (however, game has more * latency). */ - if((VR::mode() == VR::MODE_OCULUS_RIFT) && VR::hasHeadOrientation()) + if((vrCfg().mode() == VRConfig::OculusRift) && vrCfg().oculusRift().isReady()) { - Vector3f const pry = VR::getHeadOrientation(); + Vector3f const pry = vrCfg().oculusRift().headOrientation(); // Use angles directly from the Rift for best response. roll = -radianToDegree(pry[1]); diff --git a/doomsday/client/src/render/viewports.cpp b/doomsday/client/src/render/viewports.cpp index 7dff6f4200..b57447398d 100644 --- a/doomsday/client/src/render/viewports.cpp +++ b/doomsday/client/src/render/viewports.cpp @@ -868,9 +868,11 @@ DENG_EXTERN_C void R_RenderPlayerView(int num) // Setup for rendering the frame. R_SetupFrame(player); + vrCfg().setEyeHeightInMapUnits(Con_GetInteger("player-eyeheight")); + // Latest possible time to check the real head angles. After this we'll be // using the provided values. - VR::updateHeadOrientation(); + vrCfg().oculusRift().update(); R_SetupPlayerSprites(); @@ -1322,7 +1324,7 @@ angle_t viewer_t::angle() const { // Apply the actual, current yaw offset. The game has omitted the "body yaw" // portion from the value already. - a += (fixed_t)(radianToDegree(VR::getHeadOrientation()[2]) / 180 * ANGLE_180); + a += (fixed_t)(radianToDegree(vrCfg().oculusRift().headOrientation()[2]) / 180 * ANGLE_180); } return a; } diff --git a/doomsday/client/src/render/vr.cpp b/doomsday/client/src/render/vr.cpp index cf09dcaaba..d3a5de1d45 100644 --- a/doomsday/client/src/render/vr.cpp +++ b/doomsday/client/src/render/vr.cpp @@ -1,7 +1,7 @@ /** @file render/vr.cpp Stereoscopic rendering and Oculus Rift support. * + * @authors Copyright (c) 2013-2014 Jaakko Keränen * @authors Copyright (c) 2013 Christopher Bruns - * @authors Copyright (c) 2013 Jaakko Keränen * * @par License * GPL: http://www.gnu.org/licenses/gpl.html @@ -20,105 +20,54 @@ #include "de_console.h" #include "render/vr.h" -VR::RiftState VR::riftState; +#include -VR::RiftState::RiftState() - : m_screenSize(0.14976f, 0.09360f) - , m_lensSeparationDistance(0.0635f) - , m_hmdWarpParam(1.0f, 0.220f, 0.240f, 0.000f) - , m_chromAbParam(0.996f, -0.004f, 1.014f, 0.0f) - , m_eyeToScreenDistance(0.041f) -{ -} - -float VR::RiftState::distortionScale() const -{ - float lensShift = hScreenSize() * 0.25f - lensSeparationDistance() * 0.5f; - float lensViewportShift = 4.0f * lensShift / hScreenSize(); - float fitRadius = fabs(-1 - lensViewportShift); - float rsq = fitRadius*fitRadius; - const de::Vector4f& k = hmdWarpParam(); - float scale = (k[0] + k[1] * rsq + k[2] * rsq * rsq + k[3] * rsq * rsq * rsq); - return scale; -} +using namespace de; -float VR::RiftState::fovX() const { - float w = 0.25 * hScreenSize() * distortionScale(); - float d = m_eyeToScreenDistance; - float fov = de::radianToDegree(2.0 * atan(w/d)); - return fov; +namespace VR { + float weaponDistance = 10.f; // global } -float VR::RiftState::fovY() const { - float w = 0.5 * vScreenSize() * distortionScale(); - float d = m_eyeToScreenDistance; - float fov = de::radianToDegree(2.0 * atan(w/d)); - return fov; -} - -// Console variables - -static int vrMode = (int)VR::MODE_MONO; -VR::Stereo3DMode VR::mode() -{ - return (VR::Stereo3DMode)vrMode; -} +static int vrMode = VRConfig::Mono; +static float vrRiftFovX = 114.8f; +static float vrNonRiftFovX = 95.f; +static float vrHudDistance; +static float vrRiftLatency; +static float vrPlayerHeight; +static float vrIpd; +static int vrRiftFBSamples; +static byte vrSwapEyes; +static float vrDominantEye; +static byte autoLoadRiftParams = 1; -bool VR::modeNeedsStereoGLFormat(VR::Stereo3DMode mode) +VRConfig &vrCfg() { - return mode == VR::MODE_QUAD_BUFFERED; + DENG2_ASSERT(DENG2_BASE_GUI_APP != 0); + return DENG2_BASE_GUI_APP->vr(); } -static float vrRiftFovX = 114.8f; -float VR::riftFovX() /// Horizontal field of view in degrees +float VR_RiftFovX() { return vrRiftFovX; } -static float vrNonRiftFovX = 95.0; - -static float vrLatency = 0.030f; -float VR::riftLatency() { - return vrLatency; -} - -// Interpupillary distance in meters -float VR::ipd = .064f; // average male IPD -float VR::playerHeight = 1.75f; -float VR::dominantEye = 0.0f; -byte VR::swapEyes = 0; - - -// Global variables -bool VR::applyFrustumShift = true; -float VR::eyeShift = 0; -float VR::hudDistance = 20.0f; -int VR::riftFramebufferSamples = 2; -float VR::weaponDistance = 10.0f; - -/// @param eye: -1 means left eye, +1 means right eye -/// @return viewpoint eye shift in map units -float VR::getEyeShift(float eye) +static void vrConfigVariableChanged() { - // 0.95 because eyes are not at top of head - float mapUnitsPerMeter = Con_GetInteger("player-eyeheight") / ((0.95) * VR::playerHeight); - float result = mapUnitsPerMeter * (eye - VR::dominantEye) * 0.5 * VR::ipd; - if ( VR::swapEyes != 0) - result *= -1; - return result; -} - -static void vrLatencyChanged() -{ - if (VR::hasHeadOrientation()) { - VR::setRiftLatency(vrLatency); - } + vrCfg().setDominantEye(vrDominantEye); + vrCfg().setScreenDistance(vrHudDistance); + vrCfg().setInterpupillaryDistance(vrIpd); + vrCfg().setPhysicalPlayerHeight(vrPlayerHeight); + vrCfg().oculusRift().setPredictionLatency(vrRiftLatency); + vrCfg().setRiftFramebufferSampleCount(vrRiftFBSamples); + vrCfg().setSwapEyes(vrSwapEyes); } // Interplay among vrNonRiftFovX, vrRiftFovX, and cameraFov depends on vrMode // see also rend_main.cpp static void vrModeChanged() { + vrCfg().setMode(VRConfig::StereoMode(vrMode)); + if(ClientWindow::mainExists()) { // The logical UI size may need to be changed. @@ -126,17 +75,18 @@ static void vrModeChanged() win.updateRootSize(); win.updateCanvasFormat(); // possibly changes pixel format } - if (VR::mode() == VR::MODE_OCULUS_RIFT) { + + // Update FOV cvar accordingly. + if(vrMode == VRConfig::OculusRift) + { if(Con_GetFloat("rend-camera-fov") != vrRiftFovX) Con_SetFloat("rend-camera-fov", vrRiftFovX); - if (VR::hasHeadOrientation()) - { - // Update prediction latency. - VR::setRiftLatency(vrLatency); - } + // Update prediction latency. + vrCfg().oculusRift().setPredictionLatency(vrRiftLatency); } - else { + else + { if(Con_GetFloat("rend-camera-fov") != vrNonRiftFovX) Con_SetFloat("rend-camera-fov", vrNonRiftFovX); } @@ -144,7 +94,8 @@ static void vrModeChanged() static void vrRiftFovXChanged() { - if (VR::mode() == VR::MODE_OCULUS_RIFT) { + if(vrCfg().mode() == VRConfig::OculusRift) + { if(Con_GetFloat("rend-camera-fov") != vrRiftFovX) Con_SetFloat("rend-camera-fov", vrRiftFovX); } @@ -152,244 +103,70 @@ static void vrRiftFovXChanged() static void vrNonRiftFovXChanged() { - if (VR::mode() != VR::MODE_OCULUS_RIFT) { + if(vrCfg().mode() != VRConfig::OculusRift) + { if(Con_GetFloat("rend-camera-fov") != vrNonRiftFovX) Con_SetFloat("rend-camera-fov", vrNonRiftFovX); } } -static byte autoLoadRiftParams = 1; D_CMD(LoadRiftParams) { - return VR::loadRiftParameters(); + return VR_LoadRiftParameters(); } -void VR::consoleRegister() +void VR_ConsoleRegister() { - C_VAR_BYTE ("rend-vr-autoload-rift-params", & autoLoadRiftParams, 0, 0, 1); - C_VAR_FLOAT ("rend-vr-dominant-eye", & VR::dominantEye, 0, -1.0f, 1.0f); - C_VAR_FLOAT ("rend-vr-hud-distance", & VR::hudDistance, 0, 0.01f, 40.0f); - C_VAR_FLOAT ("rend-vr-ipd", & VR::ipd, 0, 0.02f, 0.1f); - C_VAR_INT2 ("rend-vr-mode", & vrMode, 0, 0, (int)(VR::MODE_MAX_3D_MODE_PLUS_ONE - 1), vrModeChanged); - C_VAR_FLOAT2("rend-vr-nonrift-fovx", & vrNonRiftFovX, 0, 5.0f, 270.0f, vrNonRiftFovXChanged); - C_VAR_FLOAT ("rend-vr-player-height", & VR::playerHeight, 0, 1.0f, 2.4f); - C_VAR_FLOAT2("rend-vr-rift-fovx", & vrRiftFovX, 0, 5.0f, 270.0f, vrRiftFovXChanged); - C_VAR_FLOAT2("rend-vr-rift-latency", & vrLatency, 0, 0.0f, 0.100f, vrLatencyChanged); - C_VAR_INT ("rend-vr-rift-samples", & VR::riftFramebufferSamples, 0, 1, 4); - C_VAR_BYTE ("rend-vr-swap-eyes", & VR::swapEyes, 0, 0, 1); - - C_CMD("loadriftparams", NULL, LoadRiftParams); -} - - -// Warping - -/// @todo warping - -// Head tracking - -#ifdef DENG_HAVE_OCULUS_API -#include "OVR.h" - -class OculusTracker { -public: - OculusTracker() - : pitch(0) - , roll(0) - , yaw(0) - , latency(0) - { - OVR::System::Init(); - pFusionResult = new OVR::SensorFusion(); - pManager = *OVR::DeviceManager::Create(); - pHMD = *pManager->EnumerateDevices().CreateDevice(); - if(pHMD) - { - InfoLoaded = pHMD->GetDeviceInfo(&Info); - pSensor = pHMD->GetSensor(); - } - else - { - pSensor = *pManager->EnumerateDevices().CreateDevice(); - } - - if (pSensor) - { - pFusionResult->AttachToSensor(pSensor); - } - } + // Get the built-in defaults. + vrDominantEye = vrCfg().dominantEye(); + vrHudDistance = vrCfg().screenDistance(); + vrIpd = vrCfg().interpupillaryDistance(); + vrPlayerHeight = vrCfg().physicalPlayerHeight(); + vrRiftLatency = vrCfg().oculusRift().predictionLatency(); + vrRiftFBSamples = vrCfg().riftFramebufferSampleCount(); + vrSwapEyes = vrCfg().swapEyes(); - ~OculusTracker() - { - pSensor.Clear(); - pHMD.Clear(); - pManager.Clear(); - delete pFusionResult; - OVR::System::Destroy(); - } - - const OVR::HMDInfo& getInfo() const { - return Info; - } - - bool isGood() const { - return pSensor.GetPtr() != NULL; - } - - void update() - { - OVR::Quatf quaternion; - if (latency == 0) - quaternion = pFusionResult->GetOrientation(); - else - quaternion = pFusionResult->GetPredictedOrientation(); - quaternion.GetEulerAngles(&yaw, &pitch, &roll); - } - - void setLatency(float lat) - { - if (latency == lat) - return; // no change - latency = lat; - if (latency == 0) - { - pFusionResult->SetPredictionEnabled(false); - pFusionResult->SetPrediction(latency); - } - else - { - pFusionResult->SetPredictionEnabled(true); - pFusionResult->SetPrediction(latency); - } - } - - // Head orientation state, refreshed by call to update(); - float pitch, roll, yaw; - -private: - OVR::Ptr pManager; - OVR::Ptr pHMD; - OVR::Ptr pSensor; - OVR::SensorFusion* pFusionResult; - OVR::HMDInfo Info; - bool InfoLoaded; - float latency; -}; - -static OculusTracker* oculusTracker = NULL; -#endif - -static bool loadRiftParametersNoCheck() { -#ifdef DENG_HAVE_OCULUS_API - VR::riftState.loadRiftParameters(); - const OVR::HMDInfo& info = oculusTracker->getInfo(); - Con_SetFloat("rend-vr-ipd", info.InterpupillaryDistance); - Con_SetFloat("rend-vr-rift-fovx", VR::riftState.fovX()); - // I think this field of view is unreliable... CMB - /* - float fov = 180.0f / de::PI * 2.0f * (atan2( - info.EyeToScreenDistance, - 0.5f * (info.HScreenSize - info.InterpupillaryDistance))); - */ - return true; -#endif - return false; -} -// True if Oculus Rift is enabled and can report head orientation. -bool VR::hasHeadOrientation() -{ -#ifdef DENG_HAVE_OCULUS_API - if (oculusTracker == NULL) { - oculusTracker = new OculusTracker(); - if (oculusTracker->isGood() && autoLoadRiftParams) - loadRiftParametersNoCheck(); - } - return oculusTracker->isGood(); -#else - // No API; No head tracking. - return false; -#endif -} + /** + * @todo When old-style console variables become obsolete, VRConfig should expose + * these settings as a Record that can be attached under Config. + */ -bool VR::loadRiftParameters() { - if (! VR::hasHeadOrientation()) - return false; - return loadRiftParametersNoCheck(); -} + C_VAR_INT2 ("rend-vr-mode", &vrMode, 0, 0, VRConfig::NUM_STEREO_MODES - 1, vrModeChanged); + C_VAR_BYTE ("rend-vr-autoload-rift-params", &autoLoadRiftParams, 0, 0, 1); + C_VAR_FLOAT2("rend-vr-nonrift-fovx", &vrNonRiftFovX, 0, 5.0f, 270.0f, vrNonRiftFovXChanged); + C_VAR_FLOAT2("rend-vr-rift-fovx", &vrRiftFovX, 0, 5.0f, 270.0f, vrRiftFovXChanged); -bool VR::RiftState::loadRiftParameters() { - if (! VR::hasHeadOrientation()) - return false; -#ifdef DENG_HAVE_OCULUS_API - const OVR::HMDInfo& info = oculusTracker->getInfo(); - m_screenSize = de::Vector2f(info.HScreenSize, info.VScreenSize); - m_lensSeparationDistance = info.LensSeparationDistance; - m_hmdWarpParam = de::Vector4f( - info.DistortionK[0], - info.DistortionK[1], - info.DistortionK[2], - info.DistortionK[3]); - m_chromAbParam = de::Vector4f( - info.ChromaAbCorrection[0], - info.ChromaAbCorrection[1], - info.ChromaAbCorrection[2], - info.ChromaAbCorrection[3]); - m_eyeToScreenDistance = info.EyeToScreenDistance; - return true; -#else - return false; -#endif -} + C_VAR_FLOAT2("rend-vr-dominant-eye", &vrDominantEye, 0, -1.0f, 1.0f, vrConfigVariableChanged); + C_VAR_FLOAT2("rend-vr-hud-distance", &vrHudDistance, 0, 0.01f, 40.0f, vrConfigVariableChanged); + C_VAR_FLOAT2("rend-vr-ipd", &vrIpd, 0, 0.02f, 0.1f, vrConfigVariableChanged); + C_VAR_FLOAT2("rend-vr-player-height", &vrPlayerHeight, 0, 1.0f, 2.4f, vrConfigVariableChanged); + C_VAR_FLOAT2("rend-vr-rift-latency", &vrRiftLatency, 0, 0.0f, 0.100f, vrConfigVariableChanged); + C_VAR_INT2 ("rend-vr-rift-samples", &vrRiftFBSamples, 0, 1, 4, vrConfigVariableChanged); + C_VAR_BYTE2 ("rend-vr-swap-eyes", &vrSwapEyes, 0, 0, 1, vrConfigVariableChanged); -void VR::setRiftLatency(float latency) -{ -#ifdef DENG_HAVE_OCULUS_API - if (! VR::hasHeadOrientation()) - return; - oculusTracker->setLatency(latency); -#endif + C_CMD("loadriftparams", NULL, LoadRiftParams); } -static bool headOrientationUpdateIsAllowed = true; +// Warping -void VR::allowHeadOrientationUpdate() -{ - headOrientationUpdateIsAllowed = true; -} +/// @todo warping -void VR::updateHeadOrientation() +bool VR_LoadRiftParameters() { -#ifdef DENG_HAVE_OCULUS_API - if(headOrientationUpdateIsAllowed && VR::hasHeadOrientation()) - { - oculusTracker->update(); - headOrientationUpdateIsAllowed = false; - } -#endif -} + de::OculusRift &ovr = vrCfg().oculusRift(); -de::Vector3f VR::getHeadOrientation() -{ - de::Vector3f result; -#ifdef DENG_HAVE_OCULUS_API - if(VR::hasHeadOrientation()) + if(ovr.isReady()) { - result[0] = oculusTracker->pitch; - result[1] = oculusTracker->roll; - result[2] = oculusTracker->yaw; - } -#endif - return result; -} + Con_SetFloat("rend-vr-ipd", ovr.interpupillaryDistance()); // from Oculus SDK + Con_SetFloat("rend-vr-rift-fovx", ovr.fovX()); -// To release memory and resources when done, for tidiness. -void VR::deleteOculusTracker() -{ -#ifdef DENG_HAVE_OCULUS_API - if (oculusTracker != NULL) - { - delete oculusTracker; - oculusTracker = NULL; + // I think this field of view is unreliable... CMB + /* + float fov = 180.0f / de::PI * 2.0f * (atan2( + info.EyeToScreenDistance, + 0.5f * (info.HScreenSize - info.InterpupillaryDistance))); + */ + return true; } -#endif + return false; } diff --git a/doomsday/client/src/ui/clientwindow.cpp b/doomsday/client/src/ui/clientwindow.cpp index a9f42c2735..a1601bcf32 100644 --- a/doomsday/client/src/ui/clientwindow.cpp +++ b/doomsday/client/src/ui/clientwindow.cpp @@ -27,16 +27,17 @@ #include "ui/clientwindow.h" #include "ui/clientrootwidget.h" #include "clientapp.h" +#include #include #include #include -#include #include #include #include #include #include #include +#include #include #include "gl/sys_opengl.h" @@ -54,18 +55,17 @@ #include "dd_main.h" #include "con_main.h" -#include "ui/vrwindowtransform.h" #include "render/vr.h" using namespace de; -DENG2_PIMPL(ClientWindow), -DENG2_OBSERVES(KeyEventSource, KeyEvent), -DENG2_OBSERVES(MouseEventSource, MouseStateChange), -DENG2_OBSERVES(MouseEventSource, MouseEvent), -DENG2_OBSERVES(Canvas, FocusChange), -DENG2_OBSERVES(App, GameChange), -DENG2_OBSERVES(App, StartupComplete) +DENG2_PIMPL(ClientWindow) +, DENG2_OBSERVES(KeyEventSource, KeyEvent) +, DENG2_OBSERVES(MouseEventSource, MouseStateChange) +, DENG2_OBSERVES(MouseEventSource, MouseEvent) +, DENG2_OBSERVES(Canvas, FocusChange) +, DENG2_OBSERVES(App, GameChange) +, DENG2_OBSERVES(App, StartupComplete) { bool needMainInit; bool needRecreateCanvas; @@ -123,6 +123,8 @@ DENG2_OBSERVES(App, StartupComplete) , oldFps(0) , contentXf(*i) { + self.setTransform(contentXf); + /// @todo The decision whether to receive input notifications from the /// canvas is really a concern for the input drivers. @@ -622,7 +624,7 @@ DENG2_OBSERVES(App, StartupComplete) if(!compositor) return; - if(VR::mode() == VR::MODE_OCULUS_RIFT) + if(vrCfg().mode() == VRConfig::OculusRift) { compositor->setCompositeProjection(Matrix4f::ortho(-1.1f, 2.2f, -1.1f, 2.2f)); } @@ -636,7 +638,7 @@ DENG2_OBSERVES(App, StartupComplete) void updateMouseCursor() { // The cursor is only needed if the content is warped. - cursor->show(!self.canvas().isMouseTrapped() && VR::mode() == VR::MODE_OCULUS_RIFT); + cursor->show(!self.canvas().isMouseTrapped() && vrCfg().mode() == VRConfig::OculusRift); if(cursor->isVisible()) { @@ -677,6 +679,25 @@ ClientWindow::ClientWindow(String const &id) d->setupUI(); } +Vector2f ClientWindow::windowContentSize() +{ + return Vector2f(root().viewWidth().value(), root().viewHeight().value()); +} + +Canvas &ClientWindow::windowCanvas() +{ + return canvas(); +} + +void ClientWindow::drawWindowContent() +{ + LIBGUI_ASSERT_GL_OK(); + + root().draw(); + + LIBGUI_ASSERT_GL_OK(); +} + ClientRootWidget &ClientWindow::root() { return d->root; @@ -744,7 +765,7 @@ void ClientWindow::canvasGLReady(Canvas &canvas) PersistentCanvasWindow::canvasGLReady(canvas); - if(VR::modeNeedsStereoGLFormat(VR::mode()) && !canvas.format().stereo()) + if(vrCfg().needsStereoGLFormat() && !canvas.format().stereo()) { LOG_GL_WARNING("Current VR mode needs a stereo buffer, but it isn't supported"); } @@ -827,13 +848,14 @@ bool ClientWindow::setDefaultGLFormat() // static //fmt.setStencilBufferSize(8); fmt.setDoubleBuffer(true); - if(VR::modeNeedsStereoGLFormat(VR::mode())) + if(vrCfg().needsStereoGLFormat()) { // Only use a stereo format for modes that require it. LOG_GL_MSG("Using a stereoscopic frame buffer format"); fmt.setStereo(true); } + /* if(CommandLine_Exists("-novsync") || !Con_GetByte("vid-vsync")) { fmt.setSwapInterval(0); // vsync off @@ -844,6 +866,7 @@ bool ClientWindow::setDefaultGLFormat() // static fmt.setSwapInterval(1); LOG_GL_VERBOSE("Vertical sync on"); } + */ // The value of the "vid-fsaa" variable is written to this settings // key when the value of the variable changes. @@ -879,7 +902,7 @@ void ClientWindow::draw() ClientApp::app().loop().pause(); // Offscreen composition is only needed in Oculus Rift mode. - d->enableCompositor(VR::mode() == VR::MODE_OCULUS_RIFT); + d->enableCompositor(vrCfg().mode() == VRConfig::OculusRift); if(d->performDeferredTasks() == Instance::AbortFrame) { diff --git a/doomsday/client/src/ui/dd_input.cpp b/doomsday/client/src/ui/dd_input.cpp index afe8a9f954..792a27c518 100644 --- a/doomsday/client/src/ui/dd_input.cpp +++ b/doomsday/client/src/ui/dd_input.cpp @@ -1556,8 +1556,8 @@ void DD_ReadHeadTracker(void) I_GetDevice(IDEV_HEAD_TRACKER)->flags |= ID_ACTIVE; // Get the latest values. - VR::allowHeadOrientationUpdate(); - VR::updateHeadOrientation(); + vrCfg().oculusRift().allowUpdate(); + vrCfg().oculusRift().update(); ddevent_t ev; @@ -1565,7 +1565,7 @@ void DD_ReadHeadTracker(void) ev.type = E_AXIS; ev.axis.type = EAXIS_ABSOLUTE; - Vector3f const pry = VR::getHeadOrientation(); + Vector3f const pry = vrCfg().oculusRift().headOrientation(); // Yaw (1.0 means 180 degrees). ev.axis.id = 0; // Yaw. diff --git a/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp b/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp index 2d2ecb03a8..cc8c8ff398 100644 --- a/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp @@ -50,17 +50,17 @@ DENG_GUI_PIMPL(VRSettingsDialog) area.add(mode = new CVarChoiceWidget("rend-vr-mode")); mode->items() - << new ChoiceItem(tr("No stereo"), VR::MODE_MONO) - << new ChoiceItem(tr("Anaglyph (green/magenta)"), VR::MODE_GREEN_MAGENTA) - << new ChoiceItem(tr("Anaglyph (red/cyan)"), VR::MODE_RED_CYAN) - << new ChoiceItem(tr("Left eye only"), VR::MODE_LEFT) - << new ChoiceItem(tr("Right eye only"), VR::MODE_RIGHT) - << new ChoiceItem(tr("Top/bottom"), VR::MODE_TOP_BOTTOM) - << new ChoiceItem(tr("Side-by-side"), VR::MODE_SIDE_BY_SIDE) - << new ChoiceItem(tr("Parallel"), VR::MODE_PARALLEL) - << new ChoiceItem(tr("Cross-eye"), VR::MODE_CROSSEYE) - << new ChoiceItem(tr("Oculus Rift"), VR::MODE_OCULUS_RIFT) - << new ChoiceItem(tr("Hardware stereo"), VR::MODE_QUAD_BUFFERED); + << new ChoiceItem(tr("No stereo"), VRConfig::Mono) + << new ChoiceItem(tr("Anaglyph (green/magenta)"), VRConfig::GreenMagenta) + << new ChoiceItem(tr("Anaglyph (red/cyan)"), VRConfig::RedCyan) + << new ChoiceItem(tr("Left eye only"), VRConfig::LeftOnly) + << new ChoiceItem(tr("Right eye only"), VRConfig::RightOnly) + << new ChoiceItem(tr("Top/bottom"), VRConfig::TopBottom) + << new ChoiceItem(tr("Side-by-side"), VRConfig::SideBySide) + << new ChoiceItem(tr("Parallel"), VRConfig::Parallel) + << new ChoiceItem(tr("Cross-eye"), VRConfig::CrossEye) + << new ChoiceItem(tr("Oculus Rift"), VRConfig::OculusRift) + << new ChoiceItem(tr("Hardware stereo"), VRConfig::QuadBuffered); area.add(swapEyes = new CVarToggleWidget("rend-vr-swap-eyes", tr("Swap Eyes"))); area.add(dominantEye = new CVarSliderWidget("rend-vr-dominant-eye")); @@ -70,7 +70,7 @@ DENG_GUI_PIMPL(VRSettingsDialog) area.add(ipd = new CVarSliderWidget("rend-vr-ipd")); ipd->setDisplayFactor(1000); - if(VR::hasHeadOrientation()) + if(vrCfg().oculusRift().isReady()) { area.add(riftPredictionLatency = new CVarSliderWidget("rend-vr-rift-latency")); riftPredictionLatency->setDisplayFactor(1000); @@ -121,7 +121,7 @@ VRSettingsDialog::VRSettingsDialog(String const &name) << Const(0) << *d->swapEyes << *sampleLabel << *d->riftSamples; - if(VR::hasHeadOrientation()) + if(vrCfg().oculusRift().isReady()) { LabelWidget *ovrLabel = LabelWidget::newWithText(_E(1)_E(D) + tr("Oculus Rift"), &area()); LabelWidget *latencyLabel = LabelWidget::newWithText(tr("Prediction Latency:"), &area()); @@ -129,7 +129,7 @@ VRSettingsDialog::VRSettingsDialog(String const &name) ovrLabel->margins().setTop("gap"); - layout.setCellAlignment(Vector2i(0, 5), ui::AlignLeft); + layout.setCellAlignment(Vector2i(0, 6), ui::AlignLeft); layout.append(*ovrLabel, 2); layout << *latencyLabel << *d->riftPredictionLatency @@ -149,7 +149,7 @@ VRSettingsDialog::VRSettingsDialog(String const &name) void VRSettingsDialog::resetToDefaults() { - Con_SetInteger("rend-vr-mode", VR::MODE_MONO); + Con_SetInteger("rend-vr-mode", VRConfig::Mono); Con_SetInteger("rend-vr-swap-eyes", 0); Con_SetFloat ("rend-vr-dominant-eye", 0); Con_SetFloat ("rend-vr-player-height", 1.75f); @@ -168,7 +168,7 @@ void VRSettingsDialog::autoConfigForOculusRift() /// @todo This would be a good use case for cvar overriding. -jk - Con_SetInteger("rend-vr-mode", VR::MODE_OCULUS_RIFT); + Con_SetInteger("rend-vr-mode", VRConfig::OculusRift); Con_SetInteger("vid-fsaa", 0); Con_SetFloat ("vid-gamma", 1.176f); Con_SetFloat ("vid-contrast", 1.186f); @@ -182,7 +182,7 @@ void VRSettingsDialog::autoConfigForOculusRift() void VRSettingsDialog::autoConfigForDesktop() { - Con_SetInteger("rend-vr-mode", VR::MODE_MONO); + Con_SetInteger("rend-vr-mode", VRConfig::Mono); Con_SetFloat ("vid-gamma", 1); Con_SetFloat ("vid-contrast", 1); Con_SetFloat ("vid-bright", 0); diff --git a/doomsday/client/src/ui/vrwindowtransform.cpp b/doomsday/client/src/ui/vrwindowtransform.cpp deleted file mode 100644 index cc8d403fbd..0000000000 --- a/doomsday/client/src/ui/vrwindowtransform.cpp +++ /dev/null @@ -1,430 +0,0 @@ -/** @file vrwindowtransform.cpp Window content transformation for virtual reality. - * - * @authors Copyright (c) 2013 Christopher Bruns - * @authors Copyright (c) 2013 Jaakko Keränen - * - * @par License - * GPL: http://www.gnu.org/licenses/gpl.html - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. This program is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. You should have received a copy of the GNU - * General Public License along with this program; if not, see: - * http://www.gnu.org/licenses - */ - -#include "ui/vrwindowtransform.h" -#include "de_platform.h" -#include "con_main.h" -#include "render/vr.h" - -#include -#include - -using namespace de; - -DENG2_PIMPL(VRWindowTransform) -{ - Drawable oculusRift; - GLUniform uOculusRiftFB; - GLUniform uOculusDistortionScale; - GLUniform uOculusScreenSize; - GLUniform uOculusLensSeparation; - GLUniform uOculusHmdWarpParam; - GLUniform uOculusChromAbParam; - - typedef GLBufferT OculusRiftVBuf; - GLFramebuffer unwarpedFB; - - Instance(Public *i) - : Base(i), - uOculusRiftFB("texture", GLUniform::Sampler2D), - uOculusDistortionScale("distortionScale", GLUniform::Float), - uOculusScreenSize("screenSize", GLUniform::Vec2), - uOculusLensSeparation("lensSeparationDistance", GLUniform::Float), - uOculusHmdWarpParam("hmdWarpParam", GLUniform::Vec4), - uOculusChromAbParam("chromAbParam", GLUniform::Vec4) - {} - - void init() - { - /// @todo Only do this when Oculus Rift mode is enabled. - /// Free the allocated resources when non-Rift mode in use. - - OculusRiftVBuf *buf = new OculusRiftVBuf; - oculusRift.addBuffer(buf); - - // Set up a simple static quad. - OculusRiftVBuf::Type const verts[4] = { - { Vector3f(-1, 1, 0.5f), Vector2f(0, 1), }, - { Vector3f( 1, 1, 0.5f), Vector2f(1, 1), }, - { Vector3f(-1, -1, 0.5f), Vector2f(0, 0), }, - { Vector3f( 1, -1, 0.5f), Vector2f(1, 0), } - }; - buf->setVertices(gl::TriangleStrip, verts, 4, gl::Static); - - self.window().root().shaders() - .build(oculusRift.program(), "vr.oculusrift.barrel") - << uOculusRiftFB - << uOculusDistortionScale - << uOculusScreenSize - << uOculusLensSeparation - << uOculusHmdWarpParam - << uOculusChromAbParam; - - unwarpedFB.glInit(); - uOculusRiftFB = unwarpedFB.colorTexture(); - } - - void deinit() - { - oculusRift.clear(); - unwarpedFB.glDeinit(); - } - - Canvas &canvas() const - { - return self.window().canvas(); - } - - GLTarget &target() const - { - return canvas().renderTarget(); - } - - int width() const - { - return canvas().width(); - } - - int height() const - { - return canvas().height(); - } - - void drawContent() const - { - self.window().root().draw(); - } - - /** - * Draws the entire UI in two halves, one for the left eye and one for the - * right. The Oculus Rift optical distortion effect is applied using a - * shader. - * - * @todo unwarpedTarget and unwarpedTexture should be cleared/deleted when - * Oculus Rift mode is disabled (or whenever they are not needed). - */ - void vrDrawOculusRift() - { - VR::applyFrustumShift = false; - - /// @todo shrunken hud - // Allocate offscreen buffers - larger than Oculus Rift size, to get adequate resolution at center after warp - // For some reason, 1.5X looks best, even though objects are ~2.3X unwarped size at center. - float unwarpFactor = 1.5f; - Canvas::Size textureSize = Canvas::Size(1280, 800) * unwarpFactor; - // Canvas::Size textureSize(2560, 1600); // 2 * 1280x800 // Undesirable relative softness at very center of image - // Canvas::Size textureSize(3200, 2000); // 2.5 * 1280x800 // Softness here too - unwarpedFB.resize(textureSize); - - // Use a little bit of multisampling to smooth out the magnified jagged edges. - // Note: Independent of the vid-fsaa setting because this is beneficial even when - // vid-fsaa is disabled. - unwarpedFB.setSampleCount(VR::riftFramebufferSamples); - unwarpedFB.colorTexture().setFilter(gl::Linear, gl::Linear, gl::MipNone); - - // Set render target to offscreen temporarily. - GLState::push() - .setTarget(unwarpedFB.target()) - .setViewport(Rectangleui::fromSize(unwarpedFB.size())) - .apply(); - unwarpedFB.target().unsetActiveRect(true); - unwarpedFB.target().clear(GLTarget::ColorDepth); - - // Left eye view on left side of screen. - VR::eyeShift = VR::getEyeShift(-1); - unwarpedFB.target().setActiveRect(Rectangleui(0, 0, textureSize.x/2, textureSize.y), true); - drawContent(); - - // Right eye view on right side of screen. - VR::eyeShift = VR::getEyeShift(+1); - unwarpedFB.target().setActiveRect(Rectangleui(textureSize.x/2, 0, textureSize.x/2, textureSize.y), true); - drawContent(); - - unwarpedFB.target().unsetActiveRect(true); - - GLState::pop().apply(); - - // Necessary until the legacy code uses GLState, too: - glEnable(GL_TEXTURE_2D); - - target().clear(GLTarget::Color); - GLState::push() - .setDepthTest(false); - - // Copy contents of offscreen buffer to normal screen. - uOculusDistortionScale = VR::riftState.distortionScale(); - uOculusScreenSize = VR::riftState.screenSize(); - uOculusLensSeparation = VR::riftState.lensSeparationDistance(); - uOculusHmdWarpParam = VR::riftState.hmdWarpParam(); - uOculusChromAbParam = VR::riftState.chromAbParam(); - // - oculusRift.draw(); - - glBindTexture(GL_TEXTURE_2D, 0); - glDepthMask(GL_TRUE); - - GLState::pop().apply(); - - VR::applyFrustumShift = true; // restore default - } -}; - -VRWindowTransform::VRWindowTransform(ClientWindow &window) - : WindowTransform(window), d(new Instance(this)) -{} - -void VRWindowTransform::glInit() -{ - d->init(); -} - -void VRWindowTransform::glDeinit() -{ - d->deinit(); -} - -Vector2ui VRWindowTransform::logicalRootSize(Vector2ui const &physicalCanvasSize) const -{ - Canvas::Size size = physicalCanvasSize; - - switch(VR::mode()) - { - // Left-right screen split modes - case VR::MODE_CROSSEYE: - case VR::MODE_PARALLEL: - // Adjust effective UI size for stereoscopic rendering. - size.y *= 2; - size *= .75f; // Make it a bit bigger. - break; - - case VR::MODE_OCULUS_RIFT: - /// @todo - taskbar needs to elevate above bottom of screen in Rift mode - // Adjust effective UI size for stereoscopic rendering. - size.x = size.y * VR::riftState.aspect(); - size *= 1.0f; // Use a large font in taskbar - break; - - // Allow UI to squish in top/bottom and SBS mode: 3D hardware will unsquish them - case VR::MODE_TOP_BOTTOM: - case VR::MODE_SIDE_BY_SIDE: - default: - break; - } - - return size; -} - -Vector2f VRWindowTransform::windowToLogicalCoords(Vector2i const &winPos) const -{ - // We need to map the real window coordinates to logical root view - // coordinates according to the used transformation. - - Vector2f pos = winPos; - - Vector2f const size = window().canvas().size(); - Vector2f const viewSize = Vector2f(window().root().viewWidth().value(), - window().root().viewHeight().value()); - - switch(VR::mode()) - { - // Left-right screen split modes - case VR::MODE_SIDE_BY_SIDE: - case VR::MODE_CROSSEYE: - case VR::MODE_PARALLEL: - case VR::MODE_OCULUS_RIFT: - // Make it possible to access both frames. - if(pos.x >= size.x/2) - { - pos.x -= size.x/2; - } - pos.x *= 2; - - // Scale to logical size. - pos = pos / size * viewSize; - break; - - // Top-bottom screen split modes - case VR::MODE_TOP_BOTTOM: - // Make it possible to access both frames. - if(pos.y >= size.y/2) - { - pos.y -= size.y/2; - } - pos.y *= 2; - - // Scale to logical size. - pos = pos / size * viewSize; - break; - - default: - // Not transformed. - break; - } - - return pos; -} - -void VRWindowTransform::drawTransformed() -{ - VR::allowHeadOrientationUpdate(); - - switch(VR::mode()) - { - // A) Single view type stereo 3D modes here: - case VR::MODE_MONO: - // Non-stereoscopic frame. - d->drawContent(); - break; - - case VR::MODE_LEFT: - // Left eye view - VR::eyeShift = VR::getEyeShift(-1); - d->drawContent(); - break; - - case VR::MODE_RIGHT: - // Right eye view - VR::eyeShift = VR::getEyeShift(+1); - d->drawContent(); - break; - - // B) Split-screen type stereo 3D modes here: - case VR::MODE_TOP_BOTTOM: // Left goes on top - // Left eye view on top of screen. - VR::eyeShift = VR::getEyeShift(-1); - d->target().setActiveRect(Rectangleui(0, 0, d->width(), d->height()/2), true); - d->drawContent(); - // Right eye view on bottom of screen. - VR::eyeShift = VR::getEyeShift(+1); - d->target().setActiveRect(Rectangleui(0, d->height()/2, d->width(), d->height()/2), true); - d->drawContent(); - break; - - case VR::MODE_SIDE_BY_SIDE: // Squished aspect - // Left eye view on left side of screen. - VR::eyeShift = VR::getEyeShift(-1); - d->target().setActiveRect(Rectangleui(0, 0, d->width()/2, d->height()), true); - d->drawContent(); - // Right eye view on right side of screen. - VR::eyeShift = VR::getEyeShift(+1); - d->target().setActiveRect(Rectangleui(d->width()/2, 0, d->width()/2, d->height()), true); - d->drawContent(); - break; - - case VR::MODE_PARALLEL: // Normal aspect - // Left eye view on left side of screen. - VR::eyeShift = VR::getEyeShift(-1); - d->target().setActiveRect(Rectangleui(0, 0, d->width()/2, d->height()), true); - d->drawContent(); - // Right eye view on right side of screen. - VR::eyeShift = VR::getEyeShift(+1); - d->target().setActiveRect(Rectangleui(d->width()/2, 0, d->width()/2, d->height()), true); - d->drawContent(); - break; - - case VR::MODE_CROSSEYE: // Normal aspect - // Right eye view on left side of screen. - VR::eyeShift = VR::getEyeShift(+1); - d->target().setActiveRect(Rectangleui(0, 0, d->width()/2, d->height()), true); - d->drawContent(); - // Left eye view on right side of screen. - VR::eyeShift = VR::getEyeShift(-1); - d->target().setActiveRect(Rectangleui(d->width()/2, 0, d->width()/2, d->height()), true); - d->drawContent(); - break; - - case VR::MODE_OCULUS_RIFT: - d->vrDrawOculusRift(); - break; - - // Overlaid type stereo 3D modes below: - case VR::MODE_GREEN_MAGENTA: - // Left eye view - VR::eyeShift = VR::getEyeShift(-1); - GLState::push().setColorMask(gl::WriteGreen | gl::WriteAlpha).apply(); // Left eye view green - d->drawContent(); - // Right eye view - VR::eyeShift = VR::getEyeShift(+1); - GLState::current().setColorMask(gl::WriteRed | gl::WriteBlue | gl::WriteAlpha).apply(); // Right eye view magenta - d->drawContent(); - GLState::pop().apply(); - break; - - case VR::MODE_RED_CYAN: - // Left eye view - VR::eyeShift = VR::getEyeShift(-1); - GLState::push().setColorMask(gl::WriteRed | gl::WriteAlpha).apply(); // Left eye view red - d->drawContent(); - // Right eye view - VR::eyeShift = VR::getEyeShift(+1); - GLState::current().setColorMask(gl::WriteGreen | gl::WriteBlue | gl::WriteAlpha).apply(); // Right eye view cyan - d->drawContent(); - GLState::pop().apply(); - break; - - case VR::MODE_QUAD_BUFFERED: - if(d->canvas().format().stereo()) - { - // Left eye view - VR::eyeShift = VR::getEyeShift(-1); - d->drawContent(); - d->canvas().framebuffer().swapBuffers(d->canvas(), gl::SwapStereoLeftBuffer); - - // Right eye view - VR::eyeShift = VR::getEyeShift(+1); - d->drawContent(); - d->canvas().framebuffer().swapBuffers(d->canvas(), gl::SwapStereoRightBuffer); - } - else - { - // Normal non-stereoscopic frame. - d->drawContent(); - } - break; - - case VR::MODE_ROW_INTERLEAVED: - { - // Use absolute screen position of window to determine whether the - // first scan line is odd or even. - QPoint ulCorner(0, 0); - ulCorner = d->canvas().mapToGlobal(ulCorner); // widget to screen coordinates - bool rowParityIsEven = ((ulCorner.x() % 2) == 0); - DENG_UNUSED(rowParityIsEven); - /// @todo - use row parity in shader or stencil, to actually interleave rows. - // Left eye view - VR::eyeShift = VR::getEyeShift(-1); - d->drawContent(); - // Right eye view - VR::eyeShift = VR::getEyeShift(+1); - d->drawContent(); - break; - } - - case VR::MODE_COLUMN_INTERLEAVED: /// @todo implement column interleaved stereo 3D after row intleaved is working correctly... - case VR::MODE_CHECKERBOARD: /// @todo implement checker stereo 3D after row intleaved is working correctly ... - default: - // Non-stereoscopic frame. - d->drawContent(); - break; - } - - // Restore default VR dynamic parameters - d->target().unsetActiveRect(true); - VR::eyeShift = 0; -} diff --git a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp index d672b918a9..b49092cff8 100644 --- a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp +++ b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp @@ -49,6 +49,10 @@ DENG_GUI_PIMPL(GameSelectionWidget) : ui::ActionItem(label, action), game(gameRef) { setData(&gameRef); } + String sortKey() const { + // Sort by identity key. + return game.identityKey(); + } Game const &game; }; diff --git a/doomsday/client/src/ui/widgets/gamesessionwidget.cpp b/doomsday/client/src/ui/widgets/gamesessionwidget.cpp index 8ea413fa0e..5e74ed84f7 100644 --- a/doomsday/client/src/ui/widgets/gamesessionwidget.cpp +++ b/doomsday/client/src/ui/widgets/gamesessionwidget.cpp @@ -80,7 +80,8 @@ DENG2_PIMPL(GameSessionWidget) GameSessionWidget::GameSessionWidget() : d(new Instance(this)) { - rule().setInput(Rule::Height, style().fonts().font("default").height() * 4); + Font const &font = style().fonts().font("default"); + rule().setInput(Rule::Height, font.lineSpacing() * 3 + font.height() + margins().height()); // Button for extra information. d->load->rule() diff --git a/doomsday/dep_rift.pri b/doomsday/dep_rift.pri index 71f1d95e85..d511daa6e1 100644 --- a/doomsday/dep_rift.pri +++ b/doomsday/dep_rift.pri @@ -9,10 +9,10 @@ exists($${LIBOVR_DIR}/Include/OVR.h) { LIBS += shell32.lib winmm.lib } macx { - # ACK! Must rebuild libovr with RTTI (TODO) deng_debug: LIBS += $${LIBOVR_DIR}/Lib/MacOS/Debug/libovr.a else: LIBS += $${LIBOVR_DIR}/Lib/MacOS/Release/libovr.a - LIBS += -framework IOKit + useFramework(Cocoa) + useFramework(IOKit) } # For linux, you need to install libxinerama-dev and libudev-dev linux-g++|linux-g++-32 { diff --git a/doomsday/libappfw/include/de/BaseWindow b/doomsday/libappfw/include/de/BaseWindow new file mode 100644 index 0000000000..f1d64ce2ad --- /dev/null +++ b/doomsday/libappfw/include/de/BaseWindow @@ -0,0 +1,2 @@ +#include "framework/basewindow.h" + diff --git a/doomsday/libappfw/include/de/OculusRift b/doomsday/libappfw/include/de/OculusRift new file mode 100644 index 0000000000..10291c7525 --- /dev/null +++ b/doomsday/libappfw/include/de/OculusRift @@ -0,0 +1,2 @@ +#include "vr/oculusrift.h" + diff --git a/doomsday/libappfw/include/de/VRConfig b/doomsday/libappfw/include/de/VRConfig new file mode 100644 index 0000000000..2edc1f263f --- /dev/null +++ b/doomsday/libappfw/include/de/VRConfig @@ -0,0 +1,2 @@ +#include "vr/vrconfig.h" + diff --git a/doomsday/libappfw/include/de/VRWindowTransform b/doomsday/libappfw/include/de/VRWindowTransform new file mode 100644 index 0000000000..4abd895a45 --- /dev/null +++ b/doomsday/libappfw/include/de/VRWindowTransform @@ -0,0 +1,2 @@ +#include "framework/vrwindowtransform.h" + diff --git a/doomsday/libappfw/include/de/WindowTransform b/doomsday/libappfw/include/de/WindowTransform new file mode 100644 index 0000000000..a109cccba2 --- /dev/null +++ b/doomsday/libappfw/include/de/WindowTransform @@ -0,0 +1,2 @@ +#include "framework/windowtransform.h" + diff --git a/doomsday/libappfw/include/de/framework/atlasproceduralimage.h b/doomsday/libappfw/include/de/framework/atlasproceduralimage.h index fc27545677..3b58fccbee 100644 --- a/doomsday/libappfw/include/de/framework/atlasproceduralimage.h +++ b/doomsday/libappfw/include/de/framework/atlasproceduralimage.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_ATLASPROCEDURALIMAGE_H #define LIBAPPFW_ATLASPROCEDURALIMAGE_H -#include "libappfw.h" +#include "../libappfw.h" #include "../ProceduralImage" #include "../GuiWidget" #include "../GuiRootWidget" diff --git a/doomsday/libappfw/include/de/framework/baseguiapp.h b/doomsday/libappfw/include/de/framework/baseguiapp.h index 489488747e..e8b4970bac 100644 --- a/doomsday/libappfw/include/de/framework/baseguiapp.h +++ b/doomsday/libappfw/include/de/framework/baseguiapp.h @@ -19,13 +19,20 @@ #ifndef LIBAPPFW_BASEGUIAPP_H #define LIBAPPFW_BASEGUIAPP_H -#include "libappfw.h" +#include "../libappfw.h" #include #include +/** + * Macro for conveniently accessing the de::BaseGuiApp singleton instance. + */ +#define DENG2_BASE_GUI_APP (static_cast(qApp)) + namespace de { +class VRConfig; + /** * Base class for GUI applications. * @@ -39,6 +46,7 @@ class LIBAPPFW_PUBLIC BaseGuiApp : public GuiApp public: static BaseGuiApp &app(); static GLShaderBank &shaders(); + static VRConfig &vr(); private: DENG2_PRIVATE(d) diff --git a/doomsday/libappfw/include/de/framework/basewindow.h b/doomsday/libappfw/include/de/framework/basewindow.h new file mode 100644 index 0000000000..9fafffadb5 --- /dev/null +++ b/doomsday/libappfw/include/de/framework/basewindow.h @@ -0,0 +1,89 @@ +/** @file basewindow.h Abstract base class for application windows. + * + * @authors Copyright (c) 2014 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBAPPFW_BASEWINDOW_H +#define LIBAPPFW_BASEWINDOW_H + +#include "../libappfw.h" + +#include +#include + +namespace de { + +class WindowTransform; + +/** + * Abstract base class for application windows. + * + * All windows must have a Canvas where the contents of the window are drawn. Windows may + * additionally specify a content transformation using a WindowTransform object, which + * will override the built-in transformation. The built-in transformation specifies an + * "identity" transformation that doesn't differ from the logical layout. + */ +class BaseWindow +{ +public: + BaseWindow(); + + /** + * Sets a new content transformation being applied in the window. The provided + * object must remain in existence as long as the BaseWindow instance uses it. + * + * @param xf Content transformaton. + */ + void setTransform(WindowTransform &xf); + + /** + * Changes the window transformation to the default one that applies no actual + * transformation. + */ + void useDefaultTransform(); + + /** + * Returns the current content transformation being applied to the content of the + * window. + */ + WindowTransform &transform(); + + /** + * Returns the logical size of the window contents (e.g., root widget). + */ + virtual Vector2f windowContentSize() = 0; + + /** + * Returns the Canvas that represents the visible contents of the window. + */ + virtual Canvas& windowCanvas() = 0; + + /** + * Causes the contents of the window to be drawn. The contents are drawn immediately + * and the method does not return until everything has been drawn. The method should + * draw an entire frame using the non-transformed logical size of the view. + */ + virtual void drawWindowContent() = 0; + + DENG2_AS_IS_METHODS() + +private: + DENG2_PRIVATE(d) +}; + +} // namespace de + +#endif // LIBAPPFW_BASEWINDOW_H diff --git a/doomsday/libappfw/include/de/framework/childwidgetorganizer.h b/doomsday/libappfw/include/de/framework/childwidgetorganizer.h index 344e1dcff0..e08335626f 100644 --- a/doomsday/libappfw/include/de/framework/childwidgetorganizer.h +++ b/doomsday/libappfw/include/de/framework/childwidgetorganizer.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_CHILDWIDGETORGANIZER_H #define LIBAPPFW_CHILDWIDGETORGANIZER_H -#include "libappfw.h" +#include "../libappfw.h" #include "../ui/Data" #include "../GuiWidget" diff --git a/doomsday/libappfw/include/de/framework/dialogcontentstylist.h b/doomsday/libappfw/include/de/framework/dialogcontentstylist.h index 3ce5ba954c..066e9270f1 100644 --- a/doomsday/libappfw/include/de/framework/dialogcontentstylist.h +++ b/doomsday/libappfw/include/de/framework/dialogcontentstylist.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_DIALOGCONTENTSTYLIST_H #define LIBAPPFW_DIALOGCONTENTSTYLIST_H -#include "libappfw.h" +#include "../libappfw.h" #include "../ui/Stylist" #include diff --git a/doomsday/libappfw/include/de/framework/fontlinewrapping.h b/doomsday/libappfw/include/de/framework/fontlinewrapping.h index baa04c1bd8..b0271f3114 100644 --- a/doomsday/libappfw/include/de/framework/fontlinewrapping.h +++ b/doomsday/libappfw/include/de/framework/fontlinewrapping.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_FONTLINEWRAPPING_H #define LIBAPPFW_FONTLINEWRAPPING_H -#include "libappfw.h" +#include "../libappfw.h" #include #include diff --git a/doomsday/libappfw/include/de/framework/guirootwidget.h b/doomsday/libappfw/include/de/framework/guirootwidget.h index c841cef355..de4b21c794 100644 --- a/doomsday/libappfw/include/de/framework/guirootwidget.h +++ b/doomsday/libappfw/include/de/framework/guirootwidget.h @@ -26,7 +26,7 @@ #include #include -#include "libappfw.h" +#include "../libappfw.h" namespace de { diff --git a/doomsday/libappfw/include/de/framework/item.h b/doomsday/libappfw/include/de/framework/item.h index 85b59be417..6994c679f2 100644 --- a/doomsday/libappfw/include/de/framework/item.h +++ b/doomsday/libappfw/include/de/framework/item.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_UI_DATAITEM_H #define LIBAPPFW_UI_DATAITEM_H -#include "libappfw.h" +#include "../libappfw.h" #include #include diff --git a/doomsday/libappfw/include/de/framework/proceduralimage.h b/doomsday/libappfw/include/de/framework/proceduralimage.h index 744222cda5..c695b682aa 100644 --- a/doomsday/libappfw/include/de/framework/proceduralimage.h +++ b/doomsday/libappfw/include/de/framework/proceduralimage.h @@ -22,7 +22,7 @@ #include #include -#include "libappfw.h" +#include "../libappfw.h" namespace de { diff --git a/doomsday/libappfw/include/de/framework/signalaction.h b/doomsday/libappfw/include/de/framework/signalaction.h index 41790d94a6..147766ea9b 100644 --- a/doomsday/libappfw/include/de/framework/signalaction.h +++ b/doomsday/libappfw/include/de/framework/signalaction.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_SIGNALACTION_H #define LIBAPPFW_SIGNALACTION_H -#include "libappfw.h" +#include "../libappfw.h" #include #include diff --git a/doomsday/libappfw/include/de/framework/style.h b/doomsday/libappfw/include/de/framework/style.h index 13ebc70555..3e4f2ad4b6 100644 --- a/doomsday/libappfw/include/de/framework/style.h +++ b/doomsday/libappfw/include/de/framework/style.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_STYLE_H #define LIBAPPFW_STYLE_H -#include "libappfw.h" +#include "../libappfw.h" #include #include #include diff --git a/doomsday/libappfw/include/de/framework/stylist.h b/doomsday/libappfw/include/de/framework/stylist.h index c464cbc7db..a9c43714b2 100644 --- a/doomsday/libappfw/include/de/framework/stylist.h +++ b/doomsday/libappfw/include/de/framework/stylist.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_UI_STYLIST_H #define LIBAPPFW_UI_STYLIST_H -#include "libappfw.h" +#include "../libappfw.h" namespace de { diff --git a/doomsday/client/include/ui/vrwindowtransform.h b/doomsday/libappfw/include/de/framework/vrwindowtransform.h similarity index 76% rename from doomsday/client/include/ui/vrwindowtransform.h rename to doomsday/libappfw/include/de/framework/vrwindowtransform.h index f44e8cae55..303d02fd34 100644 --- a/doomsday/client/include/ui/vrwindowtransform.h +++ b/doomsday/libappfw/include/de/framework/vrwindowtransform.h @@ -17,10 +17,12 @@ * http://www.gnu.org/licenses */ -#ifndef DENG_CLIENT_UI_VRWINDOWTRANSFORM_H -#define DENG_CLIENT_UI_VRWINDOWTRANSFORM_H +#ifndef LIBAPPFW_VRWINDOWTRANSFORM_H +#define LIBAPPFW_VRWINDOWTRANSFORM_H -#include "windowtransform.h" +#include "../WindowTransform" + +namespace de { /** * Window content transformation for virtual reality. @@ -28,13 +30,13 @@ class VRWindowTransform : public WindowTransform { public: - VRWindowTransform(ClientWindow &window); + VRWindowTransform(BaseWindow &window); void glInit(); void glDeinit(); - de::Vector2ui logicalRootSize(de::Vector2ui const &physicalCanvasSize) const; - de::Vector2f windowToLogicalCoords(de::Vector2i const &pos) const; + Vector2ui logicalRootSize(Vector2ui const &physicalCanvasSize) const; + Vector2f windowToLogicalCoords(Vector2i const &pos) const; void drawTransformed(); @@ -42,4 +44,6 @@ class VRWindowTransform : public WindowTransform DENG2_PRIVATE(d) }; -#endif // DENG_CLIENT_UI_VRWINDOWTRANSFORM_H +} // namespace de + +#endif // LIBAPPFW_VRWINDOWTRANSFORM_H diff --git a/doomsday/client/include/ui/windowtransform.h b/doomsday/libappfw/include/de/framework/windowtransform.h similarity index 76% rename from doomsday/client/include/ui/windowtransform.h rename to doomsday/libappfw/include/de/framework/windowtransform.h index 473e324702..3596c36c6e 100644 --- a/doomsday/client/include/ui/windowtransform.h +++ b/doomsday/libappfw/include/de/framework/windowtransform.h @@ -16,12 +16,14 @@ * http://www.gnu.org/licenses */ -#ifndef DENG_CLIENT_UI_WINDOWTRANSFORM_H -#define DENG_CLIENT_UI_WINDOWTRANSFORM_H +#ifndef LIBAPPFW_WINDOWTRANSFORM_H +#define LIBAPPFW_WINDOWTRANSFORM_H #include -class ClientWindow; +namespace de { + +class BaseWindow; /** * Base class for window content transformation. @@ -29,9 +31,9 @@ class ClientWindow; class WindowTransform { public: - WindowTransform(ClientWindow &window); + WindowTransform(BaseWindow &window); - ClientWindow &window() const; + BaseWindow &window() const; /** * Called by the window when GL is ready. @@ -47,7 +49,7 @@ class WindowTransform * * @return Logical size (UI units). */ - virtual de::Vector2ui logicalRootSize(de::Vector2ui const &physicalCanvasSize) const; + virtual Vector2ui logicalRootSize(Vector2ui const &physicalCanvasSize) const; /** * Translate a point in physical window coordinates to logical coordinates. @@ -56,12 +58,18 @@ class WindowTransform * * @return Logical coordinates inside the root widget's area. */ - virtual de::Vector2f windowToLogicalCoords(de::Vector2i const &pos) const; + virtual Vector2f windowToLogicalCoords(Vector2i const &pos) const; + /** + * Applies the appropriate transformation state and tells the window to draw its + * contents. + */ virtual void drawTransformed(); private: DENG2_PRIVATE(d) }; -#endif // DENG_CLIENT_UI_WINDOWTRANSFORM_H +} // namespace de + +#endif // LIBAPPFW_WINDOWTRANSFORM_H diff --git a/doomsday/libappfw/include/de/framework/libappfw.h b/doomsday/libappfw/include/de/libappfw.h similarity index 100% rename from doomsday/libappfw/include/de/framework/libappfw.h rename to doomsday/libappfw/include/de/libappfw.h diff --git a/doomsday/libappfw/include/de/ui/defs.h b/doomsday/libappfw/include/de/ui/defs.h index 875125ae9b..3bf3587018 100644 --- a/doomsday/libappfw/include/de/ui/defs.h +++ b/doomsday/libappfw/include/de/ui/defs.h @@ -19,7 +19,7 @@ #ifndef LIBAPPFW_UI_DEFS_H #define LIBAPPFW_UI_DEFS_H -#include "../framework/libappfw.h" +#include "../libappfw.h" #include #include diff --git a/doomsday/libappfw/include/de/vr/oculusrift.h b/doomsday/libappfw/include/de/vr/oculusrift.h new file mode 100644 index 0000000000..1d17907743 --- /dev/null +++ b/doomsday/libappfw/include/de/vr/oculusrift.h @@ -0,0 +1,80 @@ +/** @file oculusrift.h + * + * @authors Copyright (c) 2014 Jaakko Keränen + * @authors Copyright (c) 2013 Christopher Bruns + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBAPPFW_OCULUSRIFT_H +#define LIBAPPFW_OCULUSRIFT_H + +#include "../libappfw.h" +#include + +namespace de { + +/** + * Oculus Rift configuration and head tracking. + */ +class LIBAPPFW_PUBLIC OculusRift +{ +public: + OculusRift(); + + bool init(); + + void deinit(); + + /** + * Checks if Oculus Rift is enabled and can report head orientation. + */ + bool isReady() const; + + void setPredictionLatency(float latency); + + // Called to allow head orientation to change again. + void allowUpdate(); + + void update(); + + // Returns current pitch, roll, yaw angles, in radians. If no head tracking is available, + // the returned values are not valid. + Vector3f headOrientation() const; + + float predictionLatency() const; + + /** + * Returns the IPD configured in the Oculus Rift preferences. + */ + float interpupillaryDistance() const; + + // Use screen size instead of resolution in case non-square pixels? + float aspect() const; + + Vector2f screenSize() const; + Vector4f chromAbParam() const; + float distortionScale() const; + float fovX() const; // in degrees + float fovY() const; // in degrees + Vector4f hmdWarpParam() const; + float lensSeparationDistance() const; + +private: + DENG2_PRIVATE(d) +}; + +} // namespace de + +#endif // LIBAPPFW_OCULUSRIFT_H diff --git a/doomsday/libappfw/include/de/vr/vrconfig.h b/doomsday/libappfw/include/de/vr/vrconfig.h new file mode 100644 index 0000000000..ffdbce7daa --- /dev/null +++ b/doomsday/libappfw/include/de/vr/vrconfig.h @@ -0,0 +1,187 @@ +/** @file vrconfig.h Virtual reality configuration. + * + * @authors Copyright (c) 2014 Jaakko Keränen + * @authors Copyright (c) 2013 Christopher Bruns + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef LIBAPPFW_VRCONFIG_H +#define LIBAPPFW_VRCONFIG_H + +#include + +namespace de { + +/** + * Virtual reality configuration settings. + */ +class LIBAPPFW_PUBLIC VRConfig +{ +public: + /** + * Stereoscopic 3D rendering mode. This enumeration determines the integer value in + * the console variable. + */ + enum StereoMode + { + Mono, // 0 + GreenMagenta, + RedCyan, + LeftOnly, + RightOnly, + TopBottom, // 5 + SideBySide, + Parallel, + CrossEye, + OculusRift, + RowInterleaved, // 10 // NOT IMPLEMENTED YET + ColumnInterleaved, // NOT IMPLEMENTED YET + Checkerboard, // NOT IMPLEMENTED YET + QuadBuffered, + NUM_STEREO_MODES + }; + +public: + VRConfig(); + + /** + * Sets the current stereoscopic rendering mode. + * + * @param newMode Rendering mode. + */ + void setMode(StereoMode newMode); + + /** + * Sets the distance from the eye to the screen onto which projection is being + * done. This used when calculating a frustum-shifted projection matrix. + * This is not used with Oculus Rift. + * + * @param distance Distance. + */ + void setScreenDistance(float distance); + + /** + * Sets the height of the eye in map units. This is used to determine how big an + * eye shift is needed. + * + * @param eyeHeightInMapUnits Height of the eye in map units, measured from the + * "ground". + */ + void setEyeHeightInMapUnits(float eyeHeightInMapUnits); + + /** + * Sets the currently used physical IPD. This is used to determine how big an + * eye shift is needed. + * + * @param ipd IPD in mm. + */ + void setInterpupillaryDistance(float ipd); + + /** + * Sets the height of the player in the real world. This is used as a scaling + * factor to convert physical units to map units. + * + * @param heightInMeters Height of the player in meters. + */ + void setPhysicalPlayerHeight(float heightInMeters); + + enum Eye { + NeitherEye, + LeftEye, + RightEye + }; + + /** + * Sets the eye currently used for rendering a frame. In stereoscopic modes, + * the frame is drawn twice; once for each eye. + * + * @param eye Eye to render. In non-stereoscopic modes, NeitherEye is used. + */ + void setCurrentEye(Eye eye); + + /** + * Enables or disables projection frustum shifting. + * + * @param enable @c true to enable. + */ + void enableFrustumShift(bool enable = true); + + /** + * Sets the number of multisampling samples used in the offscreen framebuffer + * where Oculus Rift frames are first drawn. This framebuffer is typically some + * multiple of the Oculus Rift display resolution. + * + * @param samples Number of samples to use for multisampling the Oculus Rift + * framebuffer. + */ + void setRiftFramebufferSampleCount(int samples); + + /** + * Sets the eyes-swapped mode. + * + * @param swapped @c true to swap left and right (default: false). + */ + void setSwapEyes(bool swapped); + + void setDominantEye(float value); + + /** + * Currently active stereo rendering mode. + */ + StereoMode mode() const; + + float screenDistance() const; + + /** + * Determines if the current stereoscopic rendering mode needs support from + * the graphics hardware for quad-buffering (left/right back and front + * buffers stored and drawn separately). + */ + bool needsStereoGLFormat() const; + + float interpupillaryDistance() const; + + float physicalPlayerHeight() const; + + /** + * Local viewpoint relative eye position in map units. + */ + float eyeShift() const; + + /** + * Determines if frustum shift is enabled. + */ + bool frustumShift() const; + + bool swapEyes() const; + + float dominantEye() const; + + /// Multisampling used in unwarped Rift framebuffer. + int riftFramebufferSampleCount() const; + + de::OculusRift &oculusRift(); + de::OculusRift const &oculusRift() const; + +public: + static bool modeNeedsStereoGLFormat(StereoMode mode); + +private: + DENG2_PRIVATE(d) +}; + +} // namespace de + +#endif // LIBAPPFW_VRCONFIG_H diff --git a/doomsday/libappfw/libappfw.pro b/doomsday/libappfw/libappfw.pro index 5316b68e91..25e06b0e16 100644 --- a/doomsday/libappfw/libappfw.pro +++ b/doomsday/libappfw/libappfw.pro @@ -17,6 +17,7 @@ include(../dep_deng2.pri) include(../dep_shell.pri) include(../dep_gui.pri) include(../dep_opengl.pri) +include(../dep_rift.pri) DEFINES += __LIBAPPFW__ INCLUDEPATH += include @@ -25,17 +26,12 @@ win32 { # Keep the version number out of the file name. TARGET_EXT = .dll } -else:macx { - #useFramework(Cocoa) -} -else:unix { - #LIBS += -lX11 -} # Public headers. HEADERS += \ include/de/AtlasProceduralImage \ include/de/BaseGuiApp \ + include/de/BaseWindow \ include/de/BlurWidget \ include/de/ButtonWidget \ include/de/ChildWidgetOrganizer \ @@ -61,6 +57,7 @@ HEADERS += \ include/de/MenuWidget \ include/de/MessageDialog \ include/de/NotificationWidget \ + include/de/OculusRift \ include/de/PanelWidget \ include/de/PopupMenuWidget \ include/de/PopupWidget \ @@ -73,6 +70,8 @@ HEADERS += \ include/de/SliderWidget \ include/de/TextDrawable \ include/de/ToggleWidget \ + include/de/VRWindowTransform \ + include/de/WindowTransform \ include/de/ui/ActionItem \ include/de/ui/Data \ include/de/ui/Item \ @@ -84,12 +83,14 @@ HEADERS += \ include/de/ui/VariableToggleItem \ include/de/VariableChoiceWidget \ include/de/VariableToggleWidget \ + include/de/VRConfig \ \ include/de/dialogs/inputdialog.h \ include/de/dialogs/messagedialog.h \ include/de/framework/actionitem.h \ include/de/framework/atlasproceduralimage.h \ include/de/framework/baseguiapp.h \ + include/de/framework/basewindow.h \ include/de/framework/childwidgetorganizer.h \ include/de/framework/data.h \ include/de/framework/dialogcontentstylist.h \ @@ -100,7 +101,6 @@ HEADERS += \ include/de/framework/guiwidget.h \ include/de/framework/guiwidgetprivate.h \ include/de/framework/item.h \ - include/de/framework/libappfw.h \ include/de/framework/listdata.h \ include/de/framework/margins.h \ include/de/framework/proceduralimage.h \ @@ -112,7 +112,12 @@ HEADERS += \ include/de/framework/subwidgetitem.h \ include/de/framework/textdrawable.h \ include/de/framework/variabletoggleitem.h \ + include/de/framework/vrwindowtransform.h \ + include/de/framework/windowtransform.h \ + include/de/libappfw.h \ include/de/ui/defs.h \ + include/de/vr/oculusrift.h \ + include/de/vr/vrconfig.h \ include/de/widgets/blurwidget.h \ include/de/widgets/buttonwidget.h \ include/de/widgets/choicewidget.h \ @@ -137,11 +142,13 @@ HEADERS += \ include/de/widgets/sliderwidget.h \ include/de/widgets/togglewidget.h \ include/de/widgets/variablechoicewidget.h \ - include/de/widgets/variabletogglewidget.h + include/de/widgets/variabletogglewidget.h \ + include/de/vr/vrconfig.h # Sources and private headers. SOURCES += \ src/baseguiapp.cpp \ + src/basewindow.cpp \ src/childwidgetorganizer.cpp \ src/data.cpp \ src/dialogcontentstylist.cpp \ @@ -160,6 +167,9 @@ SOURCES += \ src/signalaction.cpp \ src/style.cpp \ src/textdrawable.cpp \ + src/vrwindowtransform.cpp \ + src/vr/oculusrift.cpp \ + src/vr/vrconfig.cpp \ src/widgets/blurwidget.cpp \ src/widgets/buttonwidget.cpp \ src/widgets/choicewidget.cpp \ @@ -184,7 +194,8 @@ SOURCES += \ src/widgets/sliderwidget.cpp \ src/widgets/togglewidget.cpp \ src/widgets/variablechoicewidget.cpp \ - src/widgets/variabletogglewidget.cpp + src/widgets/variabletogglewidget.cpp \ + src/windowtransform.cpp # Installation --------------------------------------------------------------- diff --git a/doomsday/libappfw/src/baseguiapp.cpp b/doomsday/libappfw/src/baseguiapp.cpp index 7b501444c0..ed47807794 100644 --- a/doomsday/libappfw/src/baseguiapp.cpp +++ b/doomsday/libappfw/src/baseguiapp.cpp @@ -17,12 +17,14 @@ */ #include "de/BaseGuiApp" +#include "de/VRConfig" namespace de { DENG2_PIMPL_NOREF(BaseGuiApp) { GLShaderBank shaders; + VRConfig vr; }; BaseGuiApp::BaseGuiApp(int &argc, char **argv) @@ -39,4 +41,9 @@ GLShaderBank &BaseGuiApp::shaders() return app().d->shaders; } +VRConfig &BaseGuiApp::vr() +{ + return app().d->vr; +} + } // namespace de diff --git a/doomsday/libappfw/src/basewindow.cpp b/doomsday/libappfw/src/basewindow.cpp new file mode 100644 index 0000000000..7b9a9114ff --- /dev/null +++ b/doomsday/libappfw/src/basewindow.cpp @@ -0,0 +1,55 @@ +/** @file basewindow.cpp Abstract base class for application windows. + * + * @authors Copyright (c) 2014 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/BaseWindow" +#include "de/WindowTransform" + +namespace de { + +DENG2_PIMPL(BaseWindow) +{ + WindowTransform defaultXf; ///< Used by default (doesn't apply any transformation). + WindowTransform *xf; + + Instance(Public *i) + : Base(i) + , defaultXf(self) + , xf(&defaultXf) + {} +}; + +BaseWindow::BaseWindow() : d(new Instance(this)) +{} + +void BaseWindow::setTransform(WindowTransform &xf) +{ + d->xf = &xf; +} + +void de::BaseWindow::useDefaultTransform() +{ + d->xf = &d->defaultXf; +} + +WindowTransform &BaseWindow::transform() +{ + DENG2_ASSERT(d->xf != 0); + return *d->xf; +} + +} // namespace de diff --git a/doomsday/libappfw/src/vr/oculusrift.cpp b/doomsday/libappfw/src/vr/oculusrift.cpp new file mode 100644 index 0000000000..aa44c77edf --- /dev/null +++ b/doomsday/libappfw/src/vr/oculusrift.cpp @@ -0,0 +1,356 @@ +/** @file oculusrift.cpp + * + * @authors Copyright (c) 2014 Jaakko Keränen + * @authors Copyright (c) 2013 Christopher Bruns + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/OculusRift" + +#include +#include +#include + +#ifdef DENG_HAVE_OCULUS_API +# include +#endif + +namespace de { + +#ifdef DENG_HAVE_OCULUS_API +class OculusTracker +{ +public: + OculusTracker() + : pitch(0) + , roll(0) + , yaw(0) + , _latency(0) + { +#ifdef DENG2_DEBUG + OVR::System::Init(OVR::Log::ConfigureDefaultLog(OVR::LogMask_All)); +#else + OVR::System::Init(); +#endif + _fusionResult = new OVR::SensorFusion(); + _manager = *OVR::DeviceManager::Create(); + _hmd = *_manager->EnumerateDevices().CreateDevice(); + if(_hmd) + { + _infoLoaded = _hmd->GetDeviceInfo(&_info); + _sensor = *_hmd->GetSensor(); + } + else + { + _sensor = *_manager->EnumerateDevices().CreateDevice(); + } + + if (_sensor) + { + _fusionResult->AttachToSensor(_sensor); + } + } + + ~OculusTracker() + { + _sensor.Clear(); + _hmd.Clear(); + _manager.Clear(); + delete _fusionResult; + OVR::System::Destroy(); + } + + OVR::HMDInfo const &getInfo() const + { + return _info; + } + + bool isGood() const + { + return _sensor.GetPtr() != NULL; + } + + void update() + { + OVR::Quatf quaternion; + if(_latency == 0) + { + quaternion = _fusionResult->GetOrientation(); + } + else + { + quaternion = _fusionResult->GetPredictedOrientation(); + } + quaternion.GetEulerAngles(&yaw, &pitch, &roll); + } + + void setLatency(float lat) + { + if (_latency == lat) + return; // no change + + _latency = lat; + if (_latency == 0) + { + _fusionResult->SetPredictionEnabled(false); + _fusionResult->SetPrediction(_latency); + } + else + { + _fusionResult->SetPredictionEnabled(true); + _fusionResult->SetPrediction(_latency); + } + } + + float latency() const + { + return _latency; + } + + // Head orientation state, refreshed by call to update(); + float pitch, roll, yaw; + +private: + OVR::Ptr _manager; + OVR::Ptr _hmd; + OVR::Ptr _sensor; + OVR::SensorFusion* _fusionResult; + OVR::HMDInfo _info; + bool _infoLoaded; + float _latency; +}; +#endif + +DENG2_PIMPL(OculusRift), public Lockable +{ + bool inited; + Vector2f screenSize; + float lensSeparationDistance; + Vector4f hmdWarpParam; + Vector4f chromAbParam; + float eyeToScreenDistance; + float latency; + float ipd; + bool headOrientationUpdateIsAllowed; + +#ifdef DENG_HAVE_OCULUS_API + OculusTracker *oculusTracker; +#endif + + Instance(Public *i) + : Base(i) + , inited(false) + , screenSize(0.14976f, 0.09360f) + , lensSeparationDistance(0.0635f) + , hmdWarpParam(1.0f, 0.220f, 0.240f, 0.000f) + , chromAbParam(0.996f, -0.004f, 1.014f, 0.0f) + , eyeToScreenDistance(0.041f) + , latency(.030f) + , ipd(.064f) + , headOrientationUpdateIsAllowed(true) +#ifdef DENG_HAVE_OCULUS_API + , oculusTracker(0) +#endif + {} + + ~Instance() + { + DENG2_GUARD(this); + deinit(); + } + + void init() + { + if(inited) return; + inited = true; + +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(this); + + DENG2_ASSERT_IN_MAIN_THREAD(); + DENG2_ASSERT(!oculusTracker); + + oculusTracker = new OculusTracker; + + if(oculusTracker->isGood() /*&& autoLoadRiftParams*/) + { + OVR::HMDInfo const &info = oculusTracker->getInfo(); + ipd = info.InterpupillaryDistance; + screenSize = Vector2f(info.HScreenSize, info.VScreenSize); + lensSeparationDistance = info.LensSeparationDistance; + hmdWarpParam = Vector4f( + info.DistortionK[0], + info.DistortionK[1], + info.DistortionK[2], + info.DistortionK[3]); + chromAbParam = Vector4f( + info.ChromaAbCorrection[0], + info.ChromaAbCorrection[1], + info.ChromaAbCorrection[2], + info.ChromaAbCorrection[3]); + eyeToScreenDistance = info.EyeToScreenDistance; + } +#endif + } + + void deinit() + { + if(!inited) return; + inited = false; + +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(this); + + delete oculusTracker; + oculusTracker = 0; +#endif + } +}; + +OculusRift::OculusRift() : d(new Instance(this)) +{} + +bool OculusRift::init() +{ + d->init(); + return isReady(); +} + +void OculusRift::deinit() +{ + d->deinit(); +} + +float OculusRift::interpupillaryDistance() const +{ + return d->ipd; +} + +float OculusRift::aspect() const +{ + return 0.5f * d->screenSize.x / d->screenSize.y; +} + +Vector2f OculusRift::screenSize() const +{ + return d->screenSize; +} + +Vector4f OculusRift::chromAbParam() const +{ + return d->chromAbParam; +} + +Vector4f OculusRift::hmdWarpParam() const +{ + return d->hmdWarpParam; +} + +float OculusRift::lensSeparationDistance() const +{ + return d->lensSeparationDistance; +} + +void OculusRift::setPredictionLatency(float latency) +{ +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(d); + if(isReady()) + { + d->oculusTracker->setLatency(latency); + } +#endif +} + +float OculusRift::predictionLatency() const +{ +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(d); + if(isReady()) + { + return d->oculusTracker->latency(); + } +#endif + return 0; +} + +// True if Oculus Rift is enabled and can report head orientation. +bool OculusRift::isReady() const +{ +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(d); + return d->inited && d->oculusTracker->isGood(); +#else + return false; +#endif +} + +void OculusRift::allowUpdate() +{ + d->headOrientationUpdateIsAllowed = true; +} + +void OculusRift::update() +{ +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(d); + + if(d->headOrientationUpdateIsAllowed && isReady()) + { + d->oculusTracker->update(); + d->headOrientationUpdateIsAllowed = false; + } +#endif +} + +Vector3f OculusRift::headOrientation() const +{ + de::Vector3f result; +#ifdef DENG_HAVE_OCULUS_API + DENG2_GUARD(d); + if(isReady()) + { + result[0] = d->oculusTracker->pitch; + result[1] = d->oculusTracker->roll; + result[2] = d->oculusTracker->yaw; + } +#endif + return result; +} + +float OculusRift::distortionScale() const +{ + float lensShift = d->screenSize.x * 0.25f - lensSeparationDistance() * 0.5f; + float lensViewportShift = 4.0f * lensShift / d->screenSize.x; + float fitRadius = fabs(-1 - lensViewportShift); + float rsq = fitRadius*fitRadius; + Vector4f k = hmdWarpParam(); + float scale = (k[0] + k[1] * rsq + k[2] * rsq * rsq + k[3] * rsq * rsq * rsq); + return scale; +} + +float OculusRift::fovX() const +{ + float const w = 0.25 * d->screenSize.x * distortionScale(); + return de::radianToDegree(2.0 * atan(w / d->eyeToScreenDistance)); +} + +float OculusRift::fovY() const +{ + float const w = 0.5 * d->screenSize.y * distortionScale(); + return de::radianToDegree(2.0 * atan(w / d->eyeToScreenDistance)); +} + +} // namespace de diff --git a/doomsday/libappfw/src/vr/vrconfig.cpp b/doomsday/libappfw/src/vr/vrconfig.cpp new file mode 100644 index 0000000000..453c7ddfeb --- /dev/null +++ b/doomsday/libappfw/src/vr/vrconfig.cpp @@ -0,0 +1,187 @@ +/** @file vrconfig.cpp Virtual reality configuration. + * + * @authors Copyright (c) 2014 Jaakko Keränen + * @authors Copyright (c) 2013 Christopher Bruns + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/VRConfig" + +namespace de { + +DENG2_PIMPL(VRConfig) +{ + StereoMode mode; + de::OculusRift ovr; + float screenDistance; + float ipd; + float eyeHeightInMapUnits; + float eyeShift; + float playerPhysicalHeight; + bool swapEyes; + int riftFramebufferSamples; // Multisampling used in unwarped Rift framebuffer + + /** + * Unlike most 3D modes, Oculus Rift typically uses no frustum shift. (or if we did, + * it would be different and complicated) + */ + bool frustumShift; + + float dominantEye; ///< Kludge for aim-down-weapon-sight modes + + Instance(Public *i) + : Base(i) + , mode(Mono) + , screenDistance(20.f) + , ipd(.064f) // average male IPD + , eyeHeightInMapUnits(41) + , eyeShift(0) + , playerPhysicalHeight(1.75f) + , swapEyes(false) + , riftFramebufferSamples(2) + , frustumShift(true) + , dominantEye(0.0f) + { + ovr.init(); + } +}; + +VRConfig::VRConfig() : d(new Instance(this)) +{} + +void VRConfig::setMode(StereoMode newMode) +{ + d->mode = newMode; +} + +void VRConfig::setScreenDistance(float distance) +{ + d->screenDistance = distance; +} + +void VRConfig::setEyeHeightInMapUnits(float eyeHeightInMapUnits) +{ + d->eyeHeightInMapUnits = eyeHeightInMapUnits; +} + +void VRConfig::setInterpupillaryDistance(float ipd) +{ + d->ipd = ipd; +} + +void VRConfig::setPhysicalPlayerHeight(float heightInMeters) +{ + d->playerPhysicalHeight = heightInMeters; +} + +void VRConfig::setCurrentEye(Eye eye) +{ + float eyePos = (eye == NeitherEye? 0 : eye == LeftEye? -1 : 1); + + // 0.95 because eyes are not at top of head + float mapUnitsPerMeter = d->eyeHeightInMapUnits / (0.95 * d->playerPhysicalHeight); + d->eyeShift = mapUnitsPerMeter * (eyePos - d->dominantEye) * 0.5 * d->ipd; + if(d->swapEyes) + { + d->eyeShift *= -1; + } +} + +void VRConfig::enableFrustumShift(bool enable) +{ + d->frustumShift = enable; +} + +void VRConfig::setRiftFramebufferSampleCount(int samples) +{ + d->riftFramebufferSamples = samples; +} + +void VRConfig::setSwapEyes(bool swapped) +{ + d->swapEyes = swapped; +} + +void VRConfig::setDominantEye(float value) +{ + d->dominantEye = value; +} + +VRConfig::StereoMode VRConfig::mode() const +{ + return d->mode; +} + +float VRConfig::screenDistance() const +{ + return d->screenDistance; +} + +bool VRConfig::needsStereoGLFormat() const +{ + return modeNeedsStereoGLFormat(mode()); +} + +bool VRConfig::modeNeedsStereoGLFormat(StereoMode mode) +{ + return mode == QuadBuffered; +} + +float VRConfig::interpupillaryDistance() const +{ + return d->ipd; +} + +float VRConfig::physicalPlayerHeight() const +{ + return d->playerPhysicalHeight; +} + +float VRConfig::eyeShift() const +{ + return d->eyeShift; +} + +bool VRConfig::frustumShift() const +{ + return d->frustumShift; +} + +bool VRConfig::swapEyes() const +{ + return d->swapEyes; +} + +float VRConfig::dominantEye() const +{ + return d->dominantEye; +} + +int VRConfig::riftFramebufferSampleCount() const +{ + return d->riftFramebufferSamples; +} + +OculusRift &VRConfig::oculusRift() +{ + return d->ovr; +} + +OculusRift const &VRConfig::oculusRift() const +{ + return d->ovr; +} + +} // namespace de diff --git a/doomsday/libappfw/src/vrwindowtransform.cpp b/doomsday/libappfw/src/vrwindowtransform.cpp new file mode 100644 index 0000000000..106d33e103 --- /dev/null +++ b/doomsday/libappfw/src/vrwindowtransform.cpp @@ -0,0 +1,446 @@ +/** @file vrwindowtransform.cpp Window content transformation for virtual reality. + * + * @authors Copyright (c) 2013 Christopher Bruns + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/VRWindowTransform" +#include "de/VRConfig" +#include "de/BaseGuiApp" +#include "de/BaseWindow" +//#include "de_platform.h" +//#include "con_main.h" +//#include "render/vr.h" + +#include +#include + +namespace de { + +DENG2_PIMPL(VRWindowTransform) +{ + VRConfig &vrCfg; + Drawable oculusRift; + GLUniform uOculusRiftFB; + GLUniform uOculusDistortionScale; + GLUniform uOculusScreenSize; + GLUniform uOculusLensSeparation; + GLUniform uOculusHmdWarpParam; + GLUniform uOculusChromAbParam; + + typedef GLBufferT OculusRiftVBuf; + GLFramebuffer unwarpedFB; + + Instance(Public *i) + : Base(i) + , vrCfg(DENG2_BASE_GUI_APP->vr()) + , uOculusRiftFB ("texture", GLUniform::Sampler2D) + , uOculusDistortionScale("distortionScale", GLUniform::Float) + , uOculusScreenSize ("screenSize", GLUniform::Vec2) + , uOculusLensSeparation ("lensSeparationDistance", GLUniform::Float) + , uOculusHmdWarpParam ("hmdWarpParam", GLUniform::Vec4) + , uOculusChromAbParam ("chromAbParam", GLUniform::Vec4) + {} + + void init() + { + /// @todo Only do this when Oculus Rift mode is enabled. Free the allocated + /// resources when non-Rift mode in use. + + OculusRiftVBuf *buf = new OculusRiftVBuf; + oculusRift.addBuffer(buf); + + // Set up a simple static quad. + OculusRiftVBuf::Type const verts[4] = { + { Vector3f(-1, 1, 0.5f), Vector2f(0, 1), }, + { Vector3f( 1, 1, 0.5f), Vector2f(1, 1), }, + { Vector3f(-1, -1, 0.5f), Vector2f(0, 0), }, + { Vector3f( 1, -1, 0.5f), Vector2f(1, 0), } + }; + buf->setVertices(gl::TriangleStrip, verts, 4, gl::Static); + + DENG2_BASE_GUI_APP->shaders() + .build(oculusRift.program(), "vr.oculusrift.barrel") + << uOculusRiftFB + << uOculusDistortionScale + << uOculusScreenSize + << uOculusLensSeparation + << uOculusHmdWarpParam + << uOculusChromAbParam; + + unwarpedFB.glInit(); + uOculusRiftFB = unwarpedFB.colorTexture(); + } + + void deinit() + { + oculusRift.clear(); + unwarpedFB.glDeinit(); + } + + Canvas &canvas() const + { + return self.window().windowCanvas(); + } + + GLTarget &target() const + { + return canvas().renderTarget(); + } + + int width() const + { + return canvas().width(); + } + + int height() const + { + return canvas().height(); + } + + void drawContent() const + { + LIBGUI_ASSERT_GL_OK(); + self.window().drawWindowContent(); + LIBGUI_ASSERT_GL_OK(); + } + + /** + * Draws the entire UI in two halves, one for the left eye and one for the right. The + * Oculus Rift optical distortion effect is applied using a shader. + * + * @todo unwarpedTarget and unwarpedTexture should be cleared/deleted when Oculus + * Rift mode is disabled (or whenever they are not needed). + */ + void vrDrawOculusRift() + { + vrCfg.enableFrustumShift(false); + + /// @todo shrunken hud + // Allocate offscreen buffers - larger than Oculus Rift size, to get adequate resolution at center after warp + // For some reason, 1.5X looks best, even though objects are ~2.3X unwarped size at center. + float unwarpFactor = 1.5f; + Canvas::Size textureSize = Canvas::Size(1280, 800) * unwarpFactor; + // Canvas::Size textureSize(2560, 1600); // 2 * 1280x800 // Undesirable relative softness at very center of image + // Canvas::Size textureSize(3200, 2000); // 2.5 * 1280x800 // Softness here too + unwarpedFB.resize(textureSize); + + // Use a little bit of multisampling to smooth out the magnified jagged edges. + // Note: Independent of the vid-fsaa setting because this is beneficial even when + // vid-fsaa is disabled. + unwarpedFB.setSampleCount(vrCfg.riftFramebufferSampleCount()); + unwarpedFB.colorTexture().setFilter(gl::Linear, gl::Linear, gl::MipNone); + + // Set render target to offscreen temporarily. + GLState::push() + .setTarget(unwarpedFB.target()) + .setViewport(Rectangleui::fromSize(unwarpedFB.size())) + .apply(); + unwarpedFB.target().unsetActiveRect(true); + unwarpedFB.target().clear(GLTarget::ColorDepth); + + // Left eye view on left side of screen. + vrCfg.setCurrentEye(VRConfig::LeftEye); + unwarpedFB.target().setActiveRect(Rectangleui(0, 0, textureSize.x/2, textureSize.y), true); + drawContent(); + + // Right eye view on right side of screen. + vrCfg.setCurrentEye(VRConfig::RightEye); + unwarpedFB.target().setActiveRect(Rectangleui(textureSize.x/2, 0, textureSize.x/2, textureSize.y), true); + drawContent(); + + unwarpedFB.target().unsetActiveRect(true); + + GLState::pop().apply(); + + // Necessary until the legacy code uses GLState, too: + glEnable(GL_TEXTURE_2D); + + target().clear(GLTarget::Color); + GLState::push() + .setDepthTest(false); + + // Copy contents of offscreen buffer to normal screen. + uOculusDistortionScale = vrCfg.oculusRift().distortionScale(); + uOculusScreenSize = vrCfg.oculusRift().screenSize(); + uOculusLensSeparation = vrCfg.oculusRift().lensSeparationDistance(); + uOculusHmdWarpParam = vrCfg.oculusRift().hmdWarpParam(); + uOculusChromAbParam = vrCfg.oculusRift().chromAbParam(); + // + oculusRift.draw(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDepthMask(GL_TRUE); + + GLState::pop().apply(); + + vrCfg.enableFrustumShift(); // restore default + } + + void draw() + { + // Allow Oculus Rift to use the latest head tracking position for the upcoming + // draw operations. + vrCfg.oculusRift().allowUpdate(); + + switch(vrCfg.mode()) + { + // A) Single view type stereo 3D modes here: + case VRConfig::Mono: + // Non-stereoscopic frame. + drawContent(); + break; + + case VRConfig::LeftOnly: + // Left eye view + vrCfg.setCurrentEye(VRConfig::LeftEye); + drawContent(); + break; + + case VRConfig::RightOnly: + // Right eye view + vrCfg.setCurrentEye(VRConfig::RightEye); + drawContent(); + break; + + // B) Split-screen type stereo 3D modes here: + case VRConfig::TopBottom: // Left goes on top + // Left eye view on top of screen. + vrCfg.setCurrentEye(VRConfig::LeftEye); + target().setActiveRect(Rectangleui(0, 0, width(), height()/2), true); + drawContent(); + // Right eye view on bottom of screen. + vrCfg.setCurrentEye(VRConfig::RightEye); + target().setActiveRect(Rectangleui(0, height()/2, width(), height()/2), true); + drawContent(); + break; + + case VRConfig::SideBySide: // Squished aspect + // Left eye view on left side of screen. + vrCfg.setCurrentEye(VRConfig::LeftEye); + target().setActiveRect(Rectangleui(0, 0, width()/2, height()), true); + drawContent(); + // Right eye view on right side of screen. + vrCfg.setCurrentEye(VRConfig::RightEye); + target().setActiveRect(Rectangleui(width()/2, 0, width()/2, height()), true); + drawContent(); + break; + + case VRConfig::Parallel: // Normal aspect + // Left eye view on left side of screen. + vrCfg.setCurrentEye(VRConfig::LeftEye); + target().setActiveRect(Rectangleui(0, 0, width()/2, height()), true); + drawContent(); + // Right eye view on right side of screen. + vrCfg.setCurrentEye(VRConfig::RightEye); + target().setActiveRect(Rectangleui(width()/2, 0, width()/2, height()), true); + drawContent(); + break; + + case VRConfig::CrossEye: // Normal aspect + // Right eye view on left side of screen. + vrCfg.setCurrentEye(VRConfig::RightEye); + target().setActiveRect(Rectangleui(0, 0, width()/2, height()), true); + drawContent(); + // Left eye view on right side of screen. + vrCfg.setCurrentEye(VRConfig::LeftEye); + target().setActiveRect(Rectangleui(width()/2, 0, width()/2, height()), true); + drawContent(); + break; + + case VRConfig::OculusRift: + vrDrawOculusRift(); + break; + + // Overlaid type stereo 3D modes below: + case VRConfig::GreenMagenta: + // Left eye view + vrCfg.setCurrentEye(VRConfig::LeftEye); + GLState::push().setColorMask(gl::WriteGreen | gl::WriteAlpha).apply(); // Left eye view green + drawContent(); + // Right eye view + vrCfg.setCurrentEye(VRConfig::RightEye); + GLState::current().setColorMask(gl::WriteRed | gl::WriteBlue | gl::WriteAlpha).apply(); // Right eye view magenta + drawContent(); + GLState::pop().apply(); + break; + + case VRConfig::RedCyan: + // Left eye view + vrCfg.setCurrentEye(VRConfig::LeftEye); + GLState::push().setColorMask(gl::WriteRed | gl::WriteAlpha).apply(); // Left eye view red + drawContent(); + // Right eye view + vrCfg.setCurrentEye(VRConfig::RightEye); + GLState::current().setColorMask(gl::WriteGreen | gl::WriteBlue | gl::WriteAlpha).apply(); // Right eye view cyan + drawContent(); + GLState::pop().apply(); + break; + + case VRConfig::QuadBuffered: + if(canvas().format().stereo()) + { + // Left eye view + vrCfg.setCurrentEye(VRConfig::LeftEye); + drawContent(); + canvas().framebuffer().swapBuffers(canvas(), gl::SwapStereoLeftBuffer); + + // Right eye view + vrCfg.setCurrentEye(VRConfig::RightEye); + drawContent(); + canvas().framebuffer().swapBuffers(canvas(), gl::SwapStereoRightBuffer); + } + else + { + // Normal non-stereoscopic frame. + drawContent(); + } + break; + + case VRConfig::RowInterleaved: + { + // Use absolute screen position of window to determine whether the + // first scan line is odd or even. + QPoint ulCorner(0, 0); + ulCorner = canvas().mapToGlobal(ulCorner); // widget to screen coordinates + bool rowParityIsEven = ((ulCorner.x() % 2) == 0); + DENG2_UNUSED(rowParityIsEven); + /// @todo - use row parity in shader or stencil, to actually interleave rows. + // Left eye view + vrCfg.setCurrentEye(VRConfig::LeftEye); + drawContent(); + // Right eye view + vrCfg.setCurrentEye(VRConfig::RightEye); + drawContent(); + break; + } + + case VRConfig::ColumnInterleaved: /// @todo implement column interleaved stereo 3D after row intleaved is working correctly... + case VRConfig::Checkerboard: /// @todo implement checker stereo 3D after row intleaved is working correctly ... + default: + // Non-stereoscopic frame. + drawContent(); + break; + } + + // Restore default VR dynamic parameters + target().unsetActiveRect(true); + vrCfg.setCurrentEye(VRConfig::NeitherEye); + + LIBGUI_ASSERT_GL_OK(); + } +}; + +VRWindowTransform::VRWindowTransform(BaseWindow &window) + : WindowTransform(window), d(new Instance(this)) +{} + +void VRWindowTransform::glInit() +{ + d->init(); +} + +void VRWindowTransform::glDeinit() +{ + d->deinit(); +} + +Vector2ui VRWindowTransform::logicalRootSize(Vector2ui const &physicalCanvasSize) const +{ + Canvas::Size size = physicalCanvasSize; + + switch(d->vrCfg.mode()) + { + // Left-right screen split modes + case VRConfig::CrossEye: + case VRConfig::Parallel: + // Adjust effective UI size for stereoscopic rendering. + size.y *= 2; + size *= .75f; // Make it a bit bigger. + break; + + case VRConfig::OculusRift: + /// @todo - taskbar needs to elevate above bottom of screen in Rift mode + // Adjust effective UI size for stereoscopic rendering. + size.x = size.y * d->vrCfg.oculusRift().aspect(); + size *= 1.0f; // Use a large font in taskbar + break; + + // Allow UI to squish in top/bottom and SBS mode: 3D hardware will unsquish them + case VRConfig::TopBottom: + case VRConfig::SideBySide: + default: + break; + } + + return size; +} + +Vector2f VRWindowTransform::windowToLogicalCoords(Vector2i const &winPos) const +{ + // We need to map the real window coordinates to logical root view + // coordinates according to the used transformation. + + Vector2f pos = winPos; + + Vector2f const size = window().windowCanvas().size(); + Vector2f const viewSize = window().windowContentSize(); + + switch(d->vrCfg.mode()) + { + // Left-right screen split modes + case VRConfig::SideBySide: + case VRConfig::CrossEye: + case VRConfig::Parallel: + case VRConfig::OculusRift: + // Make it possible to access both frames. + if(pos.x >= size.x/2) + { + pos.x -= size.x/2; + } + pos.x *= 2; + + // Scale to logical size. + pos = pos / size * viewSize; + break; + + // Top-bottom screen split modes + case VRConfig::TopBottom: + // Make it possible to access both frames. + if(pos.y >= size.y/2) + { + pos.y -= size.y/2; + } + pos.y *= 2; + + // Scale to logical size. + pos = pos / size * viewSize; + break; + + default: + // Not transformed. + break; + } + + return pos; +} + +void VRWindowTransform::drawTransformed() +{ + d->draw(); +} + +} // namespace de diff --git a/doomsday/libappfw/src/widgets/foldpanelwidget.cpp b/doomsday/libappfw/src/widgets/foldpanelwidget.cpp index f07a235699..3937101e57 100644 --- a/doomsday/libappfw/src/widgets/foldpanelwidget.cpp +++ b/doomsday/libappfw/src/widgets/foldpanelwidget.cpp @@ -80,6 +80,7 @@ ButtonWidget *FoldPanelWidget::makeTitle(String const &text) d->title->setTextColor("accent"); d->title->setHoverTextColor("text", ButtonWidget::ReplaceColor); d->title->setFont("heading"); + d->title->setTextLineAlignment(ui::AlignLeft); d->title->set(Background()); // no frame or background d->title->setAction(new SignalAction(this, SLOT(toggleFold()))); d->title->setOpacity(.8f); diff --git a/doomsday/client/src/ui/windowtransform.cpp b/doomsday/libappfw/src/windowtransform.cpp similarity index 73% rename from doomsday/client/src/ui/windowtransform.cpp rename to doomsday/libappfw/src/windowtransform.cpp index f0b7902bfe..de9eda68df 100644 --- a/doomsday/client/src/ui/windowtransform.cpp +++ b/doomsday/libappfw/src/windowtransform.cpp @@ -1,4 +1,4 @@ -/** @file windowtransform.cpp Base class for window content transformation. +/** @file windowtransform.cpp Base class for window content transformation. * * @authors Copyright (c) 2013 Jaakko Keränen * @@ -16,24 +16,25 @@ * http://www.gnu.org/licenses */ -#include "ui/windowtransform.h" -#include "ui/clientwindow.h" +#include "de/WindowTransform" +#include "de/BaseWindow" -using namespace de; +namespace de { DENG2_PIMPL_NOREF(WindowTransform) { - ClientWindow *win; + BaseWindow *win; }; -WindowTransform::WindowTransform(ClientWindow &window) +WindowTransform::WindowTransform(BaseWindow &window) : d(new Instance) { d->win = &window; } -ClientWindow &WindowTransform::window() const +BaseWindow &WindowTransform::window() const { + DENG2_ASSERT(d->win != 0); return *d->win; } @@ -52,12 +53,14 @@ Vector2ui WindowTransform::logicalRootSize(Vector2ui const &physicalCanvasSize) return physicalCanvasSize; } -Vector2f WindowTransform::windowToLogicalCoords(de::Vector2i const &pos) const +Vector2f WindowTransform::windowToLogicalCoords(Vector2i const &pos) const { return pos; } void WindowTransform::drawTransformed() { - return d->win->root().draw(); + return d->win->drawWindowContent(); } + +} // namespace de diff --git a/doomsday/libdeng2/src/core/app.cpp b/doomsday/libdeng2/src/core/app.cpp index 3191aa5ccf..bc6539611c 100644 --- a/doomsday/libdeng2/src/core/app.cpp +++ b/doomsday/libdeng2/src/core/app.cpp @@ -312,6 +312,11 @@ void App::setGame(game::Game &game) bool App::inMainThread() { + if(!App::appExists()) + { + // No app even created yet, must be main thread. + return true; + } return DENG2_APP->d->mainThread == QThread::currentThread(); } diff --git a/doomsday/libgui/include/de/gui/canvaswindow.h b/doomsday/libgui/include/de/gui/canvaswindow.h index e2ede87f0d..cadb452ef3 100644 --- a/doomsday/libgui/include/de/gui/canvaswindow.h +++ b/doomsday/libgui/include/de/gui/canvaswindow.h @@ -171,6 +171,11 @@ class LIBGUI_PUBLIC CanvasWindow : public QMainWindow, */ void *nativeHandle() const; + bool isRecreationInProgress() const; + +protected slots: + void finishCanvasRecreation(); + public: static bool mainExists(); static CanvasWindow &main(); diff --git a/doomsday/libgui/include/de/gui/glentrypoints.h b/doomsday/libgui/include/de/gui/glentrypoints.h index 1d888a9ced..e14b09c721 100644 --- a/doomsday/libgui/include/de/gui/glentrypoints.h +++ b/doomsday/libgui/include/de/gui/glentrypoints.h @@ -100,6 +100,8 @@ LIBGUI_EXTERN_C PFNGLGETSHADERSOURCEPROC glGetShaderSource; LIBGUI_EXTERN_C PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; LIBGUI_EXTERN_C PFNGLISBUFFERPROC glIsBuffer; +LIBGUI_EXTERN_C PFNGLISFRAMEBUFFERPROC glIsFramebuffer; +LIBGUI_EXTERN_C PFNGLISPROGRAMPROC glIsProgram; LIBGUI_EXTERN_C PFNGLLINKPROGRAMPROC glLinkProgram; @@ -120,6 +122,10 @@ LIBGUI_EXTERN_C PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; // Extensions: +#ifdef GL_ARB_debug_output +LIBGUI_EXTERN_C PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB; +LIBGUI_EXTERN_C PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB; +#endif LIBGUI_EXTERN_C PFNGLBLITFRAMEBUFFEREXTPROC glBlitFramebufferEXT; LIBGUI_EXTERN_C PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT; #ifdef GL_NV_framebuffer_multisample_coverage @@ -128,6 +134,12 @@ LIBGUI_EXTERN_C PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC glRenderbuff void getAllOpenGLEntryPoints(); +#ifdef Q_WS_X11 +LIBGUI_PUBLIC char const *getGLXExtensionsString(); +LIBGUI_PUBLIC void setXSwapInterval(int interval); +void getGLXEntryPoints(); +#endif + #endif // LIBGUI_USE_GLENTRYPOINTS #endif // LIBGUI_GLENTRYPOINTS_H diff --git a/doomsday/libgui/include/de/gui/glinfo.h b/doomsday/libgui/include/de/gui/glinfo.h index cf7b567645..224ee3f10a 100644 --- a/doomsday/libgui/include/de/gui/glinfo.h +++ b/doomsday/libgui/include/de/gui/glinfo.h @@ -31,6 +31,7 @@ class LIBGUI_PUBLIC GLInfo /// Extension availability bits. struct Extensions { + duint32 ARB_debug_output : 1; duint32 ARB_framebuffer_object : 1; duint32 ARB_texture_env_combine : 1; duint32 ARB_texture_non_power_of_two : 1; @@ -52,6 +53,10 @@ class LIBGUI_PUBLIC GLInfo duint32 Windows_ARB_multisample : 1; duint32 Windows_EXT_swap_control : 1; #endif + +#ifdef Q_WS_X11 + duint32 X11_EXT_swap_control : 1; +#endif }; /// Implementation limits. diff --git a/doomsday/libgui/include/de/gui/glprogram.h b/doomsday/libgui/include/de/gui/glprogram.h index acb06eee3f..01e24741ce 100644 --- a/doomsday/libgui/include/de/gui/glprogram.h +++ b/doomsday/libgui/include/de/gui/glprogram.h @@ -79,6 +79,10 @@ class LIBGUI_PUBLIC GLProgram : public Asset GLProgram &build(IByteArray const &vertexShaderSource, IByteArray const &fragmentShaderSource); + void rebuildBeforeNextUse(); + + void rebuild(); + GLProgram &operator << (GLUniform const &uniform); GLProgram &bind(GLUniform const &uniform); diff --git a/doomsday/libgui/include/de/gui/glshader.h b/doomsday/libgui/include/de/gui/glshader.h index ac02a25f72..c1031eafc5 100644 --- a/doomsday/libgui/include/de/gui/glshader.h +++ b/doomsday/libgui/include/de/gui/glshader.h @@ -62,6 +62,8 @@ class LIBGUI_PUBLIC GLShader : public Counted, public Asset void compile(Type shaderType, IByteArray const &source); + void recompile(); + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/libgui/include/de/gui/guiapp.h b/doomsday/libgui/include/de/gui/guiapp.h index 8db3cd7de2..74620eb5ad 100644 --- a/doomsday/libgui/include/de/gui/guiapp.h +++ b/doomsday/libgui/include/de/gui/guiapp.h @@ -44,6 +44,12 @@ class LIBGUI_PUBLIC GuiApp : public QApplication, public App, { Q_OBJECT +public: + /** + * Notified when a Canvas is recreated. + */ + DENG2_DEFINE_AUDIENCE(GLContextChange, void appGLContextChanged()) + public: GuiApp(int &argc, char **argv); @@ -58,6 +64,8 @@ class LIBGUI_PUBLIC GuiApp : public QApplication, public App, */ void notifyDisplayModeChanged(); + void notifyGLContextChanged(); + int execLoop(); void stopLoop(int code); diff --git a/doomsday/libgui/include/de/gui/libgui.h b/doomsday/libgui/include/de/gui/libgui.h index 83f1bdcd3c..7492a56d9d 100644 --- a/doomsday/libgui/include/de/gui/libgui.h +++ b/doomsday/libgui/include/de/gui/libgui.h @@ -19,6 +19,8 @@ #ifndef LIBGUI_MAIN_H #define LIBGUI_MAIN_H +#include + /* * The LIBGUI_PUBLIC macro is used for declaring exported symbols. It must be * applied in all exported classes and functions. DEF files are not used for @@ -41,8 +43,10 @@ #endif #ifndef NDEBUG -# define LIBGUI_ASSERT_GL_OK() {GLuint _er = glGetError(); if(_er != GL_NO_ERROR) { \ - qWarning("OpenGL error: 0x%x", _er); DENG2_ASSERT(!"OpenGL operation failed"); }} +# define LIBGUI_ASSERT_GL_OK() {GLuint _er = GL_NO_ERROR; do { \ + _er = glGetError(); if(_er != GL_NO_ERROR) { \ + LogBuffer_Flush(); qWarning("OpenGL error: 0x%x", _er); \ + DENG2_ASSERT(!"OpenGL operation failed"); }} while(_er != GL_NO_ERROR);} #else # define LIBGUI_ASSERT_GL_OK() #endif diff --git a/doomsday/libgui/libgui.pro b/doomsday/libgui/libgui.pro index af5a9e4bf9..ce28e677fc 100644 --- a/doomsday/libgui/libgui.pro +++ b/doomsday/libgui/libgui.pro @@ -131,6 +131,7 @@ SOURCES += \ src/fontbank.cpp \ src/glbuffer.cpp \ src/glentrypoints.cpp \ + src/glentrypoints_x11.cpp \ src/glframebuffer.cpp \ src/glinfo.cpp \ src/glprogram.cpp \ diff --git a/doomsday/libgui/src/canvas.cpp b/doomsday/libgui/src/canvas.cpp index 466d3305af..7f8b586340 100644 --- a/doomsday/libgui/src/canvas.cpp +++ b/doomsday/libgui/src/canvas.cpp @@ -86,6 +86,11 @@ DENG2_PIMPL(Canvas) #endif } + ~Instance() + { + glDeinit(); + } + void grabMouse() { if(!self.isVisible()/* || mouseDisabled*/) return; @@ -342,6 +347,7 @@ void Canvas::resizeGL(int w, int h) if(d->currentSize != d->pendingSize) { #ifdef LIBGUI_CANVAS_USE_DEFERRED_RESIZE + qDebug() << "Canvas" << this << "triggered size to" << w << h << "from" << d->currentSize.asText(); d->resizeTimer.start(100); #else updateSize(); @@ -351,7 +357,18 @@ void Canvas::resizeGL(int w, int h) void Canvas::updateSize() { - d->currentSize = d->pendingSize; + /* + if(d->parent && d->parent->isRecreationInProgress()) + { + d->resizeTimer.start(100); + return; + } + */ + + qDebug() << this << "resizing now"; + + makeCurrent(); + d->currentSize = d->pendingSize; d->reconfigureFramebuffer(); DENG2_FOR_AUDIENCE(GLResize, i) i->canvasGLResized(*this); @@ -385,7 +402,7 @@ void Canvas::notifyReady() d->readyNotified = true; - d->framebuf.glInit(); + d->glInit(); d->reconfigureFramebuffer(); // Print some information. @@ -413,10 +430,22 @@ void Canvas::notifyReady() void Canvas::paintGL() { + if(!d->parent || d->parent->isRecreationInProgress()) return; + + if(d->resizeTimer.isActive()) + { + d->resizeTimer.stop(); + updateSize(); + } + + LIBGUI_ASSERT_GL_OK(); + // Make sure any changes to the state stack become effective. GLState::current().apply(); DENG2_FOR_AUDIENCE(GLDraw, i) i->canvasGLDraw(*this); + + LIBGUI_ASSERT_GL_OK(); } void Canvas::focusInEvent(QFocusEvent*) diff --git a/doomsday/libgui/src/canvaswindow.cpp b/doomsday/libgui/src/canvaswindow.cpp index 68f04457ab..c4a08f9a2e 100644 --- a/doomsday/libgui/src/canvaswindow.cpp +++ b/doomsday/libgui/src/canvaswindow.cpp @@ -19,18 +19,20 @@ */ #include "de/CanvasWindow" +#include "de/GuiApp" #include #include #include #include +#include -#include #include #include #include #include #include +#include #include namespace de { @@ -101,14 +103,21 @@ DENG2_PIMPL(CanvasWindow) // Set up the basic GL state for the new canvas. canvas->makeCurrent(); + LIBGUI_ASSERT_GL_OK(); DENG2_FOR_EACH_OBSERVER(Canvas::GLInitAudience, i, canvas->audienceForGLInit) { i->canvasGLInit(*canvas); } - //canvas->doneCurrent(); + DENG2_GUI_APP->notifyGLContextChanged(); + +#ifdef Q_WS_X11 + canvas->update(); +#else canvas->updateGL(); +#endif + LIBGUI_ASSERT_GL_OK(); // Reacquire the focus. canvas->setFocus(); @@ -151,6 +160,8 @@ void CanvasWindow::recreateCanvas() { DENG2_ASSERT_IN_MAIN_THREAD(); + GLState::considerNativeStateUndefined(); + d->ready = false; // Steal the focus change audience temporarily so no spurious focus @@ -162,16 +173,27 @@ void CanvasWindow::recreateCanvas() d->mouseWasTrapped = canvas().isMouseTrapped(); canvas().trapMouse(false); canvas().setParent(0); + canvas().hide(); // Create the replacement Canvas. Once it's created and visible, we'll // finish the switch-over. d->recreated = new Canvas(this, d->canvas); d->recreated->audienceForGLReady += this; - d->recreated->setGeometry(d->canvas->geometry()); + //d->recreated->setGeometry(d->canvas->geometry()); d->recreated->show(); + d->recreated->update(); + + LIBGUI_ASSERT_GL_OK(); LOGDEV_GL_MSG("Canvas recreated, old one still exists"); + qDebug() << "old Canvas" << &canvas(); + qDebug() << "new Canvas" << d->recreated; +} + +bool CanvasWindow::isRecreationInProgress() const +{ + return d->recreated != 0; } Canvas &CanvasWindow::canvas() const @@ -214,7 +236,13 @@ void CanvasWindow::canvasGLReady(Canvas &canvas) if(d->recreated == &canvas) { +#ifndef Q_WS_X11 d->finishCanvasRecreation(); +#else + // Need to defer the finalization. + qDebug() << "defer recreation"; + QTimer::singleShot(100, this, SLOT(finishCanvasRecreation())); +#endif } } @@ -266,6 +294,11 @@ void *CanvasWindow::nativeHandle() const return reinterpret_cast(winId()); } +void CanvasWindow::finishCanvasRecreation() +{ + d->finishCanvasRecreation(); +} + bool CanvasWindow::mainExists() { return mainWindow != 0; @@ -282,4 +315,6 @@ void CanvasWindow::setMain(CanvasWindow *window) mainWindow = window; } + + } // namespace de diff --git a/doomsday/libgui/src/glentrypoints.cpp b/doomsday/libgui/src/glentrypoints.cpp index e024b94f75..950af9d625 100644 --- a/doomsday/libgui/src/glentrypoints.cpp +++ b/doomsday/libgui/src/glentrypoints.cpp @@ -21,12 +21,12 @@ #ifdef LIBGUI_USE_GLENTRYPOINTS -using namespace de; - -#ifdef UNIX +#ifdef Q_WS_X11 # include #endif +using namespace de; + #ifdef LIBGUI_FETCH_GL_1_3 PFNGLACTIVETEXTUREPROC glActiveTexture; PFNGLBLENDEQUATIONPROC glBlendEquation; @@ -79,6 +79,8 @@ PFNGLGETSHADERSOURCEPROC glGetShaderSource; PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; PFNGLISBUFFERPROC glIsBuffer; +PFNGLISFRAMEBUFFERPROC glIsFramebuffer; +PFNGLISPROGRAMPROC glIsProgram; PFNGLLINKPROGRAMPROC glLinkProgram; @@ -99,10 +101,14 @@ PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; // Extensions: -PFNGLBLITFRAMEBUFFEREXTPROC glBlitFramebufferEXT; -PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT; +#ifdef GL_ARB_debug_output +PFNGLDEBUGMESSAGECONTROLARBPROC glDebugMessageControlARB; +PFNGLDEBUGMESSAGECALLBACKARBPROC glDebugMessageCallbackARB; +#endif +PFNGLBLITFRAMEBUFFEREXTPROC glBlitFramebufferEXT; +PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT; #ifdef GL_NV_framebuffer_multisample_coverage -PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC glRenderbufferStorageMultisampleCoverageNV; +PFNGLRENDERBUFFERSTORAGEMULTISAMPLECOVERAGENVPROC glRenderbufferStorageMultisampleCoverageNV; #endif void getAllOpenGLEntryPoints() @@ -166,6 +172,8 @@ void getAllOpenGLEntryPoints() GET_PROC(glGetShaderSource); GET_PROC(glGetUniformLocation); GET_PROC(glIsBuffer); + GET_PROC(glIsFramebuffer); + GET_PROC(glIsProgram); GET_PROC(glLinkProgram); GET_PROC(glRenderbufferStorage); GET_PROC(glShaderSource); @@ -179,11 +187,18 @@ void getAllOpenGLEntryPoints() GET_PROC(glUseProgram); GET_PROC(glVertexAttribPointer); +#ifdef GL_ARB_debug_output + GET_PROC(glDebugMessageControlARB); + GET_PROC(glDebugMessageCallbackARB); +#endif GET_PROC_EXT(glBlitFramebufferEXT); GET_PROC_EXT(glRenderbufferStorageMultisampleEXT); #ifdef GL_NV_framebuffer_multisample_coverage GET_PROC_EXT(glRenderbufferStorageMultisampleCoverageNV); #endif +#ifdef Q_WS_X11 + getGLXEntryPoints(); +#endif haveProcs = true; } diff --git a/doomsday/libgui/src/glentrypoints_x11.cpp b/doomsday/libgui/src/glentrypoints_x11.cpp new file mode 100644 index 0000000000..a4328cdf42 --- /dev/null +++ b/doomsday/libgui/src/glentrypoints_x11.cpp @@ -0,0 +1,52 @@ +/** @file glentrypoints_x11.cpp + * + * @authors Copyright (c) 2014 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/gui/glentrypoints.h" + +#ifdef Q_WS_X11 + +#include "de/CanvasWindow" + +#include +#include +#include + +#define GET_PROC_EXT(name) *((void (**)())&name) = glXGetProcAddress((GLubyte const *)#name) + +PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT; + +void getGLXEntryPoints() +{ + GET_PROC_EXT(glXSwapIntervalEXT); +} + +char const *getGLXExtensionsString() +{ + return glXQueryExtensionsString(QX11Info::display(), QX11Info::appScreen()); +} + +void setXSwapInterval(int interval) +{ + if(glXSwapIntervalEXT) + { + DENG2_ASSERT(de::CanvasWindow::mainExists()); + glXSwapIntervalEXT(QX11Info::display(), de::CanvasWindow::main().canvas().winId(), interval); + } +} + +#endif // Q_WS_X11 diff --git a/doomsday/libgui/src/glframebuffer.cpp b/doomsday/libgui/src/glframebuffer.cpp index 1eeaf2b57b..9093049876 100644 --- a/doomsday/libgui/src/glframebuffer.cpp +++ b/doomsday/libgui/src/glframebuffer.cpp @@ -17,6 +17,7 @@ */ #include "de/GLFramebuffer" +#include "de/GuiApp" #include #include @@ -30,6 +31,7 @@ DENG2_STATIC_PROPERTY(DefaultSampleCount, int) DENG2_PIMPL(GLFramebuffer) , DENG2_OBSERVES(DefaultSampleCount, Change) +, DENG2_OBSERVES(GuiApp, GLContextChange) { Image::Format colorFormat; Size size; @@ -53,11 +55,22 @@ DENG2_PIMPL(GLFramebuffer) , uBufTex ("uTex", GLUniform::Sampler2D) { pDefaultSampleCount.audienceForChange += this; + //DENG2_GUI_APP->audienceForGLContextChange += this; } ~Instance() { pDefaultSampleCount.audienceForChange -= this; + //DENG2_GUI_APP->audienceForGLContextChange -= this; + } + + void appGLContextChanged() + { + /* + qDebug() << "rebooting FB" << thisPublic << self.isReady() << target.glName() << target.isReady() << size.asText(); + self.glDeinit(); + self.glInit(); + */ } int sampleCount() const diff --git a/doomsday/libgui/src/glinfo.cpp b/doomsday/libgui/src/glinfo.cpp index aad4257b63..236bcf9148 100644 --- a/doomsday/libgui/src/glinfo.cpp +++ b/doomsday/libgui/src/glinfo.cpp @@ -30,6 +30,12 @@ namespace de { static GLInfo info; +static void glDebugOut(GLenum source, GLenum type, GLuint id, GLenum severity, + GLsizei length, GLchar const *message, void const *userParam) +{ + qWarning() << "[GL]" << String(message, length); +} + DENG2_PIMPL_NOREF(GLInfo) { bool inited; @@ -94,6 +100,12 @@ DENG2_PIMPL_NOREF(GLInfo) return true; #endif +#ifdef Q_WS_X11 + // Check GLX specific extensions. + if(checkExtensionString(ext, (GLubyte const *) getGLXExtensionsString())) + return true; +#endif + return checkExtensionString(ext, glGetString(GL_EXTENSIONS)); } @@ -102,6 +114,7 @@ DENG2_PIMPL_NOREF(GLInfo) if(inited) return; // Extensions. + ext.ARB_debug_output = query("GL_ARB_debug_output"); ext.ARB_framebuffer_object = query("GL_ARB_framebuffer_object"); ext.ARB_texture_env_combine = query("GL_ARB_texture_env_combine") || query("GL_EXT_texture_env_combine"); ext.ARB_texture_non_power_of_two = query("GL_ARB_texture_non_power_of_two"); @@ -124,6 +137,10 @@ DENG2_PIMPL_NOREF(GLInfo) ext.Windows_EXT_swap_control = query("WGL_EXT_swap_control"); #endif +#ifdef Q_WS_X11 + ext.X11_EXT_swap_control = query("GLX_EXT_swap_control"); +#endif + // Limits. glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint *) &lim.maxTexSize); glGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint *) &lim.maxTexUnits); @@ -142,6 +159,12 @@ DENG2_PIMPL_NOREF(GLInfo) LOG_GL_NOTE("Using requested maximum texture size of %i x %i") << lim.maxTexSize << lim.maxTexSize; } + if(CommandLine_Exists("-gldebug")) + { + /// @todo The GL context is not created with the debug output bit. -jk + glDebugMessageCallbackARB(glDebugOut, NULL); + } + inited = true; } }; diff --git a/doomsday/libgui/src/glprogram.cpp b/doomsday/libgui/src/glprogram.cpp index b6f523e8fb..b4d736a903 100644 --- a/doomsday/libgui/src/glprogram.cpp +++ b/doomsday/libgui/src/glprogram.cpp @@ -21,6 +21,7 @@ #include "de/GLBuffer" #include "de/GLShader" #include "de/GLTexture" +#include "de/GuiApp" #include "de/gui/opengl.h" #include #include @@ -32,9 +33,10 @@ namespace de { using namespace internal; -DENG2_PIMPL(GLProgram), -DENG2_OBSERVES(GLUniform, ValueChange), -DENG2_OBSERVES(GLUniform, Deletion) +DENG2_PIMPL(GLProgram) +, DENG2_OBSERVES(GLUniform, ValueChange) +, DENG2_OBSERVES(GLUniform, Deletion) +, DENG2_OBSERVES(GuiApp, GLContextChange) { typedef QSet Uniforms; typedef QList UniformList; @@ -48,19 +50,35 @@ DENG2_OBSERVES(GLUniform, Deletion) GLuint name; Shaders shaders; bool inUse; + bool needRebuild; Instance(Public *i) - : Base(i), - texturesChanged(false), - name(0), - inUse(false) - {} + : Base(i) + , texturesChanged(false) + , name(0) + , inUse(false) + , needRebuild(false) + { + //DENG2_GUI_APP->audienceForGLContextChange += this; + } ~Instance() { + //DENG2_GUI_APP->audienceForGLContextChange -= this; release(); } + void appGLContextChanged() + { + /* + if(name && !needRebuild) + { + qDebug() << "rebuild program" << name << "before use"; + self.rebuildBeforeNextUse(); + } + */ + } + void alloc() { if(!name) @@ -95,6 +113,7 @@ DENG2_OBSERVES(GLUniform, Deletion) DENG2_ASSERT(shader->isReady()); alloc(); glAttachShader(name, shader->glName()); + LIBGUI_ASSERT_GL_OK(); shaders.insert(holdRef(shader)); } @@ -242,6 +261,28 @@ DENG2_OBSERVES(GLUniform, Deletion) } } + void rebuild() + { + qDebug() << "Rebuilding GL program" << name; + + if(name) + { + glDeleteProgram(name); + name = 0; + } + + alloc(); + + foreach(GLShader const *shader, shaders) + { + glAttachShader(name, shader->glName()); + LIBGUI_ASSERT_GL_OK(); + } + + bindVertexAttribs(); + markAllBoundUniformsChanged(); + } + void uniformValueChanged(GLUniform &uniform) { changed.insert(&uniform); @@ -288,6 +329,16 @@ GLProgram &GLProgram::build(IByteArray const &vertexShaderSource, refless(new GLShader(GLShader::Fragment, fragmentShaderSource))); } +void GLProgram::rebuildBeforeNextUse() +{ + d->needRebuild = true; +} + +void GLProgram::rebuild() +{ + d->rebuild(); +} + GLProgram &GLProgram::operator << (GLUniform const &uniform) { return bind(uniform); @@ -333,9 +384,20 @@ GLProgram &GLProgram::unbind(GLUniform const &uniform) void GLProgram::beginUse() const { + LIBGUI_ASSERT_GL_OK(); + DENG2_ASSERT_IN_MAIN_THREAD(); + DENG2_ASSERT(QGLContext::currentContext() != 0); DENG2_ASSERT(isReady()); DENG2_ASSERT(!d->inUse); + if(d->needRebuild) + { + d->needRebuild = false; + const_cast(this)->rebuild(); + } + + DENG2_ASSERT(glIsProgram(d->name)); + d->inUse = true; // The program is now ready for use. diff --git a/doomsday/libgui/src/glshader.cpp b/doomsday/libgui/src/glshader.cpp index 83f4145ba7..41b08b12ca 100644 --- a/doomsday/libgui/src/glshader.cpp +++ b/doomsday/libgui/src/glshader.cpp @@ -17,6 +17,7 @@ */ #include "de/GLShader" +#include "de/GuiApp" #include "de/gui/opengl.h" #include #include @@ -24,15 +25,20 @@ namespace de { DENG2_PIMPL(GLShader) +, DENG2_OBSERVES(GuiApp, GLContextChange) { GLuint name; Type type; + Block compiledSource; Instance(Public *i) : Base(i), name(0), type(Vertex) - {} + { + //DENG2_GUI_APP->audienceForGLContextChange += this; + } ~Instance() { + //DENG2_GUI_APP->audienceForGLContextChange -= this; release(); } @@ -58,6 +64,15 @@ DENG2_PIMPL(GLShader) } self.setState(Asset::NotReady); } + + void appGLContextChanged() + { + /* + qDebug() << "Recompiling shader" << name; + + self.recompile(); + */ + } }; GLShader::GLShader() : d(new Instance(this)) @@ -103,6 +118,9 @@ void GLShader::compile(Type shaderType, IByteArray const &source) setState(NotReady); + // Keep a copy of the source for possible recompilation. + d->compiledSource = source; + d->type = shaderType; d->alloc(); @@ -147,4 +165,11 @@ void GLShader::compile(Type shaderType, IByteArray const &source) setState(Ready); } +void GLShader::recompile() +{ + d->release(); + compile(d->type, d->compiledSource); + DENG2_ASSERT(isReady()); +} + } // namespace de diff --git a/doomsday/libgui/src/glstate.cpp b/doomsday/libgui/src/glstate.cpp index fe9b9072f8..976a34b403 100644 --- a/doomsday/libgui/src/glstate.cpp +++ b/doomsday/libgui/src/glstate.cpp @@ -303,6 +303,8 @@ DENG2_PIMPL(GLState) default: break; } + + LIBGUI_ASSERT_GL_OK(); } void removeRedundancies(BitField::Ids &changed) @@ -569,6 +571,8 @@ Rectangleui GLState::scissorRect() const void GLState::apply() const { + LIBGUI_ASSERT_GL_OK(); + bool forceViewportAndScissor = false; // Update the render target. @@ -594,6 +598,8 @@ void GLState::apply() const } } + LIBGUI_ASSERT_GL_OK(); + // Determine which properties have changed. BitField::Ids changed; if(!currentProps.size()) diff --git a/doomsday/libgui/src/gltarget.cpp b/doomsday/libgui/src/gltarget.cpp index 08b2aeaee8..2d0566d7a4 100644 --- a/doomsday/libgui/src/gltarget.cpp +++ b/doomsday/libgui/src/gltarget.cpp @@ -504,6 +504,7 @@ void GLTarget::configure(Flags const &attachment, GLTexture &texture, Flags cons void GLTarget::glBind() const { + LIBGUI_ASSERT_GL_OK(); DENG2_ASSERT(isReady()); if(!isReady()) return; @@ -514,9 +515,12 @@ void GLTarget::glBind() const } else { + DENG2_ASSERT(!d->fbo || glIsFramebuffer(d->fbo)); + //qDebug() << "GLTarget: binding FBO" << d->fbo; glBindFramebuffer(GLInfo::extensions().EXT_framebuffer_blit? GL_DRAW_FRAMEBUFFER_EXT : GL_FRAMEBUFFER, d->fbo); + LIBGUI_ASSERT_GL_OK(); } } diff --git a/doomsday/libgui/src/guiapp.cpp b/doomsday/libgui/src/guiapp.cpp index ad9d34d5d2..79b897e68a 100644 --- a/doomsday/libgui/src/guiapp.cpp +++ b/doomsday/libgui/src/guiapp.cpp @@ -66,6 +66,12 @@ void GuiApp::notifyDisplayModeChanged() emit displayModeChanged(); } +void GuiApp::notifyGLContextChanged() +{ + qDebug() << "notifying GL context change" << audienceForGLContextChange.size(); + DENG2_FOR_AUDIENCE(GLContextChange, i) i->appGLContextChanged(); +} + int GuiApp::execLoop() { LOGDEV_NOTE("Starting GuiApp event loop..."); diff --git a/doomsday/tests/test_glsandbox/test_glsandbox.pro b/doomsday/tests/test_glsandbox/test_glsandbox.pro index 677cd46abf..254c388f90 100644 --- a/doomsday/tests/test_glsandbox/test_glsandbox.pro +++ b/doomsday/tests/test_glsandbox/test_glsandbox.pro @@ -18,17 +18,17 @@ deployTest($$TARGET) gfx.files = testpic.png -win32 { - gfx.path = $$DENG_DATA_DIR/graphics - INSTALLS += gfx -} -else:macx { +macx { linkBinaryToBundledLibdeng2($${TARGET}.app/Contents/MacOS/$${TARGET}) linkBinaryToBundledLibdengGui($${TARGET}.app/Contents/MacOS/$${TARGET}) gfx.path = Contents/Resources/graphics QMAKE_BUNDLE_DATA += gfx } +else { + gfx.path = $$DENG_DATA_DIR/graphics + INSTALLS += gfx +} HEADERS += \ testwindow.h