mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-13 19:29:58 +01:00
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.
1201 lines
37 KiB
C++
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;
|
|
}
|
|
|