X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=nageru%2Fimage_input.cpp;h=6a2c5abc846b913123ef00647373bd6267ee1d4b;hb=f9024d141398e69e7b4011becd3ebbe37eaa1776;hp=5f695702e7ffcbc3adb63127298afffec7b93f92;hpb=eeda8995329601f9f4e35047358400833eeae68e;p=nageru diff --git a/nageru/image_input.cpp b/nageru/image_input.cpp index 5f69570..6a2c5ab 100644 --- a/nageru/image_input.cpp +++ b/nageru/image_input.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ extern "C" { #include } +#include #include #include #include @@ -30,6 +32,7 @@ extern "C" { #include #include +#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 lock(all_images_lock); + lock_guard 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 ImageInput::load_image(const string &filename, const string &pathname) { - unique_lock lock(all_images_lock); // Held also during loading. + lock_guard 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 ImageInput::load_image_raw(const string &pat unique_ptr 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(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(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 lock(threads_should_quit_mu); - threads_should_quit_modified.wait_for(lock, chrono::seconds(1), []() { return threads_should_quit; }); + unique_lock 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> images_to_check; + { + unique_lock 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 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 image = load_image_raw(pathname); + if (image == nullptr) { + fprintf(stderr, "Couldn't load image, leaving the old in place.\n"); + continue; + } + + unique_lock lock(all_images_lock); + all_images[pathname] = image; } - fprintf(stderr, "Loaded new version of %s from disk.\n", pathname.c_str()); - unique_lock 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 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 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 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> ImageInput::all_images; -map 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;