diff --git a/libopenage/gamestate/resource.cpp b/libopenage/gamestate/resource.cpp index c0c7d5b9e7..4abaa5fba0 100644 --- a/libopenage/gamestate/resource.cpp +++ b/libopenage/gamestate/resource.cpp @@ -1,5 +1,6 @@ // Copyright 2015-2017 the openage authors. See copying.md for legal info. +#include #include #include "resource.h" diff --git a/libopenage/gui/guisys/CMakeLists.txt b/libopenage/gui/guisys/CMakeLists.txt index 2835522f22..1cb654b3e5 100644 --- a/libopenage/gui/guisys/CMakeLists.txt +++ b/libopenage/gui/guisys/CMakeLists.txt @@ -21,13 +21,16 @@ list(APPEND QT_SDL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/private/game_logic_caller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_application_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_callback.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_ctx_setup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_dedicated_thread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_engine_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_event_queue_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_image_provider_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_input_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_renderer_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_rendering_setup_routines.cpp ${CMAKE_CURRENT_SOURCE_DIR}/private/gui_subtree_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/private/opengl_debug_logger.cpp ) list(APPEND QT_SDL_SOURCES diff --git a/libopenage/gui/guisys/private/gui_ctx_setup.cpp b/libopenage/gui/guisys/private/gui_ctx_setup.cpp new file mode 100644 index 0000000000..942ff698c9 --- /dev/null +++ b/libopenage/gui/guisys/private/gui_ctx_setup.cpp @@ -0,0 +1,105 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "gui_ctx_setup.h" + +#include + +#include + +#include "platforms/context_extraction.h" +#include "opengl_debug_logger.h" + +namespace qtsdl { + +CtxExtractionException::CtxExtractionException(const std::string &what_arg) + : + std::runtime_error{what_arg} { +} + +QOpenGLContext* CtxExtractionMode::get_ctx() { + return &this->ctx; +} + +GuiUniqueRenderingContext::GuiUniqueRenderingContext(SDL_Window *window) + : + CtxExtractionMode{} { + + QVariant handle; + WId id; + + std::tie(handle, id) = extract_native_context(window); + + if (handle.isValid()) { + // pass the SDL opengl context so qt can use it + this->ctx.setNativeHandle(handle); + this->ctx.create(); + assert(this->ctx.isValid()); + + // reuse the sdl window + QWindow *w = QWindow::fromWinId(id); + w->setSurfaceType(QSurface::OpenGLSurface); + + if (this->ctx.makeCurrent(w)) { + return; + } + } + + throw CtxExtractionException("adding GUI to the main rendering context failed"); +} + +void GuiUniqueRenderingContext::pre_render() { +} + +void GuiUniqueRenderingContext::post_render() { +} + +GuiSeparateRenderingContext::GuiSeparateRenderingContext(SDL_Window *window) + : + CtxExtractionMode{} { + + QVariant handle; + + std::tie(handle, this->make_current_back) = extract_native_context_and_switchback_func(window); + + if (handle.isValid()) { + this->main_ctx.setNativeHandle(handle); + this->main_ctx.create(); + assert(this->main_ctx.isValid()); + + auto context_debug_parameters = get_current_opengl_debug_parameters(this->main_ctx); + + this->ctx.setFormat(this->main_ctx.format()); + this->ctx.setShareContext(&this->main_ctx); + this->ctx.create(); + assert(this->ctx.isValid()); + assert(!(this->main_ctx.format().options() ^ this->ctx.format().options()).testFlag(QSurfaceFormat::DebugContext)); + + this->offscreen_surface.setFormat(this->ctx.format()); + this->offscreen_surface.create(); + + this->pre_render(); + apply_opengl_debug_parameters(context_debug_parameters, this->ctx); + this->post_render(); + } else { + throw CtxExtractionException("creating separate context for GUI failed"); + } +} + +GuiSeparateRenderingContext::~GuiSeparateRenderingContext() { + this->pre_render(); + this->ctx_logger.reset(); + this->post_render(); +} + +void GuiSeparateRenderingContext::pre_render() { + if (!this->ctx.makeCurrent(&this->offscreen_surface)) { + assert(false); + return; + } +} + +void GuiSeparateRenderingContext::post_render() { + this->make_current_back(); +} + +} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_ctx_setup.h b/libopenage/gui/guisys/private/gui_ctx_setup.h new file mode 100644 index 0000000000..bcc88e932a --- /dev/null +++ b/libopenage/gui/guisys/private/gui_ctx_setup.h @@ -0,0 +1,94 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include +#include +#include + +#include +#include + +struct SDL_Window; + +QT_FORWARD_DECLARE_CLASS(QOpenGLDebugLogger) + +namespace qtsdl { + +class CtxExtractionException : public std::runtime_error { +public: + explicit CtxExtractionException(const std::string &what_arg); +}; + +/** + * Abstract base for the method of getting a Qt-usable context. + */ +class CtxExtractionMode { +public: + virtual ~CtxExtractionMode() { + } + + /** + * @return context that can be used by Qt + */ + QOpenGLContext* get_ctx(); + + /** + * Function that must be called before rendering the GUI. + */ + virtual void pre_render() = 0; + + /** + * Function that must be called after rendering the GUI. + */ + virtual void post_render() = 0; + +protected: + QOpenGLContext ctx; +}; + +/** + * Use the same context to render the GUI. + */ +class GuiUniqueRenderingContext : public CtxExtractionMode { +public: + explicit GuiUniqueRenderingContext(SDL_Window *window); + + virtual void pre_render() override; + virtual void post_render() override; +}; + +/** + * Create a separate context to render the GUI, make it shared with the main context. + */ +class GuiSeparateRenderingContext : public CtxExtractionMode { +public: + explicit GuiSeparateRenderingContext(SDL_Window *window); + virtual ~GuiSeparateRenderingContext(); + + virtual void pre_render() override; + virtual void post_render() override; + +private: + /** + * GL context of the game + */ + QOpenGLContext main_ctx; + + /** + * GL debug logger of the GL context of the GUI + */ + std::unique_ptr ctx_logger; + + /** + * Function to make the game context current + */ + std::function make_current_back; + + /** + * Surface that is needed to make the GUI context current + */ + QOffscreenSurface offscreen_surface; +}; + +} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_renderer_impl.cpp b/libopenage/gui/guisys/private/gui_renderer_impl.cpp index 2d27e97d81..c74ae0f507 100644 --- a/libopenage/gui/guisys/private/gui_renderer_impl.cpp +++ b/libopenage/gui/guisys/private/gui_renderer_impl.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2017 the openage authors. See copying.md for legal info. #include "gui_renderer_impl.h" @@ -12,7 +12,6 @@ #include #include "../public/gui_renderer.h" -#include "platforms/context_extraction.h" namespace qtsdl { @@ -77,6 +76,7 @@ void EventHandlingQuickWindow::on_resized(const QSize &size) { GuiRendererImpl::GuiRendererImpl(SDL_Window *window) : QObject{}, + gui_rendering_setup_routines{window}, need_fbo_resize{true}, need_sync{}, need_render{}, @@ -85,26 +85,6 @@ GuiRendererImpl::GuiRendererImpl(SDL_Window *window) this->moveToThread(QCoreApplication::instance()->thread()); - QVariant handle; - WId id; - - std::tie(handle, id) = extract_native_context(window); - - // pass the SDL opengl context so qt can use it - this->ctx = std::make_unique(); - this->ctx->setNativeHandle(handle); - this->ctx->create(); - assert(this->ctx->isValid()); - - // reuse the sdl window - QWindow *w = QWindow::fromWinId(id); - w->setSurfaceType(QSurface::OpenGLSurface); - - if (!this->ctx->makeCurrent(w)) { - assert(false); - return; - } - QObject::connect(&this->render_control, &QQuickRenderControl::renderRequested, [&] () { this->need_render = true; }); @@ -125,9 +105,9 @@ GuiRendererImpl::GuiRendererImpl(SDL_Window *window) QObject::connect(&*this->window, &QQuickWindow::widthChanged, [this] { this->new_fbo_width = this->window->width(); this->need_fbo_resize = true; }); QObject::connect(&*this->window, &QQuickWindow::heightChanged, [this] { this->new_fbo_height = this->window->height(); this->need_fbo_resize = true; }); - this->render_control.initialize(&*this->ctx); + GuiRenderingCtxActivator activate_render(this->gui_rendering_setup_routines); - assert(this->ctx->isValid()); + this->render_control.initialize(this->gui_rendering_setup_routines.get_ctx()); } void GuiRendererImpl::on_scene_changed() { @@ -137,7 +117,7 @@ void GuiRendererImpl::on_scene_changed() { } void GuiRendererImpl::reinit_fbo_if_needed() { - assert(QThread::currentThread() == this->ctx->thread()); + assert(QThread::currentThread() == this->gui_rendering_setup_routines.get_ctx()->thread()); if (this->need_fbo_resize) { this->fbo = std::make_unique(QSize(this->new_fbo_width, this->new_fbo_height), QOpenGLFramebufferObject::CombinedDepthStencil); @@ -163,6 +143,8 @@ GuiRendererImpl* GuiRendererImpl::impl(GuiRenderer *renderer) { } GLuint GuiRendererImpl::render() { + GuiRenderingCtxActivator activate_render(this->gui_rendering_setup_routines); + this->reinit_fbo_if_needed(); // QQuickRenderControl::sync() must be called from the render thread while the gui thread is stopped. diff --git a/libopenage/gui/guisys/private/gui_renderer_impl.h b/libopenage/gui/guisys/private/gui_renderer_impl.h index 2584e626fb..2f775f262a 100644 --- a/libopenage/gui/guisys/private/gui_renderer_impl.h +++ b/libopenage/gui/guisys/private/gui_renderer_impl.h @@ -1,9 +1,8 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2017 the openage authors. See copying.md for legal info. #pragma once #include -#include #include #include #include @@ -14,9 +13,10 @@ #include #include +#include "gui_rendering_setup_routines.h" + struct SDL_Window; -QT_FORWARD_DECLARE_CLASS(QOpenGLContext) QT_FORWARD_DECLARE_CLASS(QOpenGLFramebufferObject) namespace qtsdl { @@ -111,9 +111,10 @@ private slots: void reinit_fbo_if_needed(); /** - * GL context of the game + * Contains rendering context + * Use GuiRenderingCtxActivator to enable it */ - std::unique_ptr ctx; + GuiRenderingSetupRoutines gui_rendering_setup_routines; /** * Contains scene graph of the GUI diff --git a/libopenage/gui/guisys/private/gui_rendering_setup_routines.cpp b/libopenage/gui/guisys/private/gui_rendering_setup_routines.cpp new file mode 100644 index 0000000000..75962cfa1a --- /dev/null +++ b/libopenage/gui/guisys/private/gui_rendering_setup_routines.cpp @@ -0,0 +1,68 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "gui_rendering_setup_routines.h" + +#include + +#include + +#include "gui_ctx_setup.h" + +namespace qtsdl { + +GuiRenderingSetupRoutines::GuiRenderingSetupRoutines(SDL_Window *window) { + try { + this->ctx_extraction_mode = std::make_unique(window); + } catch (const CtxExtractionException&) { + + qInfo() << "Falling back to separate render context for GUI"; + + try { + this->ctx_extraction_mode = std::make_unique(window); + } catch (const CtxExtractionException&) { + assert(false && "setting up context for GUI failed"); + } + } +} + +GuiRenderingSetupRoutines::~GuiRenderingSetupRoutines() { +} + +QOpenGLContext* GuiRenderingSetupRoutines::get_ctx() { + return this->ctx_extraction_mode->get_ctx(); +} + +void GuiRenderingSetupRoutines::pre_render() { + this->ctx_extraction_mode->pre_render(); +} + +void GuiRenderingSetupRoutines::post_render() { + this->ctx_extraction_mode->post_render(); +} + +GuiRenderingCtxActivator::GuiRenderingCtxActivator(GuiRenderingSetupRoutines &rendering_setup_routines) + : + rendering_setup_routines{&rendering_setup_routines} { + + this->rendering_setup_routines->pre_render(); +} + +GuiRenderingCtxActivator::~GuiRenderingCtxActivator() { + if (this->rendering_setup_routines) + this->rendering_setup_routines->post_render(); +} + +GuiRenderingCtxActivator::GuiRenderingCtxActivator(GuiRenderingCtxActivator&& o) + : + rendering_setup_routines{o.rendering_setup_routines} { + + o.rendering_setup_routines = nullptr; +} + +GuiRenderingCtxActivator& GuiRenderingCtxActivator::operator=(GuiRenderingCtxActivator&& o) { + this->rendering_setup_routines = o.rendering_setup_routines; + o.rendering_setup_routines = nullptr; + return *this; +} + +} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/gui_rendering_setup_routines.h b/libopenage/gui/guisys/private/gui_rendering_setup_routines.h new file mode 100644 index 0000000000..e392d70cb1 --- /dev/null +++ b/libopenage/gui/guisys/private/gui_rendering_setup_routines.h @@ -0,0 +1,57 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include + +struct SDL_Window; + +QT_FORWARD_DECLARE_CLASS(QOpenGLContext) + +namespace qtsdl { + +class CtxExtractionMode; + +class GuiRenderingCtxActivator; + +/** + * Returns a GL context usable by Qt classes. + * Provides pre- and post-rendering functions to make the context usable for GUI rendering. + */ +class GuiRenderingSetupRoutines { +public: + explicit GuiRenderingSetupRoutines(SDL_Window *window); + ~GuiRenderingSetupRoutines(); + + QOpenGLContext* get_ctx(); + +private: + friend class GuiRenderingCtxActivator; + void pre_render(); + void post_render(); + + std::unique_ptr ctx_extraction_mode; +}; + +/** + * Prepares the context for rendering the GUI for one frame. + * Activator must be destroyed as soon as the GUI has executed its frame render call. + */ +class GuiRenderingCtxActivator { +public: + explicit GuiRenderingCtxActivator(GuiRenderingSetupRoutines &rendering_setup_routines); + ~GuiRenderingCtxActivator(); + + GuiRenderingCtxActivator(GuiRenderingCtxActivator&& o); + GuiRenderingCtxActivator& operator=(GuiRenderingCtxActivator&& o); + +private: + GuiRenderingCtxActivator(const GuiRenderingCtxActivator&) = delete; + GuiRenderingCtxActivator& operator=(const GuiRenderingCtxActivator&) = delete; + + GuiRenderingSetupRoutines *rendering_setup_routines; +}; + +} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/opengl_debug_logger.cpp b/libopenage/gui/guisys/private/opengl_debug_logger.cpp new file mode 100644 index 0000000000..2beb5d28ee --- /dev/null +++ b/libopenage/gui/guisys/private/opengl_debug_logger.cpp @@ -0,0 +1,48 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#include "opengl_debug_logger.h" + +#include +#include + +#ifdef __APPLE__ +// from https://www.khronos.org/registry/OpenGL/api/GL/glext.h +#define GL_DEBUG_CALLBACK_FUNCTION 0x8244 +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_TYPE_ERROR 0x824C +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR 0x824E +#endif + +namespace qtsdl { + +gl_debug_parameters get_current_opengl_debug_parameters(const QOpenGLContext ¤t_source_context) { + gl_debug_parameters params{}; + + if (current_source_context.versionFunctions()) + if ((params.is_debug = current_source_context.format().options().testFlag(QSurfaceFormat::DebugContext))) { + glGetPointerv(GL_DEBUG_CALLBACK_FUNCTION, ¶ms.callback); + params.synchronous = glIsEnabled(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } + + return params; +} + +void apply_opengl_debug_parameters(gl_debug_parameters params, QOpenGLContext ¤t_dest_context) { + if (params.is_debug && params.callback) { + if (auto functions = current_dest_context.versionFunctions()) { + functions->initializeOpenGLFunctions(); + + functions->glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE); + + functions->glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE); + functions->glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE); + + functions->glDebugMessageCallback((GLDEBUGPROC)params.callback, nullptr); + + if (params.synchronous) + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } + } +} + +} // namespace qtsdl diff --git a/libopenage/gui/guisys/private/opengl_debug_logger.h b/libopenage/gui/guisys/private/opengl_debug_logger.h new file mode 100644 index 0000000000..7c6130f2b1 --- /dev/null +++ b/libopenage/gui/guisys/private/opengl_debug_logger.h @@ -0,0 +1,43 @@ +// Copyright 2017-2017 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +QT_FORWARD_DECLARE_CLASS(QOpenGLContext) + +namespace qtsdl { +struct gl_debug_parameters { + /** + * True if the GL context is a debug context + */ + bool is_debug; + + /** + * Function that GL context uses to report debug messages + */ + GLvoid *callback; + + /** + * True if debug callback calling method is chosen to be synchronous + */ + bool synchronous; +}; + +/** + * Get debugging settings of the current GL context + * + * @param current_source_context current GL context + * @return debugging settings + */ +gl_debug_parameters get_current_opengl_debug_parameters(const QOpenGLContext ¤t_source_context); + +/** + * Create a GL logger in the current GL context + * + * @param params debugging settings + * @param current_dest_context current GL context to which parameters will be applied + */ +void apply_opengl_debug_parameters(gl_debug_parameters params, QOpenGLContext ¤t_dest_context); + +} diff --git a/libopenage/gui/guisys/private/platforms/context_extraction.h b/libopenage/gui/guisys/private/platforms/context_extraction.h index 224c081121..baf13818bb 100644 --- a/libopenage/gui/guisys/private/platforms/context_extraction.h +++ b/libopenage/gui/guisys/private/platforms/context_extraction.h @@ -1,8 +1,9 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2017 the openage authors. See copying.md for legal info. #pragma once #include +#include #include #include @@ -11,6 +12,14 @@ struct SDL_Window; namespace qtsdl { +/** + * @return current context (or null) and id of the window + */ std::tuple extract_native_context(SDL_Window *window); +/** + * @return current context (or null) and function to get it back to the window + */ +std::tuple> extract_native_context_and_switchback_func(SDL_Window *window); + } // namespace qtsdl diff --git a/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm b/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm index 5ba2ac4918..d60fb6d03a 100644 --- a/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm +++ b/libopenage/gui/guisys/private/platforms/context_extraction_cocoa.mm @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2017 the openage authors. See copying.md for legal info. #include @@ -12,6 +12,10 @@ namespace qtsdl { std::tuple extract_native_context(SDL_Window *window) { + return std::tuple{}; +} + +std::tuple> extract_native_context_and_switchback_func(SDL_Window *window) { assert(window); NSOpenGLContext *current_context = [NSOpenGLContext currentContext]; @@ -25,11 +29,14 @@ if (SDL_GetWindowWMInfo(window, &wm_info)) { NSWindow *ns_window = wm_info.info.cocoa.window; view = [ns_window contentView]; + assert(view); + + return std::make_tuple(QVariant::fromValue(QCocoaNativeContext(current_context)), [current_context] { + [current_context makeCurrentContext]; + }); } - assert(view); - - return std::make_tuple(QVariant::fromValue(QCocoaNativeContext(current_context)), reinterpret_cast(view)); + return std::tuple>{}; } } // namespace qtsdl diff --git a/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp b/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp index b08288a35a..623a72257d 100644 --- a/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp +++ b/libopenage/gui/guisys/private/platforms/context_extraction_x11.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2016 the openage authors. See copying.md for legal info. +// Copyright 2015-2017 the openage authors. See copying.md for legal info. #include @@ -24,15 +24,38 @@ std::tuple extract_native_context(SDL_Window *window) { current_context = glXGetCurrentContext(); assert(current_context); + + return std::make_tuple( + QVariant::fromValue( + QGLXNativeContext(current_context, + wm_info.info.x11.display, + wm_info.info.x11.window)), + wm_info.info.x11.window + ); + } + + return std::tuple{}; +} + +std::tuple> extract_native_context_and_switchback_func(SDL_Window *window) { + assert(window); + + GLXContext current_context; + SDL_SysWMinfo wm_info; + SDL_VERSION(&wm_info.version); + + if (SDL_GetWindowWMInfo(window, &wm_info)) { + assert(wm_info.info.x11.display); + + current_context = glXGetCurrentContext(); + assert(current_context); + + return std::make_tuple(QVariant::fromValue(QGLXNativeContext(current_context, wm_info.info.x11.display, wm_info.info.x11.window)), [wm_info, current_context] { + glXMakeCurrent(wm_info.info.x11.display, wm_info.info.x11.window, current_context); + }); } - return std::make_tuple( - QVariant::fromValue( - QGLXNativeContext(current_context, - wm_info.info.x11.display, - wm_info.info.x11.window)), - wm_info.info.x11.window - ); + return std::tuple>{}; } } // namespace qtsdl