]> git.sesse.net Git - nageru/commitdiff
Make the ImageInput cache store textures, not images.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 18 May 2019 09:26:33 +0000 (11:26 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 18 May 2019 09:26:33 +0000 (11:26 +0200)
This saves on a lot of texture memory when the same image is used
in multiple chains, but perhaps more importantly, will allow us to
decouple ImageInputs from which images they show later.

nageru/image_input.cpp
nageru/image_input.h
nageru/main.cpp
nageru/mixer.cpp
nageru/mixer.h

index 12789dc09ff45523c31eea5c67225d76998d8fe2..d2d6611162fae0d88ab06dd382d026f5997337a2 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,6 +41,7 @@ struct SwsContext;
 
 using namespace std;
 
+// FIXME gl context
 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.
@@ -50,7 +54,7 @@ ImageInput::ImageInput(const string &filename)
        }
        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)
@@ -65,7 +69,7 @@ void ImageInput::set_gl_state(GLuint glsl_program_num, const string& prefix, uns
                lock_guard<mutex> lock(all_images_lock);
                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);
@@ -79,12 +83,6 @@ shared_ptr<const ImageInput::Image> ImageInput::load_image(const string &filenam
        }
 
        all_images[pathname] = load_image_raw(pathname);
-
-       if (!update_thread_started) {
-               update_thread = thread(update_thread_func);
-               update_thread_started = true;
-       }
-
        return all_images[pathname];
 }
 
@@ -196,23 +194,59 @@ 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_RGBA8, frame->width, frame->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 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 all images every second.
 // We could do inotify, but this is good enough for now.
-void ImageInput::update_thread_func()
+void ImageInput::update_thread_func(QSurface *surface)
 {
        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();
+       }
+
        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;
                }
 
@@ -255,24 +289,25 @@ void ImageInput::update_thread_func()
        }
 }
 
-void ImageInput::shutdown_updaters()
+void ImageInput::start_update_thread(QSurface *surface)
 {
-       {
-               lock_guard<mutex> lock(threads_should_quit_mu);
-               threads_should_quit = true;
-               threads_should_quit_modified.notify_all();
-       }
+       update_thread = thread(update_thread_func, surface);
+}
 
-       lock_guard<mutex> lock(all_images_lock);
-       if (update_thread_started) {
-               update_thread.join();
+void ImageInput::end_update_thread()
+
+{
+       {
+               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;
-bool ImageInput::update_thread_started = false;
 thread ImageInput::update_thread;
-mutex ImageInput::threads_should_quit_mu;
-bool ImageInput::threads_should_quit = false;
-condition_variable ImageInput::threads_should_quit_modified;
+mutex ImageInput::update_thread_should_quit_mu;
+bool ImageInput::update_thread_should_quit = false;
+condition_variable ImageInput::update_thread_should_quit_modified;
index ac3c519147d6d4d6f66ad4e2baffaffaabdf7a69..f7289f3164e647358722ad5c021d75fa7f881c3e 100644 (file)
 #include <string>
 #include <thread>
 
+#include "ref_counted_texture.h"
+
+class QSurface;
+
 // An output that takes its input from a static image, loaded with ffmpeg.
 // comes from a single 2D array with chunky pixels. The image is refreshed
 // from disk about every second.
 class ImageInput : public movit::FlatInput {
 public:
+       // NOTE: You will need to call start_update_thread() yourself, once per program.
        ImageInput(const std::string &filename);
 
        std::string effect_type_id() const override { return "ImageInput"; }
        void set_gl_state(GLuint glsl_program_num, const std::string& prefix, unsigned *sampler_num) override;
-       static void shutdown_updaters();
+
+       static void start_update_thread(QSurface *surface);
+       static void end_update_thread();
        
 private:
        struct Image {
                unsigned width, height;
-               std::unique_ptr<uint8_t[]> pixels;
+               RefCountedTexture tex;
                timespec last_modified;
        };
 
@@ -36,15 +43,14 @@ private:
 
        static std::shared_ptr<const Image> load_image(const std::string &filename, const std::string &pathname);
        static std::shared_ptr<const Image> load_image_raw(const std::string &pathname);
-       static void update_thread_func();
+       static void update_thread_func(QSurface *surface);
        static std::mutex all_images_lock;
        static std::map<std::string, std::shared_ptr<const Image>> all_images;  // Under all_images_lock.
-       static bool update_thread_started;  // Under all_images_lock.
-       static std::thread update_thread;  // Under all_images_lock.
 
-       static std::mutex threads_should_quit_mu;
-       static bool threads_should_quit;  // Under threads_should_quit_mu.
-       static std::condition_variable threads_should_quit_modified;  // Signals when threads_should_quit is set.
+       static std::thread update_thread;
+       static std::mutex update_thread_should_quit_mu;
+       static bool update_thread_should_quit;  // Under thread_should_quit_mu.
+       static std::condition_variable update_thread_should_quit_modified;  // Signals when threads_should_quit is set.
 };
 
 #endif // !defined(_IMAGE_INPUT_H)
index b5463754a463bdc60f0be758a18be3add0e8ee71..351662039d2cd8e1790129f5d2d5e0282de76982 100644 (file)
@@ -120,6 +120,5 @@ int main(int argc, char *argv[])
 
        int rc = app.exec();
        delete global_mixer;
-       ImageInput::shutdown_updaters();
        return rc;
 }
index ef73b613c0643bae638a3f6533a645ece146ea10..83d6eada183d8f0f787c1f9703211915db790917 100644 (file)
@@ -44,6 +44,7 @@
 #include "shared/disk_space_estimator.h"
 #include "ffmpeg_capture.h"
 #include "flags.h"
+#include "image_input.h"
 #include "input_mapping.h"
 #include "shared/metrics.h"
 #include "mjpeg_encoder.h"
@@ -306,7 +307,8 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
          num_cards(num_cards),
          mixer_surface(create_surface(format)),
          h264_encoder_surface(create_surface(format)),
-         decklink_output_surface(create_surface(format))
+         decklink_output_surface(create_surface(format)),
+         image_update_surface(create_surface(format))
 {
        memcpy(ycbcr_interpretation, global_flags.ycbcr_interpretation, sizeof(ycbcr_interpretation));
        CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
@@ -500,10 +502,14 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        }
 
        output_jitter_history.register_metrics({{ "card", "output" }});
+
+       ImageInput::start_update_thread(image_update_surface);
 }
 
 Mixer::~Mixer()
 {
+       ImageInput::end_update_thread();
+
        if (mjpeg_encoder != nullptr) {
                mjpeg_encoder->stop();
        }
index de8b45741ef30cf97058d5270f995f45e1680553..5af2016a74bae1d4a241d224f78f300e53463bca 100644 (file)
@@ -456,7 +456,7 @@ private:
        HTTPD httpd;
        unsigned num_cards, num_video_inputs, num_html_inputs = 0;
 
-       QSurface *mixer_surface, *h264_encoder_surface, *decklink_output_surface;
+       QSurface *mixer_surface, *h264_encoder_surface, *decklink_output_surface, *image_update_surface;
        std::unique_ptr<movit::ResourcePool> resource_pool;
        std::unique_ptr<Theme> theme;
        std::atomic<unsigned> audio_source_channel{0};