]> git.sesse.net Git - nageru/blobdiff - nageru/image_input.cpp
Unbreak showing the first two channels in the tally JSON.
[nageru] / nageru / image_input.cpp
index 5f695702e7ffcbc3adb63127298afffec7b93f92..6a2c5abc846b913123ef00647373bd6267ee1d4b 100644 (file)
@@ -3,6 +3,7 @@
 #include <errno.h>
 #include <movit/flat_input.h>
 #include <movit/image_format.h>
+#include <movit/util.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -20,6 +21,7 @@ extern "C" {
 #include <libswscale/swscale.h>
 }
 
+#include <epoxy/egl.h>
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <unistd.h>
@@ -30,6 +32,7 @@ extern "C" {
 #include <utility>
 #include <vector>
 
+#include "shared/context.h"
 #include "shared/ffmpeg_raii.h"
 #include "ffmpeg_util.h"
 #include "flags.h"
@@ -38,20 +41,24 @@ struct SwsContext;
 
 using namespace std;
 
+ImageInput::ImageInput()
+       : sRGBSwitchingFlatInput({movit::COLORSPACE_sRGB, movit::GAMMA_sRGB}, movit::FORMAT_RGBA_POSTMULTIPLIED_ALPHA,
+                                GL_UNSIGNED_BYTE, 1280, 720)  // Resolution will be overwritten.
+{}
+
 ImageInput::ImageInput(const string &filename)
-       : movit::FlatInput({movit::COLORSPACE_sRGB, movit::GAMMA_sRGB}, movit::FORMAT_RGBA_POSTMULTIPLIED_ALPHA,
-                          GL_UNSIGNED_BYTE, 1280, 720),  // Resolution will be overwritten.
-         filename(filename),
+       : sRGBSwitchingFlatInput({movit::COLORSPACE_sRGB, movit::GAMMA_sRGB}, movit::FORMAT_RGBA_POSTMULTIPLIED_ALPHA,
+                                GL_UNSIGNED_BYTE, 1280, 720),  // Resolution will be overwritten.
          pathname(search_for_file_or_die(filename)),
          current_image(load_image(filename, pathname))
 {
        if (current_image == nullptr) {  // Could happen even though search_for_file() returned.
                fprintf(stderr, "Couldn't load image, exiting.\n");
-               exit(1);
+               abort();
        }
        set_width(current_image->width);
        set_height(current_image->height);
-       set_pixel_data(current_image->pixels.get());
+       set_texture_num(*current_image->tex);
 }
 
 void ImageInput::set_gl_state(GLuint glsl_program_num, const string& prefix, unsigned *sampler_num)
@@ -63,27 +70,24 @@ void ImageInput::set_gl_state(GLuint glsl_program_num, const string& prefix, uns
        // so there's a fair amount of OpenGL memory waste anyway (the cache
        // is mostly there to save startup time, not RAM).
        {
-               unique_lock<mutex> lock(all_images_lock);
+               lock_guard<mutex> lock(all_images_lock);
+               assert(all_images.count(pathname));
                if (all_images[pathname] != current_image) {
                        current_image = all_images[pathname];
-                       set_pixel_data(current_image->pixels.get());
+                       set_texture_num(*current_image->tex);
                }
        }
-       movit::FlatInput::set_gl_state(glsl_program_num, prefix, sampler_num);
+       sRGBSwitchingFlatInput::set_gl_state(glsl_program_num, prefix, sampler_num);
 }
 
 shared_ptr<const ImageInput::Image> ImageInput::load_image(const string &filename, const string &pathname)
 {
-       unique_lock<mutex> lock(all_images_lock);  // Held also during loading.
+       lock_guard<mutex> lock(all_images_lock);  // Held also during loading.
        if (all_images.count(pathname)) {
                return all_images[pathname];
        }
 
        all_images[pathname] = load_image_raw(pathname);
-       timespec first_modified = all_images[pathname]->last_modified;
-       update_threads[pathname] =
-               thread(bind(update_thread_func, filename, pathname, first_modified));
-
        return all_images[pathname];
 }
 
@@ -195,66 +199,129 @@ shared_ptr<const ImageInput::Image> ImageInput::load_image_raw(const string &pat
        unique_ptr<uint8_t[]> image_data(new uint8_t[len]);
        av_image_copy_to_buffer(image_data.get(), len, pic_data, linesizes, AV_PIX_FMT_RGBA, frame->width, frame->height, 1);
 
-       shared_ptr<Image> image(new Image{unsigned(frame->width), unsigned(frame->height), move(image_data), last_modified});
+       // Create and upload the texture. We always make mipmaps, since we have
+       // generally no idea of all the different chains that might crop up.
+       GLuint tex;
+       glGenTextures(1, &tex);
+       check_error();
+       glBindTexture(GL_TEXTURE_2D, tex);
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+       check_error();
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       check_error();
+
+       // Actual upload.
+       glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+       check_error();
+       glPixelStorei(GL_UNPACK_ROW_LENGTH, linesizes[0] / 4);
+       check_error();
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, frame->width, frame->height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, image_data.get());
+       check_error();
+       glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+       check_error();
+
+       glGenerateMipmap(GL_TEXTURE_2D);
+       check_error();
+       glBindTexture(GL_TEXTURE_2D, 0);
+       check_error();
+
+       shared_ptr<Image> image(new Image{unsigned(frame->width), unsigned(frame->height), RefCountedTexture(new GLuint(tex)), last_modified});
        return image;
 }
 
-// Fire up a thread to update the image every second.
+// Fire up a thread to update all images every second.
 // We could do inotify, but this is good enough for now.
-void ImageInput::update_thread_func(const std::string &filename, const std::string &pathname, const timespec &first_modified)
+void ImageInput::update_thread_func(QSurface *surface)
 {
-       char thread_name[16];
-       snprintf(thread_name, sizeof(thread_name), "Update_%s", filename.c_str());
-       pthread_setname_np(pthread_self(), thread_name);
+       pthread_setname_np(pthread_self(), "Update_Images");
+
+       eglBindAPI(EGL_OPENGL_API);
+       QOpenGLContext *context = create_context(surface);
+       if (!make_current(context, surface)) {
+               printf("Couldn't initialize OpenGL context!\n");
+               abort();
+       }
 
-       timespec last_modified = first_modified;
        struct stat buf;
        for ( ;; ) {
                {
-                       unique_lock<mutex> lock(threads_should_quit_mu);
-                       threads_should_quit_modified.wait_for(lock, chrono::seconds(1), []() { return threads_should_quit; });
+                       unique_lock<mutex> lock(update_thread_should_quit_mu);
+                       update_thread_should_quit_modified.wait_for(lock, chrono::seconds(1), [] { return update_thread_should_quit; });
                }
-
-               if (threads_should_quit) {
+               if (update_thread_should_quit) {
                        return;
                }
 
-               if (stat(pathname.c_str(), &buf) != 0) {
-                       fprintf(stderr, "%s: Couldn't check for new version, leaving the old in place.\n", pathname.c_str());
-                       continue;
-               }
-               if (buf.st_mtim.tv_sec == last_modified.tv_sec &&
-                   buf.st_mtim.tv_nsec == last_modified.tv_nsec) {
-                       // Not changed.
-                       continue;
+               // Go through all loaded images and see if they need to be updated.
+               // We do one pass first through the array with no I/O, to avoid
+               // blocking the renderer.
+               vector<pair<string, timespec>> images_to_check;
+               {
+                       unique_lock<mutex> lock(all_images_lock);
+                       for (const auto &pathname_and_image : all_images) {
+                               const string pathname = pathname_and_image.first;
+                               const timespec last_modified = pathname_and_image.second->last_modified;
+                               images_to_check.emplace_back(pathname, last_modified);
+                       }
                }
-               shared_ptr<const Image> image = load_image_raw(pathname);
-               if (image == nullptr) {
-                       fprintf(stderr, "Couldn't load image, leaving the old in place.\n");
-                       continue;
+
+               for (const auto &pathname_and_timespec : images_to_check) {
+                       const string pathname = pathname_and_timespec.first;
+                       const timespec last_modified = pathname_and_timespec.second;
+
+                       if (stat(pathname.c_str(), &buf) != 0) {
+                               fprintf(stderr, "%s: Couldn't check for new version, leaving the old in place.\n", pathname.c_str());
+                               continue;
+                       }
+                       if (buf.st_mtim.tv_sec == last_modified.tv_sec &&
+                           buf.st_mtim.tv_nsec == last_modified.tv_nsec) {
+                               // Not changed.
+                               continue;
+                       }
+
+                       shared_ptr<const Image> image = load_image_raw(pathname);
+                       if (image == nullptr) {
+                               fprintf(stderr, "Couldn't load image, leaving the old in place.\n");
+                               continue;
+                       }
+
+                       unique_lock<mutex> lock(all_images_lock);
+                       all_images[pathname] = image;
                }
-               fprintf(stderr, "Loaded new version of %s from disk.\n", pathname.c_str());
-               unique_lock<mutex> lock(all_images_lock);
-               all_images[pathname] = image;
-               last_modified = image->last_modified;
        }
 }
 
-void ImageInput::shutdown_updaters()
+void ImageInput::switch_image(const string &pathname)
+{
+#ifndef NDEBUG
+       lock_guard<mutex> lock(all_images_lock);
+       assert(all_images.count(pathname));
+#endif
+       this->pathname = pathname;
+}
+
+void ImageInput::start_update_thread(QSurface *surface)
+{
+       update_thread = thread(update_thread_func, surface);
+}
+
+void ImageInput::end_update_thread()
+
 {
        {
-               unique_lock<mutex> lock(threads_should_quit_mu);
-               threads_should_quit = true;
-               threads_should_quit_modified.notify_all();
-       }
-       for (auto &it : update_threads) {
-               it.second.join();
+               lock_guard<mutex> lock(update_thread_should_quit_mu);
+               update_thread_should_quit = true;
+               update_thread_should_quit_modified.notify_all();
        }
+       update_thread.join();
 }
 
 mutex ImageInput::all_images_lock;
 map<string, shared_ptr<const ImageInput::Image>> ImageInput::all_images;
-map<string, thread> ImageInput::update_threads;
-mutex ImageInput::threads_should_quit_mu;
-bool ImageInput::threads_should_quit = false;
-condition_variable ImageInput::threads_should_quit_modified;
+thread ImageInput::update_thread;
+mutex ImageInput::update_thread_should_quit_mu;
+bool ImageInput::update_thread_should_quit = false;
+condition_variable ImageInput::update_thread_should_quit_modified;