Skip to content
Snippets Groups Projects
opengl_enc.c 55.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Lukasz Marek's avatar
    Lukasz Marek committed
    
    static av_cold int opengl_init_context(OpenGLContext *opengl)
    {
        int i, ret;
        const AVPixFmtDescriptor *desc;
    
        if ((ret = opengl_compile_shaders(opengl, opengl->pix_fmt)) < 0)
            goto fail;
    
        desc = av_pix_fmt_desc_get(opengl->pix_fmt);
        av_assert0(desc->nb_components > 0 && desc->nb_components <= 4);
        glGenTextures(desc->nb_components, opengl->texture_name);
    
        opengl->glprocs.glGenBuffers(2, &opengl->index_buffer);
        if (!opengl->index_buffer || !opengl->vertex_buffer) {
            av_log(opengl, AV_LOG_ERROR, "Buffer generation failed.\n");
            ret = AVERROR_EXTERNAL;
            goto fail;
        }
    
        opengl_configure_texture(opengl, opengl->texture_name[0], opengl->width, opengl->height);
        if (desc->nb_components > 1) {
            int has_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA;
            int num_planes = desc->nb_components - (has_alpha ? 1 : 0);
            if (opengl->non_pow_2_textures) {
                opengl->chroma_div_w = 1.0f;
                opengl->chroma_div_h = 1.0f;
            } else {
                opengl->chroma_div_w = 1 << desc->log2_chroma_w;
                opengl->chroma_div_h = 1 << desc->log2_chroma_h;
            }
            for (i = 1; i < num_planes; i++)
                if (opengl->non_pow_2_textures)
                    opengl_configure_texture(opengl, opengl->texture_name[i],
                            FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w),
                            FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h));
                else
                    opengl_configure_texture(opengl, opengl->texture_name[i], opengl->width, opengl->height);
            if (has_alpha)
                opengl_configure_texture(opengl, opengl->texture_name[3], opengl->width, opengl->height);
        }
    
        opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, opengl->index_buffer);
        opengl->glprocs.glBufferData(FF_GL_ELEMENT_ARRAY_BUFFER, sizeof(g_index), g_index, FF_GL_STATIC_DRAW);
        opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, 0);
    
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
        glClearColor((float)opengl->background[0] / 255.0f, (float)opengl->background[1] / 255.0f,
                     (float)opengl->background[2] / 255.0f, 1.0f);
    
        ret = AVERROR_EXTERNAL;
        OPENGL_ERROR_CHECK(opengl);
    
        return 0;
      fail:
        return ret;
    }
    
    static av_cold int opengl_write_header(AVFormatContext *h)
    {
        OpenGLContext *opengl = h->priv_data;
        AVStream *st;
        int ret;
    
        if (h->nb_streams != 1 ||
            h->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO ||
            h->streams[0]->codec->codec_id != AV_CODEC_ID_RAWVIDEO) {
            av_log(opengl, AV_LOG_ERROR, "Only a single video stream is supported.\n");
            return AVERROR(EINVAL);
        }
        st = h->streams[0];
        opengl->width = st->codec->width;
        opengl->height = st->codec->height;
        opengl->pix_fmt = st->codec->pix_fmt;
    
        if (!opengl->window_width)
            opengl->window_width = opengl->width;
        if (!opengl->window_height)
            opengl->window_height = opengl->height;
    
    Lukasz Marek's avatar
    Lukasz Marek committed
    
        if (!opengl->window_title && !opengl->no_window)
            opengl->window_title = av_strdup(h->filename);
    
    
        if ((ret = opengl_create_window(h)))
    
    Lukasz Marek's avatar
    Lukasz Marek committed
            goto fail;
    
        if ((ret = opengl_read_limits(opengl)) < 0)
            goto fail;
    
        if (opengl->width > opengl->max_texture_size || opengl->height > opengl->max_texture_size) {
            av_log(opengl, AV_LOG_ERROR, "Too big picture %dx%d, max supported size is %dx%d\n",
                   opengl->width, opengl->height, opengl->max_texture_size, opengl->max_texture_size);
            ret = AVERROR(EINVAL);
            goto fail;
        }
    
    
        if ((ret = opengl_load_procedures(opengl)) < 0)
    
    Lukasz Marek's avatar
    Lukasz Marek committed
            goto fail;
    
        opengl_fill_color_map(opengl);
        opengl_get_texture_params(opengl);
    
        if ((ret = opengl_init_context(opengl)) < 0)
            goto fail;
    
        if ((ret = opengl_prepare_vertex(h)) < 0)
            goto fail;
    
        glClear(GL_COLOR_BUFFER_BIT);
    
    #if HAVE_SDL
        if (!opengl->no_window)
            SDL_GL_SwapBuffers();
    #endif
        if (opengl->no_window &&
            (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DISPLAY_WINDOW_BUFFER, NULL , 0)) < 0) {
            av_log(opengl, AV_LOG_ERROR, "Application failed to display window buffer.\n");
            goto fail;
        }
    
        ret = AVERROR_EXTERNAL;
        OPENGL_ERROR_CHECK(opengl);
    
    Lukasz Marek's avatar
    Lukasz Marek committed
        return 0;
    
      fail:
        opengl_write_trailer(h);
        return ret;
    }
    
    static uint8_t* opengl_get_plane_pointer(OpenGLContext *opengl, AVPacket *pkt, int comp_index,
                                             const AVPixFmtDescriptor *desc)
    {
        uint8_t *data = pkt->data;
        int wordsize = opengl_type_size(opengl->type);
        int width_chroma = FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w);
        int height_chroma = FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h);
        int plane = desc->comp[comp_index].plane;
    
        switch(plane) {
        case 0:
            break;
        case 1:
            data += opengl->width * opengl->height * wordsize;
            break;
        case 2:
            data += opengl->width * opengl->height * wordsize;
            data += width_chroma * height_chroma * wordsize;
            break;
        case 3:
            data += opengl->width * opengl->height * wordsize;
            data += 2 * width_chroma * height_chroma * wordsize;
            break;
        default:
            return NULL;
        }
        return data;
    }
    
    
    #define LOAD_TEXTURE_DATA(comp_index, sub)                                                  \
    {                                                                                           \
        int width = sub ? FF_CEIL_RSHIFT(opengl->width, desc->log2_chroma_w) : opengl->width;   \
        int height = sub ? FF_CEIL_RSHIFT(opengl->height, desc->log2_chroma_h): opengl->height; \
        uint8_t *data;                                                                          \
        int plane = desc->comp[comp_index].plane;                                               \
                                                                                                \
        glBindTexture(GL_TEXTURE_2D, opengl->texture_name[comp_index]);                         \
        if (!is_pkt) {                                                                          \
            GLint length = ((AVFrame *)input)->linesize[plane];                                 \
            int bytes_per_pixel = opengl_type_size(opengl->type);                               \
            if (!(desc->flags & AV_PIX_FMT_FLAG_PLANAR))                                        \
                bytes_per_pixel *= desc->nb_components;                                         \
            data = ((AVFrame *)input)->data[plane];                                             \
            if (!(length % bytes_per_pixel) &&                                                  \
                (opengl->unpack_subimage || ((length / bytes_per_pixel) == width))) {           \
                length /= bytes_per_pixel;                                                      \
                if (length != width)                                                            \
                    glPixelStorei(FF_GL_UNPACK_ROW_LENGTH, length);                             \
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,                          \
                                opengl->format, opengl->type, data);                            \
                if (length != width)                                                            \
                    glPixelStorei(FF_GL_UNPACK_ROW_LENGTH, 0);                                  \
            } else {                                                                            \
                int h;                                                                          \
                for (h = 0; h < height; h++) {                                                  \
                    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, h, width, 1,                           \
                                    opengl->format, opengl->type, data);                        \
                    data += length;                                                             \
                }                                                                               \
            }                                                                                   \
        } else {                                                                                \
            data = opengl_get_plane_pointer(opengl, input, comp_index, desc);                   \
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,                              \
                            opengl->format, opengl->type, data);                                \
        }                                                                                       \
    }
    
    static int opengl_draw(AVFormatContext *h, void *input, int repaint, int is_pkt)
    
    Lukasz Marek's avatar
    Lukasz Marek committed
    {
        OpenGLContext *opengl = h->priv_data;
        enum AVPixelFormat pix_fmt = h->streams[0]->codec->pix_fmt;
        const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
        int ret;
    
    #if HAVE_SDL
        if (!opengl->no_window && (ret = opengl_sdl_process_events(h)) < 0)
            goto fail;
    #endif
        if (opengl->no_window &&
            (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_PREPARE_WINDOW_BUFFER, NULL , 0)) < 0) {
            av_log(opengl, AV_LOG_ERROR, "Application failed to prepare window buffer.\n");
            goto fail;
        }
    
        glClear(GL_COLOR_BUFFER_BIT);
    
        if (!repaint) {
    
            if (is_pkt)
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
            LOAD_TEXTURE_DATA(0, 0)
    
    Lukasz Marek's avatar
    Lukasz Marek committed
            if (desc->flags & AV_PIX_FMT_FLAG_PLANAR) {
    
                LOAD_TEXTURE_DATA(1, 1)
                LOAD_TEXTURE_DATA(2, 1)
                if (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
                    LOAD_TEXTURE_DATA(3, 0)
    
    Lukasz Marek's avatar
    Lukasz Marek committed
            }
        }
        ret = AVERROR_EXTERNAL;
        OPENGL_ERROR_CHECK(opengl);
    
        if ((ret = opengl_prepare(opengl)) < 0)
            goto fail;
    
        opengl->glprocs.glBindBuffer(FF_GL_ARRAY_BUFFER, opengl->vertex_buffer);
        opengl->glprocs.glBindBuffer(FF_GL_ELEMENT_ARRAY_BUFFER, opengl->index_buffer);
        opengl->glprocs.glVertexAttribPointer(opengl->position_attrib, 3, GL_FLOAT, GL_FALSE, sizeof(OpenGLVertexInfo), 0);
        opengl->glprocs.glEnableVertexAttribArray(opengl->position_attrib);
        opengl->glprocs.glVertexAttribPointer(opengl->texture_coords_attrib, 2, GL_FLOAT, GL_FALSE, sizeof(OpenGLVertexInfo), 12);
        opengl->glprocs.glEnableVertexAttribArray(opengl->texture_coords_attrib);
    
        glDrawElements(GL_TRIANGLES, FF_ARRAY_ELEMS(g_index), GL_UNSIGNED_SHORT, 0);
    
        ret = AVERROR_EXTERNAL;
        OPENGL_ERROR_CHECK(opengl);
    
    #if HAVE_SDL
        if (!opengl->no_window)
            SDL_GL_SwapBuffers();
    #endif
        if (opengl->no_window &&
            (ret = avdevice_dev_to_app_control_message(h, AV_DEV_TO_APP_DISPLAY_WINDOW_BUFFER, NULL , 0)) < 0) {
            av_log(opengl, AV_LOG_ERROR, "Application failed to display window buffer.\n");
            goto fail;
        }
    
        return 0;
      fail:
        return ret;
    }
    
    static int opengl_write_packet(AVFormatContext *h, AVPacket *pkt)
    {
    
        return opengl_draw(h, pkt, 0, 1);
    }
    
    static int opengl_write_frame(AVFormatContext *h, int stream_index,
                                  AVFrame **frame, unsigned flags)
    {
        if ((flags & AV_WRITE_UNCODED_FRAME_QUERY))
            return 0;
        return opengl_draw(h, *frame, 0, 0);
    
    Lukasz Marek's avatar
    Lukasz Marek committed
    }
    
    #define OFFSET(x) offsetof(OpenGLContext, x)
    #define ENC AV_OPT_FLAG_ENCODING_PARAM
    static const AVOption options[] = {
        { "background",   "set background color",   OFFSET(background),   AV_OPT_TYPE_COLOR,  {.str = "black"}, CHAR_MIN, CHAR_MAX, ENC },
        { "no_window",    "disable default window", OFFSET(no_window),    AV_OPT_TYPE_INT,    {.i64 = 0}, INT_MIN, INT_MAX, ENC },
        { "window_title", "set window title",       OFFSET(window_title), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, ENC },
    
        { "window_size",  "set window size",        OFFSET(window_width), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, ENC },
    
    Lukasz Marek's avatar
    Lukasz Marek committed
        { NULL }
    };
    
    static const AVClass opengl_class = {
        .class_name = "opengl outdev",
        .item_name  = av_default_item_name,
        .option     = options,
        .version    = LIBAVUTIL_VERSION_INT,
    
        .category   = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT,
    
    Lukasz Marek's avatar
    Lukasz Marek committed
    };
    
    AVOutputFormat ff_opengl_muxer = {
        .name           = "opengl",
        .long_name      = NULL_IF_CONFIG_SMALL("OpenGL output"),
        .priv_data_size = sizeof(OpenGLContext),
        .audio_codec    = AV_CODEC_ID_NONE,
        .video_codec    = AV_CODEC_ID_RAWVIDEO,
        .write_header   = opengl_write_header,
        .write_packet   = opengl_write_packet,
    
        .write_uncoded_frame = opengl_write_frame,
    
    Lukasz Marek's avatar
    Lukasz Marek committed
        .write_trailer  = opengl_write_trailer,
        .control_message = opengl_control_message,
        .flags          = AVFMT_NOFILE | AVFMT_VARIABLE_FPS | AVFMT_NOTIMESTAMPS,
        .priv_class     = &opengl_class,
    };