mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-11 18:34:58 +01:00
Management of inputs in Control, Management of callbacks creator per input in Source, Saving and Loading in Session, Unified renaming of SourceCallbacks, User interface window for creating and editing input mapping from Keyboard and Numerical keypad, with appropriate Settings.
930 lines
30 KiB
C++
930 lines
30 KiB
C++
/*
|
|
* This file is part of vimix - video live mixer
|
|
*
|
|
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
|
*
|
|
* 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 3 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 <https://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
#include <cstring>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <chrono>
|
|
#include <stdlib.h>
|
|
|
|
// Desktop OpenGL function loader
|
|
#include <glad/glad.h> // Initialized with gladLoadGLLoader()
|
|
|
|
// Include glfw3.h after our OpenGL definitions
|
|
#define GLFW_INCLUDE_GLEXT
|
|
#include <GLFW/glfw3.h>
|
|
|
|
#ifdef APPLE
|
|
#include "osx/CocoaToolkit.h"
|
|
#define GLFW_EXPOSE_NATIVE_COCOA
|
|
#define GLFW_EXPOSE_NATIVE_NSGL
|
|
#else
|
|
#define GLFW_EXPOSE_NATIVE_X11
|
|
#define GLFW_EXPOSE_NATIVE_GLX
|
|
#endif
|
|
#include <GLFW/glfw3native.h>
|
|
|
|
#include <glm/gtc/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
|
|
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
|
|
|
|
// Include GStreamer
|
|
#include <gst/gl/gl.h>
|
|
#include <gst/gl/gstglcontext.h>
|
|
|
|
#ifdef GLFW_EXPOSE_NATIVE_COCOA
|
|
//#include <gst/gl/cocoa/gstgldisplay_cocoa.h>
|
|
#endif
|
|
#ifdef GLFW_EXPOSE_NATIVE_GLX
|
|
#include <gst/gl/x11/gstgldisplay_x11.h>
|
|
#endif
|
|
|
|
// standalone image loader
|
|
#include <stb_image.h>
|
|
|
|
// vimix
|
|
#include "defines.h"
|
|
#include "Log.h"
|
|
#include "Resource.h"
|
|
#include "Settings.h"
|
|
#include "Primitives.h"
|
|
#include "Mixer.h"
|
|
#include "SystemToolkit.h"
|
|
#include "GstToolkit.h"
|
|
#include "UserInterfaceManager.h"
|
|
|
|
#include "RenderingManager.h"
|
|
|
|
#ifdef USE_GST_OPENGL_SYNC_HANDLER
|
|
//
|
|
// Discarded because not working under OSX - kept in case it would become useful
|
|
//
|
|
// Linking pipeline to the rendering instance ensures the opengl contexts
|
|
// created by gstreamer inside plugins (e.g. glsinkbin) is the same
|
|
//
|
|
static GstGLContext *global_gl_context = NULL;
|
|
static GstGLDisplay *global_display = NULL;
|
|
|
|
static GstBusSyncReply bus_sync_handler( GstBus *, GstMessage * msg, gpointer )
|
|
{
|
|
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_NEED_CONTEXT) {
|
|
const gchar* contextType;
|
|
gst_message_parse_context_type(msg, &contextType);
|
|
|
|
if (!g_strcmp0(contextType, GST_GL_DISPLAY_CONTEXT_TYPE)) {
|
|
GstContext *displayContext = gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE);
|
|
gst_context_set_gl_display(displayContext, global_display);
|
|
gst_element_set_context(GST_ELEMENT(msg->src), displayContext);
|
|
gst_context_unref (displayContext);
|
|
|
|
g_info ("Managed %s\n", contextType);
|
|
}
|
|
if (!g_strcmp0(contextType, "gst.gl.app_context")) {
|
|
GstContext *appContext = gst_context_new("gst.gl.app_context", TRUE);
|
|
GstStructure* structure = gst_context_writable_structure(appContext);
|
|
gst_structure_set(structure, "context", GST_TYPE_GL_CONTEXT, global_gl_context, nullptr);
|
|
gst_element_set_context(GST_ELEMENT(msg->src), appContext);
|
|
gst_context_unref (appContext);
|
|
|
|
g_info ("Managed %s\n", contextType);
|
|
}
|
|
}
|
|
|
|
gst_message_unref (msg);
|
|
|
|
return GST_BUS_DROP;
|
|
}
|
|
|
|
void Rendering::LinkPipeline( GstPipeline *pipeline )
|
|
{
|
|
// capture bus signals to force a unique opengl context for all GST elements
|
|
GstBus* m_bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
|
gst_bus_set_sync_handler (m_bus, (GstBusSyncHandler) bus_sync_handler, pipeline, NULL);
|
|
gst_object_unref (m_bus);
|
|
}
|
|
#endif
|
|
|
|
|
|
std::map<GLFWwindow *, RenderingWindow*> GLFW_window_;
|
|
|
|
static void glfw_error_callback(int error, const char* description)
|
|
{
|
|
Log::Error("Glfw Error %d: %s", error, description);
|
|
}
|
|
|
|
static void WindowRefreshCallback( GLFWwindow * )
|
|
{
|
|
Rendering::manager().draw();
|
|
}
|
|
|
|
static void WindowResizeCallback( GLFWwindow *w, int width, int height)
|
|
{
|
|
if (Rendering::manager().mainWindow().window() == w) {
|
|
// UI manager tries to keep windows in the workspace
|
|
WorkspaceWindow::notifyWorkspaceSizeChanged(GLFW_window_[w]->previous_size.x, GLFW_window_[w]->previous_size.y, width, height);
|
|
GLFW_window_[w]->previous_size = glm::vec2(width, height);
|
|
}
|
|
|
|
int id = GLFW_window_[w]->index();
|
|
if (!Settings::application.windows[id].fullscreen) {
|
|
Settings::application.windows[id].w = width;
|
|
Settings::application.windows[id].h = height;
|
|
}
|
|
}
|
|
|
|
static void WindowMoveCallback( GLFWwindow *w, int x, int y)
|
|
{
|
|
int id = GLFW_window_[w]->index();
|
|
if (!Settings::application.windows[id].fullscreen) {
|
|
Settings::application.windows[id].x = x;
|
|
Settings::application.windows[id].y = y;
|
|
}
|
|
}
|
|
|
|
static void WindowToggleFullscreen( GLFWwindow *w, int button, int action, int)
|
|
{
|
|
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
|
|
{
|
|
static double seconds = 0.f;
|
|
// detect double clic
|
|
if ( glfwGetTime() - seconds < 0.2f ) {
|
|
// toggle fullscreen
|
|
GLFW_window_[w]->toggleFullscreen();
|
|
}
|
|
// for next clic detection
|
|
seconds = glfwGetTime();
|
|
}
|
|
}
|
|
|
|
static void WindowCloseCallback( GLFWwindow* w )
|
|
{
|
|
if (!UserInterface::manager().TryClose())
|
|
glfwSetWindowShouldClose(w, GLFW_FALSE);
|
|
}
|
|
|
|
void Rendering::close()
|
|
{
|
|
glfwSetWindowShouldClose(main_.window(), GLFW_TRUE);
|
|
}
|
|
|
|
Rendering::Rendering()
|
|
{
|
|
// main_window_ = nullptr;
|
|
request_screenshot_ = false;
|
|
}
|
|
|
|
bool Rendering::init()
|
|
{
|
|
// Setup window
|
|
glfwSetErrorCallback(glfw_error_callback);
|
|
if (!glfwInit()){
|
|
Log::Error("Failed to Initialize GLFW.");
|
|
return false;
|
|
}
|
|
|
|
// Decide GL+GLSL versions GL 3.3 + GLSL 150
|
|
glsl_version = "#version 150";
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
|
#if __APPLE__
|
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
|
#endif
|
|
|
|
//
|
|
// OpenGL Multisampling main window
|
|
//
|
|
glfwWindowHint(GLFW_SAMPLES, Settings::application.render.multisampling);
|
|
main_.init(0);
|
|
// set application icon
|
|
main_.setIcon("images/vimix_256x256.png");
|
|
// additional window callbacks for main window
|
|
glfwSetWindowCloseCallback( main_.window(), WindowCloseCallback );
|
|
glfwSetWindowRefreshCallback( main_.window(), WindowRefreshCallback );
|
|
glfwSetDropCallback( main_.window(), Rendering::FileDropped);
|
|
|
|
//
|
|
// Gstreamer setup
|
|
//
|
|
std::string plugins_path = SystemToolkit::cwd_path() + "gstreamer-1.0";
|
|
std::string plugins_scanner = SystemToolkit::cwd_path() + "gst-plugin-scanner" ;
|
|
if ( SystemToolkit::file_exists(plugins_path)) {
|
|
Log::Info("Found Gstreamer plugins in %s", plugins_path.c_str());
|
|
g_setenv ("GST_PLUGIN_SYSTEM_PATH", plugins_path.c_str(), TRUE);
|
|
g_setenv ("GST_PLUGIN_SCANNER", plugins_scanner.c_str(), TRUE);
|
|
}
|
|
std::string frei0r_path = SystemToolkit::cwd_path() + "frei0r-1" ;
|
|
if ( SystemToolkit::file_exists(frei0r_path)) {
|
|
Log::Info("Found Frei0r plugins in %s", frei0r_path.c_str());
|
|
g_setenv ("FREI0R_PATH", frei0r_path.c_str(), TRUE);
|
|
}
|
|
g_setenv ("GST_GL_API", "opengl3", TRUE);
|
|
gst_init (NULL, NULL);
|
|
|
|
// increase selection rank for GPU decoding plugins
|
|
std::list<std::string> gpuplugins = GstToolkit::enable_gpu_decoding_plugins(Settings::application.render.gpu_decoding);
|
|
if (Settings::application.render.gpu_decoding) {
|
|
if (gpuplugins.size() > 0) {
|
|
Log::Info("Found the following GPU decoding plugin(s):");
|
|
int i = 1;
|
|
for(auto it = gpuplugins.rbegin(); it != gpuplugins.rend(); it++, ++i)
|
|
Log::Info("%d. %s", i, (*it).c_str());
|
|
}
|
|
else {
|
|
Log::Info("No GPU decoding plugin found.");
|
|
}
|
|
}
|
|
#ifdef SYNC_GSTREAMER_OPENGL_CONTEXT
|
|
#if GST_GL_HAVE_PLATFORM_WGL
|
|
global_gl_context = gst_gl_context_new_wrapped (display, (guintptr) wglGetCurrentContext (),
|
|
GST_GL_PLATFORM_WGL, GST_GL_API_OPENGL);
|
|
#elif GST_GL_HAVE_PLATFORM_CGL
|
|
// global_display = GST_GL_DISPLAY ( glfwGetCocoaMonitor(main_.window()) );
|
|
global_display = GST_GL_DISPLAY (gst_gl_display_cocoa_new ());
|
|
|
|
global_gl_context = gst_gl_context_new_wrapped (global_display,
|
|
(guintptr) 0,
|
|
GST_GL_PLATFORM_CGL, GST_GL_API_OPENGL);
|
|
#elif GST_GL_HAVE_PLATFORM_GLX
|
|
global_display = (GstGLDisplay*) gst_gl_display_x11_new_with_display( glfwGetX11Display() );
|
|
global_gl_context = gst_gl_context_new_wrapped (global_display,
|
|
(guintptr) glfwGetGLXContext(main_.window()),
|
|
GST_GL_PLATFORM_GLX, GST_GL_API_OPENGL);
|
|
#endif
|
|
#endif
|
|
|
|
//
|
|
// output window
|
|
//
|
|
glfwWindowHint(GLFW_SAMPLES, 0); // no need for multisampling in displaying output
|
|
output_.init(1, main_.window());
|
|
output_.setIcon("images/vimix_256x256.png");
|
|
// special callbacks for user input in output window
|
|
glfwSetMouseButtonCallback( output_.window(), WindowToggleFullscreen);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void Rendering::show()
|
|
{
|
|
// show output window
|
|
output_.show();
|
|
|
|
// show main window
|
|
main_.show();
|
|
|
|
// show menu on first show
|
|
UserInterface::manager().showPannel(NAV_MENU);
|
|
}
|
|
|
|
bool Rendering::isActive()
|
|
{
|
|
return !glfwWindowShouldClose(main_.window());
|
|
}
|
|
|
|
|
|
void Rendering::pushFrontDrawCallback(RenderingCallback function)
|
|
{
|
|
draw_callbacks_.push_front(function);
|
|
}
|
|
|
|
void Rendering::pushBackDrawCallback(RenderingCallback function)
|
|
{
|
|
draw_callbacks_.push_back(function);
|
|
}
|
|
|
|
void Rendering::draw()
|
|
{
|
|
// change windows fullscreen mode if requested
|
|
main_.toggleFullscreen_();
|
|
output_.toggleFullscreen_();
|
|
|
|
// change main window title if requested
|
|
if (!main_new_title_.empty()) {
|
|
main_.setTitle(main_new_title_);
|
|
main_new_title_.clear();
|
|
}
|
|
|
|
// operate on main window context
|
|
main_.makeCurrent();
|
|
|
|
// draw
|
|
std::list<Rendering::RenderingCallback>::iterator iter;
|
|
for (iter=draw_callbacks_.begin(); iter != draw_callbacks_.end(); ++iter)
|
|
{
|
|
(*iter)();
|
|
}
|
|
|
|
// perform screenshot if requested
|
|
if (request_screenshot_) {
|
|
// glfwMakeContextCurrent(main_window_);
|
|
screenshot_.captureGL(0, 0, main_.width(), main_.height());
|
|
request_screenshot_ = false;
|
|
}
|
|
|
|
// swap GL buffers
|
|
glfwSwapBuffers(main_.window());
|
|
|
|
// draw output window (and swap buffer output)
|
|
output_.draw( Mixer::manager().session()->frame() );
|
|
|
|
// software framerate limiter 60FPS if not v-sync
|
|
if ( Settings::application.render.vsync < 1 ) {
|
|
static GTimer *timer = g_timer_new ();
|
|
double elapsed = g_timer_elapsed (timer, NULL) * 1000000.0;
|
|
if ( (elapsed < 16000.0) && (elapsed > 0.0) )
|
|
g_usleep( 16000 - (gulong)elapsed );
|
|
g_timer_start(timer);
|
|
}
|
|
|
|
// Poll and handle events (inputs, window resize, etc.)
|
|
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
|
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
|
|
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
|
|
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
|
glfwPollEvents();
|
|
|
|
}
|
|
|
|
void Rendering::terminate()
|
|
{
|
|
// close window
|
|
glfwDestroyWindow(output_.window());
|
|
glfwDestroyWindow(main_.window());
|
|
// glfwTerminate();
|
|
}
|
|
|
|
void Rendering::pushAttrib(RenderingAttrib ra)
|
|
{
|
|
// push it to top of pile
|
|
draw_attributes_.push_front(ra);
|
|
|
|
// apply Changes to OpenGL
|
|
glViewport(0, 0, ra.viewport.x, ra.viewport.y);
|
|
glClearColor(ra.clear_color.r, ra.clear_color.g, ra.clear_color.b, ra.clear_color.a);
|
|
}
|
|
|
|
void Rendering::popAttrib()
|
|
{
|
|
// pops the top of the pile
|
|
if (draw_attributes_.size() > 0)
|
|
draw_attributes_.pop_front();
|
|
|
|
// set attribute element to default
|
|
RenderingAttrib ra = currentAttrib();
|
|
|
|
// apply Changes to OpenGL
|
|
glViewport(0, 0, ra.viewport.x, ra.viewport.y);
|
|
glClearColor(ra.clear_color.r, ra.clear_color.g, ra.clear_color.b, ra.clear_color.a);
|
|
}
|
|
|
|
RenderingAttrib Rendering::currentAttrib()
|
|
{
|
|
// default rendering attrib is the main window's
|
|
RenderingAttrib ra = main_.attribs();
|
|
|
|
// but if there is an element at top, return it
|
|
if (draw_attributes_.size() > 0)
|
|
ra = draw_attributes_.front();
|
|
return ra;
|
|
}
|
|
|
|
glm::mat4 Rendering::Projection()
|
|
{
|
|
static glm::mat4 projection = glm::ortho(-SCENE_UNIT, SCENE_UNIT, -SCENE_UNIT, SCENE_UNIT, -SCENE_DEPTH, 1.f);
|
|
glm::mat4 scale = glm::scale(glm::identity<glm::mat4>(), glm::vec3(1.f, main_.aspectRatio(), 1.f));
|
|
|
|
return projection * scale;
|
|
}
|
|
|
|
|
|
glm::vec3 Rendering::unProject(glm::vec2 screen_coordinate, glm::mat4 modelview)
|
|
{
|
|
glm::vec3 coordinates = glm::vec3( screen_coordinate.x, main_.height() - screen_coordinate.y, 0.f);
|
|
glm::vec4 viewport = glm::vec4( 0.f, 0.f, main_.width(), main_.height());
|
|
|
|
// Log::Info("unproject %d x %d", main_window_attributes_.viewport.x, main_window_attributes_.viewport.y);
|
|
|
|
glm::vec3 point = glm::unProject(coordinates, modelview, Projection(), viewport);
|
|
|
|
return point;
|
|
}
|
|
|
|
|
|
glm::vec2 Rendering::project(glm::vec3 scene_coordinate, glm::mat4 modelview, bool to_framebuffer)
|
|
{
|
|
glm::vec4 viewport;
|
|
if (to_framebuffer)
|
|
viewport= glm::vec4( 0.f, 0.f, main_.width(), main_.height());
|
|
else
|
|
viewport= glm::vec4( 0.f, 0.f, main_.width() / main_.dpiScale(), main_.height() / main_.dpiScale());
|
|
glm::vec3 P = glm::project( scene_coordinate, modelview, Projection(), viewport );
|
|
|
|
return glm::vec2(P.x, viewport.w - P.y);
|
|
}
|
|
|
|
void Rendering::FileDropped(GLFWwindow *, int path_count, const char* paths[])
|
|
{
|
|
int i = 0;
|
|
for (; i < path_count; ++i) {
|
|
std::string filename(paths[i]);
|
|
if (filename.empty())
|
|
break;
|
|
// try to create a source
|
|
Mixer::manager().addSource ( Mixer::manager().createSourceFile( filename ) );
|
|
}
|
|
if (i>0) {
|
|
UserInterface::manager().showPannel();
|
|
Rendering::manager().mainWindow().show();
|
|
}
|
|
}
|
|
|
|
|
|
Screenshot *Rendering::currentScreenshot()
|
|
{
|
|
return &screenshot_;
|
|
}
|
|
|
|
void Rendering::requestScreenshot()
|
|
{
|
|
request_screenshot_ = true;
|
|
}
|
|
|
|
|
|
// custom surface with a new VAO
|
|
class WindowSurface : public Primitive {
|
|
|
|
public:
|
|
WindowSurface(Shader *s = new ImageShader);
|
|
};
|
|
|
|
WindowSurface::WindowSurface(Shader *s) : Primitive(s)
|
|
{
|
|
points_ = std::vector<glm::vec3> { glm::vec3( -1.f, -1.f, 0.f ), glm::vec3( -1.f, 1.f, 0.f ),
|
|
glm::vec3( 1.f, -1.f, 0.f ), glm::vec3( 1.f, 1.f, 0.f ) };
|
|
colors_ = std::vector<glm::vec4> { glm::vec4( 1.f, 1.f, 1.f , 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ),
|
|
glm::vec4( 1.f, 1.f, 1.f, 1.f ), glm::vec4( 1.f, 1.f, 1.f, 1.f ) };
|
|
texCoords_ = std::vector<glm::vec2> { glm::vec2( 0.f, 1.f ), glm::vec2( 0.f, 0.f ),
|
|
glm::vec2( 1.f, 1.f ), glm::vec2( 1.f, 0.f ) };
|
|
indices_ = std::vector<uint> { 0, 1, 2, 3 };
|
|
drawMode_ = GL_TRIANGLE_STRIP;
|
|
}
|
|
|
|
|
|
RenderingWindow::RenderingWindow() : window_(nullptr), master_(nullptr),
|
|
index_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr), request_toggle_fullscreen_(false)
|
|
{
|
|
}
|
|
|
|
RenderingWindow::~RenderingWindow()
|
|
{
|
|
if (surface_ != nullptr)
|
|
delete surface_;
|
|
if (fbo_ != 0)
|
|
glDeleteFramebuffers(1, &fbo_);
|
|
}
|
|
|
|
void RenderingWindow::setTitle(const std::string &title)
|
|
{
|
|
std::string fulltitle;
|
|
if ( title.empty() )
|
|
fulltitle = Settings::application.windows[index_].name;
|
|
else
|
|
fulltitle = title + std::string(" - " APP_NAME);
|
|
|
|
glfwSetWindowTitle(window_, fulltitle.c_str());
|
|
}
|
|
|
|
void RenderingWindow::setIcon(const std::string &resource)
|
|
{
|
|
size_t fpsize = 0;
|
|
const char *fp = Resource::getData(resource, &fpsize);
|
|
if (fp != nullptr) {
|
|
GLFWimage icon;
|
|
icon.pixels = stbi_load_from_memory( (const stbi_uc*)fp, fpsize, &icon.width, &icon.height, nullptr, 4 );
|
|
glfwSetWindowIcon( window_, 1, &icon );
|
|
free( icon.pixels );
|
|
}
|
|
}
|
|
|
|
bool RenderingWindow::isFullscreen ()
|
|
{
|
|
// return (glfwGetWindowMonitor(window_) != nullptr);
|
|
return Settings::application.windows[index_].fullscreen;
|
|
}
|
|
|
|
GLFWmonitor *RenderingWindow::monitorAt(int x, int y)
|
|
{
|
|
// default to primary monitor
|
|
GLFWmonitor *mo = glfwGetPrimaryMonitor();
|
|
|
|
// list all monitors
|
|
int count_monitors = 0;
|
|
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
|
|
// if there is more than one monitor
|
|
if (count_monitors > 1) {
|
|
// pick at the coordinates given or at pos of window
|
|
// try every monitor
|
|
int i = 0;
|
|
for (; i < count_monitors; i++) {
|
|
int workarea_x, workarea_y, workarea_width, workarea_height;
|
|
#if GLFW_VERSION_MINOR > 2
|
|
glfwGetMonitorWorkarea(monitors[i], &workarea_x, &workarea_y, &workarea_width, &workarea_height);
|
|
#else
|
|
glfwGetMonitorPos(monitors[i], &workarea_x, &workarea_y);
|
|
const GLFWvidmode *vm = glfwGetVideoMode(monitors[i]);
|
|
workarea_width = vm->width;
|
|
workarea_height = vm->height;
|
|
#endif
|
|
if ( x >= workarea_x && x <= workarea_x + workarea_width &&
|
|
y >= workarea_y && y <= workarea_y + workarea_height)
|
|
break;
|
|
}
|
|
// found the monitor containing this point !
|
|
if ( i < count_monitors)
|
|
mo = monitors[i];
|
|
}
|
|
|
|
return mo;
|
|
}
|
|
|
|
GLFWmonitor *RenderingWindow::monitorNamed(const std::string &name)
|
|
{
|
|
// default to primary monitor
|
|
GLFWmonitor *mo = glfwGetPrimaryMonitor();
|
|
|
|
// list all monitors
|
|
int count_monitors = 0;
|
|
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
|
|
// if there is more than one monitor
|
|
if (count_monitors > 1) {
|
|
// pick at the coordinates given or at pos of window
|
|
// try every monitor
|
|
int i = 0;
|
|
for (; i < count_monitors; i++) {
|
|
if ( std::string( glfwGetMonitorName(monitors[i])) == name )
|
|
break;
|
|
}
|
|
// found the monitor with this name
|
|
if ( i < count_monitors)
|
|
mo = monitors[i];
|
|
}
|
|
|
|
return mo;
|
|
}
|
|
|
|
GLFWmonitor *RenderingWindow::monitor()
|
|
{
|
|
// pick at the coordinates given or at pos of window
|
|
int x, y;
|
|
glfwGetWindowPos(window_, &x, &y);
|
|
return monitorAt(x, y);
|
|
}
|
|
|
|
void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
|
|
{
|
|
// done request
|
|
request_toggle_fullscreen_ = false;
|
|
|
|
// disable fullscreen mode
|
|
if (mo == nullptr) {
|
|
// store fullscreen mode
|
|
Settings::application.windows[index_].fullscreen = false;
|
|
|
|
// set to window mode
|
|
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
glfwSetWindowMonitor( window_, nullptr, Settings::application.windows[index_].x,
|
|
Settings::application.windows[index_].y,
|
|
Settings::application.windows[index_].w,
|
|
Settings::application.windows[index_].h, 0 );
|
|
}
|
|
// set fullscreen mode
|
|
else {
|
|
// store fullscreen mode
|
|
Settings::application.windows[index_].fullscreen = true;
|
|
Settings::application.windows[index_].monitor = glfwGetMonitorName(mo);
|
|
|
|
// set to fullscreen mode
|
|
const GLFWvidmode * mode = glfwGetVideoMode(mo);
|
|
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
|
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
|
|
}
|
|
|
|
// Enable vsync on output window only (i.e. not 0 if has a master)
|
|
// Workaround for disabled vsync in fullscreen (https://github.com/glfw/glfw/issues/1072)
|
|
glfwSwapInterval( nullptr == master_ ? 0 : Settings::application.render.vsync);
|
|
|
|
}
|
|
|
|
void RenderingWindow::exitFullscreen()
|
|
{
|
|
if (isFullscreen()) {
|
|
// exit fullscreen
|
|
request_toggle_fullscreen_ = true;
|
|
}
|
|
}
|
|
|
|
void RenderingWindow::toggleFullscreen()
|
|
{
|
|
request_toggle_fullscreen_ = true;
|
|
}
|
|
|
|
void RenderingWindow::toggleFullscreen_()
|
|
{
|
|
if (request_toggle_fullscreen_) {
|
|
|
|
// if in fullscreen mode
|
|
if (glfwGetWindowMonitor(window_) != nullptr) {
|
|
// exit fullscreen
|
|
setFullscreen_(nullptr);
|
|
}
|
|
// not in fullscreen mode
|
|
else {
|
|
// enter fullscreen in monitor where the window is
|
|
setFullscreen_(monitor());
|
|
}
|
|
}
|
|
}
|
|
|
|
int RenderingWindow::width()
|
|
{
|
|
return window_attributes_.viewport.x;
|
|
}
|
|
|
|
int RenderingWindow::height()
|
|
{
|
|
return window_attributes_.viewport.y;
|
|
}
|
|
|
|
int RenderingWindow::pixelsforRealHeight(float milimeters)
|
|
{
|
|
GLFWmonitor *mo = monitor();
|
|
|
|
int mm_w = 0;
|
|
int mm_h = 0;
|
|
glfwGetMonitorPhysicalSize(mo, &mm_w, &mm_h);
|
|
|
|
float pixels = milimeters;
|
|
if (mm_h > 0)
|
|
pixels *= static_cast<float>(glfwGetVideoMode(mo)->height) / static_cast<float>(mm_h);
|
|
else
|
|
pixels *= 5; // something reasonnable if monitor's physical size is unknown
|
|
|
|
return static_cast<int>( round(pixels) );
|
|
}
|
|
|
|
float RenderingWindow::aspectRatio()
|
|
{
|
|
return static_cast<float>(window_attributes_.viewport.x) / static_cast<float>(window_attributes_.viewport.y);
|
|
}
|
|
|
|
bool RenderingWindow::init(int index, GLFWwindow *share)
|
|
{
|
|
index_ = index;
|
|
master_ = share;
|
|
|
|
// access Settings
|
|
Settings::WindowConfig winset = Settings::application.windows[index_];
|
|
|
|
// do not show at creation
|
|
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
|
|
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
|
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
|
|
|
|
// create the window
|
|
window_ = glfwCreateWindow(winset.w, winset.h, winset.name.c_str(), NULL, master_);
|
|
if (window_ == NULL){
|
|
Log::Error("Failed to create GLFW Window %d", index_);
|
|
return false;
|
|
}
|
|
|
|
// ensure minimal window size
|
|
glfwSetWindowSizeLimits(window_, 800, 500, GLFW_DONT_CARE, GLFW_DONT_CARE);
|
|
|
|
previous_size = glm::vec2(winset.w, winset.h);
|
|
|
|
// set initial position
|
|
glfwSetWindowPos(window_, winset.x, winset.y);
|
|
|
|
/// CALLBACKS
|
|
// store global ref to pointers (used by callbacks)
|
|
GLFW_window_[window_] = this;
|
|
// window position and resize callbacks
|
|
glfwSetWindowPosCallback( window_, WindowMoveCallback );
|
|
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
|
|
|
|
// take opengl context ownership
|
|
glfwMakeContextCurrent(window_);
|
|
|
|
//
|
|
// Initialize OpenGL loader on first call
|
|
//
|
|
static bool glad_initialized = false;
|
|
if ( !glad_initialized ) {
|
|
bool err = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress) == 0;
|
|
if (err) {
|
|
Log::Error("Failed to initialize GLAD OpenGL loader.");
|
|
return false;
|
|
}
|
|
glad_initialized = true;
|
|
}
|
|
|
|
// get rendering area
|
|
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
|
// DPI scaling (retina)
|
|
dpi_scale_ = float(window_attributes_.viewport.y) / float(winset.h);
|
|
|
|
// We decide for byte aligned textures all over
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
|
|
// This hint can improve the speed of texturing when perspective-correct texture coordinate interpolation isn't needed
|
|
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
|
|
// fast mipmaps (we are not really using mipmaps anyway)
|
|
glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
|
|
// acurate derivative for shader
|
|
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
|
|
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
|
|
|
|
// if not main window
|
|
if ( master_ != NULL ) {
|
|
// Enable vsync on output window
|
|
glfwSwapInterval(Settings::application.render.vsync);
|
|
// no need for multisampling
|
|
glDisable(GL_MULTISAMPLE);
|
|
// clear to black
|
|
window_attributes_.clear_color = glm::vec4(0.f, 0.f, 0.f, 1.f);
|
|
// give back context ownership
|
|
glfwMakeContextCurrent(master_);
|
|
}
|
|
else {
|
|
// Disable vsync on main window
|
|
glfwSwapInterval(0);
|
|
// Enable Antialiasing multisampling
|
|
if (Settings::application.render.multisampling > 0) {
|
|
glEnable(GL_MULTISAMPLE);
|
|
glHint(GL_MULTISAMPLE_FILTER_HINT_NV, GL_NICEST);
|
|
}
|
|
// clear to grey
|
|
window_attributes_.clear_color = glm::vec4(COLOR_BGROUND, 1.f);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RenderingWindow::show()
|
|
{
|
|
glfwShowWindow(window_);
|
|
|
|
if ( Settings::application.windows[index_].fullscreen ) {
|
|
GLFWmonitor *mo = monitorNamed(Settings::application.windows[index_].monitor);
|
|
setFullscreen_(mo);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void RenderingWindow::makeCurrent()
|
|
{
|
|
// handle window resize
|
|
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
|
|
|
// ensure main context is current
|
|
glfwMakeContextCurrent(window_);
|
|
|
|
// set and clear
|
|
glViewport(0, 0, window_attributes_.viewport.x, window_attributes_.viewport.y);
|
|
glClearColor(window_attributes_.clear_color.r, window_attributes_.clear_color.g,
|
|
window_attributes_.clear_color.b, window_attributes_.clear_color.a);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
|
|
// TODO update parameters for draw on resize event (not every frame)
|
|
|
|
void RenderingWindow::draw(FrameBuffer *fb)
|
|
{
|
|
if (!window_ || !fb)
|
|
return;
|
|
|
|
// only draw if window is not iconified
|
|
if( !glfwGetWindowAttrib(window_, GLFW_ICONIFIED ) ) {
|
|
|
|
// update viewport (could be done with callback)
|
|
glfwGetFramebufferSize(window_, &(window_attributes_.viewport.x), &(window_attributes_.viewport.y));
|
|
|
|
// take context ownership
|
|
glfwMakeContextCurrent(window_);
|
|
|
|
// setup attribs
|
|
Rendering::manager().pushAttrib(window_attributes_);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
|
|
// blit framebuffer
|
|
if (Settings::application.render.blit) {
|
|
|
|
if ( textureid_ != fb->texture()) {
|
|
|
|
textureid_ = fb->texture();
|
|
|
|
// create a new fbo in this opengl context
|
|
if (fbo_ != 0)
|
|
glDeleteFramebuffers(1, &fbo_);
|
|
glGenFramebuffers(1, &fbo_);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
|
|
// attach the 2D texture to local FBO
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid_, 0);
|
|
#ifndef NDEBUG
|
|
Log::Info("Blit to output window enabled.");
|
|
#endif
|
|
}
|
|
|
|
// if not disabled
|
|
if (!Settings::application.render.disabled) {
|
|
|
|
// calculate scaling factor of frame buffer inside window
|
|
int rx, ry, rw, rh;
|
|
float renderingAspectRatio = fb->aspectRatio();
|
|
if (aspectRatio() < renderingAspectRatio) {
|
|
int nh = (int)( float(window_attributes_.viewport.x) / renderingAspectRatio);
|
|
rx = 0;
|
|
ry = (window_attributes_.viewport.y - nh) / 2;
|
|
rw = window_attributes_.viewport.x;
|
|
rh = (window_attributes_.viewport.y + nh) / 2;
|
|
} else {
|
|
int nw = (int)( float(window_attributes_.viewport.y) * renderingAspectRatio );
|
|
rx = (window_attributes_.viewport.x - nw) / 2;
|
|
ry = 0;
|
|
rw = (window_attributes_.viewport.x + nw) / 2;
|
|
rh = window_attributes_.viewport.y;
|
|
}
|
|
|
|
// select fbo texture read target
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
|
|
|
// select screen target
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
|
|
// blit operation from fbo (containing texture) to screen
|
|
glBlitFramebuffer(0, fb->height(), fb->width(), 0, rx, ry, rw, rh, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
|
|
}
|
|
}
|
|
|
|
// draw geometry
|
|
else if (!Settings::application.render.disabled)
|
|
{
|
|
// VAO is not shared between multiple contexts of different windows
|
|
// so we have to create a new VAO for rendering the surface in this window
|
|
if (surface_ == 0)
|
|
surface_ = new WindowSurface;
|
|
|
|
// calculate scaling factor of frame buffer inside window
|
|
float windowAspectRatio = aspectRatio();
|
|
float renderingAspectRatio = fb->aspectRatio();
|
|
glm::vec3 scale;
|
|
if (windowAspectRatio < renderingAspectRatio)
|
|
scale = glm::vec3(1.f, windowAspectRatio / renderingAspectRatio, 1.f);
|
|
else
|
|
scale = glm::vec3(renderingAspectRatio / windowAspectRatio, 1.f, 1.f);
|
|
|
|
// make sure previous shader in another glcontext is disabled
|
|
ShadingProgram::enduse();
|
|
|
|
// draw
|
|
glBindTexture(GL_TEXTURE_2D, fb->texture());
|
|
// surface->shader()->color.a = 0.4f; // TODO alpha blending ?
|
|
static glm::mat4 projection = glm::ortho(-1.f, 1.f, -1.f, 1.f, -1.f, 1.f);
|
|
surface_->draw(glm::scale(glm::identity<glm::mat4>(), scale), projection);
|
|
|
|
// done drawing (unload shader from this glcontext)
|
|
ShadingProgram::enduse();
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
// swap buffer
|
|
glfwSwapBuffers(window_);
|
|
}
|
|
|
|
// restore attribs
|
|
Rendering::manager().popAttrib();
|
|
|
|
// give back context ownership
|
|
glfwMakeContextCurrent(master_);
|
|
}
|
|
|