Files
vimix/src/RenderingManager.cpp
Bruno Herbelin 1c309b2c89 Added WhiteBalance to RenderingWindow and Display View
Render output frame to output window using a Shader implementing white balance correction. Adjusting parameters of white balance from Display View with color picker (white) and slider (temperature). GLSL filter for white balance created from ShaderToy.
2023-03-01 23:24:26 +01:00

1201 lines
37 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
#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 "osx/CocoaToolkit.h"
//#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 "Stream.h"
#include "Resource.h"
#include "Settings.h"
#include "ImageShader.h"
#include "Mixer.h"
#include "SystemToolkit.h"
#include "GstToolkit.h"
#include "UserInterfaceManager.h"
#include "ControlManager.h"
#include "ImageFilter.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
static void glfw_error_callback(int error, const char* description)
{
g_printerr("Glfw Error %d: %s\n", error, description);
}
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(Rendering::manager().mainWindow().previous_size.x, Rendering::manager().mainWindow().previous_size.y, width, height);
Rendering::manager().mainWindow().previous_size = glm::vec2(width, height);
}
int id = Rendering::manager().window(w)->index();
Settings::application.windows[id].fullscreen = glfwGetWindowMonitor(w) != nullptr;
if (!Settings::application.windows[id].fullscreen) {
Settings::application.windows[id].w = width;
Settings::application.windows[id].h = height;
}
#if __APPLE__
Rendering::manager().draw();
#endif
}
static void WindowMoveCallback( GLFWwindow *w, int x, int y)
{
int id = Rendering::manager().window(w)->index();
if (!Settings::application.windows[id].fullscreen) {
Settings::application.windows[id].x = x;
Settings::application.windows[id].y = y;
}
}
static void OutputWindowEvent( GLFWwindow *w, int button, int action, int)
{
// detect mouse press
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS)
{
static double seconds = 0.f;
// detect double clic
if ( glfwGetTime() - seconds < 0.2f ) {
// exit fullscreen if its the case
if (glfwGetWindowMonitor(w) != nullptr)
Rendering::manager().window(w)->exitFullscreen();
// show main window in DISPLAYS view to
// indicate how to manipulate output window
Mixer::manager().setView(View::DISPLAYS);
Rendering::manager().mainWindow().show();
}
// for next double clic detection
seconds = glfwGetTime();
}
}
static void WindowCloseCallback( GLFWwindow* w )
{
if (!UserInterface::manager().TryClose())
glfwSetWindowShouldClose(w, GLFW_FALSE);
}
void Rendering::MonitorConnect(GLFWmonitor* monitor, int event)
{
// reset list of monitors
Rendering::manager().monitors_geometry_.clear();
// list monitors with GLFW
int count_monitors = 0;
GLFWmonitor** monitors = glfwGetMonitors(&count_monitors);
// Fill list of monitors of rendering manager
for (int i = 0; i < count_monitors; i++) {
// fill monitor structure
int x = 0, y = 0;
glfwGetMonitorPos(monitors[i], &x, &y);
const GLFWvidmode *vm = glfwGetVideoMode(monitors[i]);
std::string n = glfwGetMonitorName(monitors[i]);
// add
Rendering::manager().monitors_geometry_[n] = glm::ivec4(x, y, vm->width, vm->height);
}
// inform Displays View that monitors changed
Mixer::manager().view(View::DISPLAYS)->recenter();
#ifndef NDEBUG
if (event == GLFW_CONNECTED)
g_printerr("Monitor %s connected\n", glfwGetMonitorName(monitor));
else if (event == GLFW_DISCONNECTED)
g_printerr("Monitor %s disconnected\n", glfwGetMonitorName(monitor));
#endif
}
GLFWmonitor *Rendering::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) {
// try every monitor
for (int i = 0; i < count_monitors; i++) {
int workarea_x, workarea_y;
glfwGetMonitorPos(monitors[i], &workarea_x, &workarea_y);
const GLFWvidmode *vm = glfwGetVideoMode(monitors[i]);
if ( x >= workarea_x && x <= workarea_x + vm->width &&
y >= workarea_y && y <= workarea_y + vm->height) {
// found the monitor containing this point !
mo = monitors[i];
break;
}
}
}
return mo;
}
GLFWmonitor *Rendering::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) {
// try every monitor
for (int i = 0; i < count_monitors; i++) {
if ( name.compare( glfwGetMonitorName(monitors[i]) ) == 0 ){
// found the monitor with this name
mo = monitors[i];
break;
}
}
}
return mo;
}
void Rendering::close()
{
glfwSetWindowShouldClose(main_.window(), GLFW_TRUE);
}
Rendering::Rendering()
{
// main_window_ = nullptr;
request_screenshot_ = false;
}
bool Rendering::init()
{
//
// Setup GLFW
//
glfwSetErrorCallback(glfw_error_callback);
if (!glfwInit()){
g_printerr("Failed to Initialize GLFW.\n");
return false;
}
//
// Gstreamer setup
//
std::string plugins_scanner = SystemToolkit::cwd_path() + "gst-plugin-scanner" ;
if ( SystemToolkit::file_exists(plugins_scanner)) {
Log::Info("Found Gstreamer scanner %s", plugins_scanner.c_str());
g_setenv ("GST_PLUGIN_SCANNER", plugins_scanner.c_str(), TRUE);
}
const gchar *c = g_getenv("GST_PLUGIN_PATH");
std::string plugins_path = c != NULL ? std::string(c) : "";
std::string local_plugin_path = SystemToolkit::cwd_path() + "gstreamer-1.0";
if ( SystemToolkit::file_exists(local_plugin_path)){
if (!plugins_path.empty())
plugins_path += ":";
plugins_path += local_plugin_path;
}
#ifdef GSTREAMER_SHMDATA_PLUGIN
std::string shmdata_plugin_path = GSTREAMER_SHMDATA_PLUGIN;
if ( SystemToolkit::file_exists(shmdata_plugin_path)) {
if (!plugins_path.empty())
plugins_path += ":";
plugins_path += SystemToolkit::path_filename(shmdata_plugin_path);
}
#endif
if ( !plugins_path.empty() ) {
Log::Info("Found Gstreamer plugins in %s", plugins_path.c_str());
g_setenv ("GST_PLUGIN_PATH", plugins_path.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);
}
// init gstreamer with opengl API
g_setenv ("GST_GL_API", VIMIX_GL_VERSION, 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 (gpuplugins.size() > 0) {
Settings::application.render.gpu_decoding_available = true;
Log::Info("Found the following hardware decoding gstreamer plugin(s):");
int i = 1;
for(auto it = gpuplugins.begin(); it != gpuplugins.end(); it++, ++i)
Log::Info("%d. %s", i, (*it).c_str());
if (Settings::application.render.gpu_decoding)
Log::Info("Hardware decoding enabled.");
else
Log::Info("Hardware decoding disabled.");
}
else {
Log::Info("No hardware 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
//
// Monitors
//
Rendering::MonitorConnect(nullptr, GLFW_DONT_CARE);
// automatic detection of monitor connect & disconnect
glfwSetMonitorCallback(Rendering::MonitorConnect);
//
// Main window
//
if ( !main_.init(0) )
return false;
//
// Output windows will be initialized in draw
//
outputs_ = std::vector<RenderingWindow>(MAX_OUTPUT_WINDOW);
return true;
}
RenderingWindow* Rendering::window(GLFWwindow *w)
{
if (windows_.count(w))
return windows_[w];
else
return &main_;
}
RenderingWindow* Rendering::window(int index)
{
if (index > 0 && index <= MAX_OUTPUT_WINDOW )
return &outputs_[index - 1];
else
return &main_;
}
void Rendering::show()
{
// show output windows
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
it->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::pushBackDrawCallback(RenderingCallback function)
{
draw_callbacks_.push_back(function);
}
void Rendering::draw()
{
// 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();
// change windows fullscreen mode if requested
main_.changeFullscreen_();
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
it->changeFullscreen_();
// 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_) {
screenshot_.captureGL(main_.width(), main_.height());
request_screenshot_ = false;
}
// draw output windows and count number of success
int count = 0;
for (auto it = outputs_.begin(); it != outputs_.end(); ++it) {
if ( it->draw( Mixer::manager().session()->frame() ) )
++count;
}
// terminate or initialize output windows to match number of output windows
if (count > Settings::application.num_output_windows)
outputs_[count-1].terminate();
else if (count < Settings::application.num_output_windows) {
outputs_[count].init( count+1, main_.window());
outputs_[count].show();
}
// software framerate limiter < 62 FPS
{
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);
}
// swap all GL buffers at once
main_.swap();
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
it->swap();
}
void Rendering::terminate()
{
// terminate all windows
for (auto it = outputs_.begin(); it != outputs_.end(); ++it)
it->terminate();
main_.terminate();
// 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;
}
// RAM usage in GPU
// returns { CurAvailMemoryInKB, TotalMemoryInKB }
// MAX values means the info in not available
#define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX 0x9048
#define GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX 0x9049
#define GL_TEXTURE_FREE_MEMORY_ATI 0x87FC
glm::ivec2 Rendering::getGPUMemoryInformation()
{
glm::ivec2 ret(INT_MAX, INT_MAX);
// Detect method to get info
static int meminfomode = -1;
if (meminfomode<0) {
// initialized
meminfomode = 0;
GLint numExtensions = 0;
glGetIntegerv( GL_NUM_EXTENSIONS, &numExtensions );
for (int i = 0; i < numExtensions; ++i){
const GLubyte *ccc = glGetStringi(GL_EXTENSIONS, i);
// NVIDIA extension available
if ( strcmp( (const char*)ccc, "GL_NVX_gpu_memory_info") == 0 ){
meminfomode = 1;
break;
}
// ATI extension available
else if ( strcmp( (const char*)ccc, "GL_ATI_meminfo") == 0 ){
meminfomode = 2;
break;
}
}
}
// NVIDIA
if (meminfomode == 1) {
static GLint nTotalMemoryInKB = -1;
if (nTotalMemoryInKB<0)
glGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &nTotalMemoryInKB );
ret.y = nTotalMemoryInKB;
glGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &ret.x );
}
// ATI
else if (meminfomode == 2) {
GLint memInMB[4] = { 0, 0, 0, 0 };
glGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, &memInMB[0] );
ret.x = memInMB[3] ;
}
return ret;
}
bool Rendering::shouldHaveEnoughMemory(glm::vec3 resolution, int flags)
{
glm::ivec2 RAM = getGPUMemoryInformation();
// approximation of RAM needed for such FBO
GLint framebufferMemoryInKB = ( resolution.x * resolution.x *
((flags & FrameBuffer::FrameBuffer_alpha)?4:3) * ((flags & FrameBuffer::FrameBuffer_multisampling)?2:1) ) / 1024;
return ( RAM.x > framebufferMemoryInKB * 3 );
}
// 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_(NULL), master_(NULL),
index_(-1), dpi_scale_(1.f), textureid_(0), fbo_(0), surface_(nullptr), request_change_fullscreen_(false)
{
pattern_ = new Stream;
}
RenderingWindow::~RenderingWindow()
{
if (window_ != NULL)
terminate();
}
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);
if (window_ != NULL)
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 && window_) {
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 );
}
}
GLFWmonitor *RenderingWindow::monitor()
{
// get monitor at the center of the window
int x = 0, y = 0, w = 2, h = 2;
if (window_) {
glfwGetWindowSize(window_, &w, &h);
glfwGetWindowPos(window_, &x, &y);
}
return Rendering::manager().monitorAt(x + w/2, y + h/2);
}
void RenderingWindow::setFullscreen_(GLFWmonitor *mo)
{
if (!window_)
return;
// 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_, NULL, 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);
glfwSetWindowMonitor( window_, mo, 0, 0, mode->width, mode->height, mode->refreshRate);
glfwSetInputMode( window_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
// 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( master_ != nullptr ? Settings::application.render.vsync : 0);
}
bool RenderingWindow::isFullscreen ()
{
// return (glfwGetWindowMonitor(window_) != nullptr);
return Settings::application.windows[index_].fullscreen;
}
void RenderingWindow::exitFullscreen ()
{
// exit fullscreen
request_change_fullscreen_ = isFullscreen();
}
void RenderingWindow::toggleFullscreen ()
{
request_change_fullscreen_ = true;
}
void RenderingWindow::setFullscreen (std::string monitorname)
{
Settings::application.windows[index_].monitor = monitorname;
request_change_fullscreen_ = true;
}
void RenderingWindow::changeFullscreen_()
{
// change upon request
if (request_change_fullscreen_) {
// done request
request_change_fullscreen_ = false;
GLFWmonitor *mo = Rendering::manager().monitorNamed( Settings::application.windows[index_].monitor );
// if in fullscreen mode
if (isFullscreen ()) {
// changing fullscreen monitor
if ( glfwGetWindowMonitor(window_) != mo)
setFullscreen_(mo);
else
// exit fullscreen
setFullscreen_(nullptr);
}
// not in fullscreen mode
else {
// enter fullscreen
setFullscreen_(mo);
}
}
}
void RenderingWindow::setDecoration (bool on)
{
if (window_ == NULL)
return;
Settings::application.windows[index_].decorated = on;
glfwSetWindowAttrib( window_, GLFW_RESIZABLE, on ? GLFW_TRUE : GLFW_FALSE);
glfwSetWindowAttrib( window_, GLFW_DECORATED, on ? GLFW_TRUE : GLFW_FALSE);
}
void RenderingWindow::setCoordinates(glm::ivec4 rect)
{
if (window_ == NULL)
return;
// restore maximized window to be able to change its coordinates
if (glfwGetWindowAttrib(window_, GLFW_MAXIMIZED))
glfwRestoreWindow(window_);
glfwSetWindowSize( window_, glm::max(50, rect.p), glm::max(50, rect.q));
glfwSetWindowPos( window_, rect.x, rect.y);
}
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)
{
if (window_)
return false;
glfwMakeContextCurrent(NULL);
///
/// Settings
///
index_ = index;
master_ = share;
Settings::WindowConfig winset = Settings::application.windows[index_];
///
/// GLFW window creation parameters
///
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
// multisampling in main window
glfwWindowHint(GLFW_SAMPLES, master_ == NULL ? Settings::application.render.multisampling : 0);
// do not show at creation
glfwWindowHint(GLFW_FOCUSED, GLFW_FALSE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
// restore decoration state
if (master_ != NULL && !winset.decorated) {
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_DECORATED, 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_, 500, 500, GLFW_DONT_CARE, GLFW_DONT_CARE);
previous_size = glm::vec2(winset.w, winset.h);
// set initial position
glfwSetWindowPos(window_, winset.x, winset.y);
// set icon
setIcon("images/vimix_256x256.png");
///
/// CALLBACKS
///
// store global ref to pointers (used by callbacks)
Rendering::manager().windows_[window_] = this;
//
// window position and resize callbacks
//
glfwSetWindowPosCallback( window_, WindowMoveCallback );
glfwSetWindowSizeCallback( window_, WindowResizeCallback );
//
// set keyboard callback
//
// all windows capture keys
glfwSetKeyCallback( window_, Control::keyboardCalback);
if (master_ != NULL) {
// additional window callbacks for user input in output windows
glfwSetMouseButtonCallback( window_, OutputWindowEvent);
}
else {
// additional window callbacks for main window
glfwSetWindowCloseCallback( window_, WindowCloseCallback );
glfwSetDropCallback( window_, Rendering::FileDropped);
}
//
// Initialize OpenGL
//
// 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 ) {
// NO vsync on output windows
glfwSwapInterval(0);
// no need for multisampling
glDisable(GL_MULTISAMPLE);
// clear to black
window_attributes_.clear_color = glm::vec4(0.f, 0.f, 0.f, 1.f);
}
else {
// vsync on main window
glfwSwapInterval(Settings::application.render.vsync);
// 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);
}
//
// Stream pattern
//
pattern_->open("videotestsrc pattern=smpte", 1024, 1024);
pattern_->play(true);
textureid_ = Resource::getTextureBlack();
return true;
}
void RenderingWindow::terminate()
{
// cleanup
if (surface_ != nullptr)
delete surface_;
if (fbo_ != 0)
glDeleteFramebuffers(1, &fbo_);
if (window_ != NULL) {
// remove global ref to pointers
Rendering::manager().windows_.erase(window_);
// delete window
glfwDestroyWindow(window_);
}
// invalidate
window_ = NULL;
shader_ = nullptr;
surface_ = nullptr;
fbo_ = 0;
index_ = -1;
textureid_ = Resource::getTextureBlack();
}
void RenderingWindow::show()
{
if (!window_)
return;
glfwShowWindow(window_);
if ( Settings::application.windows[index_].fullscreen ) {
GLFWmonitor *mo = Rendering::manager().monitorNamed(Settings::application.windows[index_].monitor);
setFullscreen_(mo);
}
}
void RenderingWindow::makeCurrent()
{
if (!window_)
return;
// 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);
}
void RenderingWindow::swap()
{
if (window_)
// swap buffer
glfwSwapBuffers(window_);
}
FilteringProgram whitebalance("Whitebalance", "shaders/filters/whitebalance.glsl", "", { { "Red", 1.0}, { "Green", 1.0}, { "Blue", 1.0}, { "Temperature", 0.5} });
void RenderingWindow::setWhiteBalance(glm::vec4 colorcorrection)
{
if (shader_)
shader_->uniforms_ = std::map< std::string, float >{
{ "Red", colorcorrection.x},
{ "Green", colorcorrection.y},
{ "Blue", colorcorrection.z},
{ "Temperature", colorcorrection.w}
};
}
glm::vec4 RenderingWindow::whiteBalance() const
{
glm::vec4 ret(1.f, 1.f, 1.f, 0.5f);
ret.x = shader_->uniforms_["Red"];
ret.y = shader_->uniforms_["Green"];
ret.z = shader_->uniforms_["Blue"];
ret.w = shader_->uniforms_["Temperature"];
return ret;
}
bool RenderingWindow::draw(FrameBuffer *fb)
{
// cannot draw if there is no window or invalid framebuffer
if (!window_ || !fb)
return false;
// 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);
// make sure previous shader in another glcontext is disabled
ShadingProgram::enduse();
// draw geometry
if (Settings::application.render.disabled)
// no draw; indicate texture is black
textureid_ = Resource::getTextureBlack();
else {
// normal draw
// 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_ == nullptr) {
const std::pair<std::string, std::string> codes = whitebalance.code();
shader_ = new ImageFilteringShader;
shader_->setCode( codes.first );
shader_->uniforms_ = whitebalance.parameters();
surface_ = new WindowSurface(shader_);
}
// calculate scaling factor of frame buffer inside window
const float windowAspectRatio = aspectRatio();
const float renderingAspectRatio = fb->aspectRatio();
glm::vec3 scale = glm::vec3(1.f, 1.f, 1.f);
// Display option: scaled or corrected aspect ratio
if (!Settings::application.windows[index_].scaled) {
if (windowAspectRatio < renderingAspectRatio)
scale = glm::vec3(1.f, windowAspectRatio / renderingAspectRatio, 1.f);
else
scale = glm::vec3(renderingAspectRatio / windowAspectRatio, 1.f, 1.f);
}
// Display option: draw calibration pattern
if ( Settings::application.windows[index_].show_pattern) {
// draw pattern texture
pattern_->update();
textureid_ = pattern_->texture();
}
else
// draw normal texture
textureid_ = fb->texture();
// actual render of the textured surface
glBindTexture(GL_TEXTURE_2D, textureid_);
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);
}
// restore attribs
Rendering::manager().popAttrib();
}
return true;
}