Skip to content

Commit

Permalink
WIP: ass_render: add threading
Browse files Browse the repository at this point in the history
  • Loading branch information
rcombs committed Jul 29, 2022
1 parent 9a7da3c commit cb7721e
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 24 deletions.
236 changes: 212 additions & 24 deletions libass/ass_render.c
Expand Up @@ -23,6 +23,7 @@
#include <math.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>

#include "ass.h"
#include "ass_outline.h"
Expand Down Expand Up @@ -67,6 +68,22 @@ static void text_info_done(TextInfo* text_info)
free(text_info->combined_bitmaps);
}

#if CONFIG_PTHREAD
unsigned default_threads(void)
{
#ifdef _SC_NPROCESSORS_ONLN
long sc = sysconf(_SC_NPROCESSORS_ONLN);
if (sc < 0)
return 1;
return sc;
#endif

return 1;
}

static void* run_thread(void* ptr);
#endif

ASS_Renderer *ass_renderer_init(ASS_Library *library)
{
int error;
Expand Down Expand Up @@ -142,6 +159,46 @@ ASS_Renderer *ass_renderer_init(ASS_Library *library)
ass_shaper_info(library);
priv->settings.shaper = ASS_SHAPING_COMPLEX;

#if CONFIG_PTHREAD
if (pthread_mutex_init(&priv->mutex, NULL) != 0)
goto fail;

priv->mutex_inited = true;

if (pthread_cond_init(&priv->main_cond, NULL) != 0)
goto fail;

priv->main_cond_inited = true;

if (pthread_cond_init(&priv->pool_cond, NULL) != 0)
goto fail;

priv->pool_cond_inited = true;

unsigned thread_count = default_threads();
if (thread_count > 1) {
if (!(priv->threads = calloc(thread_count, sizeof(pthread_t))))
goto fail;

for (priv->n_threads = 0; priv->n_threads < thread_count; priv->n_threads++) {
if (pthread_create(&priv->threads[priv->n_threads], NULL, run_thread, priv) != 0)
goto fail;
}

int failed = 0;

pthread_mutex_lock(&priv->mutex);
while (priv->started_threads < priv->n_threads && !priv->thread_start_failed)
pthread_cond_wait(&priv->main_cond, &priv->mutex);

failed = priv->thread_start_failed;
pthread_mutex_unlock(&priv->mutex);

if (failed)
goto fail;
}
#endif

ass_msg(library, MSGL_V, "Initialized");

return priv;
Expand All @@ -158,6 +215,28 @@ void ass_renderer_done(ASS_Renderer *render_priv)
if (!render_priv)
return;

#if CONFIG_PTHREAD
if (render_priv->threads) {
pthread_mutex_lock(&render_priv->mutex);
render_priv->shutting_down = 1;
pthread_mutex_unlock(&render_priv->mutex);
pthread_cond_broadcast(&render_priv->pool_cond);

for (unsigned i = 0; i < render_priv->n_threads; i++)
pthread_join(render_priv->threads[i], NULL);

free(render_priv->threads);
}


if (render_priv->mutex_inited)
pthread_mutex_destroy(&render_priv->mutex);
if (render_priv->main_cond_inited)
pthread_cond_destroy(&render_priv->main_cond);
if (render_priv->pool_cond_inited)
pthread_cond_destroy(&render_priv->pool_cond);
#endif

ass_frame_unref(render_priv->images_root);
ass_frame_unref(render_priv->prev_images_root);

Expand Down Expand Up @@ -2668,16 +2747,34 @@ static void add_background(RenderContext *state, EventImages *event_images)
}
}

static void setup_shaper(ASS_Shaper *shaper, ASS_Renderer *render_priv)
{
ASS_Track *track = render_priv->track;

ass_shaper_set_kerning(shaper, track->Kerning);
ass_shaper_set_language(shaper, track->Language);
ass_shaper_set_level(shaper, render_priv->settings.shaper);
#ifdef USE_FRIBIDI_EX_API
ass_shaper_set_bidi_brackets(shaper,
track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS));
#endif
ass_shaper_set_whole_text_layout(shaper,
track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT));
}

/**
* \brief Main ass rendering function, glues everything together
* \param event event to render
* \param event_images struct containing resulting images, will also be initialized
* Process event, appending resulting ASS_Image's to images_root.
*/
static bool
ass_render_event(RenderContext *state, ASS_Event *event,
EventImages *event_images)
ass_render_event(RenderContext *state, EventImages *event_images)
{
ASS_Event *event = event_images->event;

event_images->event = NULL;

ASS_Renderer *render_priv = state->renderer;
if (event->Style >= render_priv->track->n_styles) {
ass_msg(render_priv->library, MSGL_WARN, "No style found");
Expand All @@ -2688,6 +2785,8 @@ ass_render_event(RenderContext *state, ASS_Event *event,
return false;
}

setup_shaper(state->shaper, render_priv);

free_render_context(state);
init_render_context(state, event);

Expand Down Expand Up @@ -2894,6 +2993,84 @@ ass_render_event(RenderContext *state, ASS_Event *event,
return true;
}

#if CONFIG_PTHREAD
static void* run_thread(void* ptr)
{
ASS_Renderer *priv = ptr;

TextInfo text_info = {0};
RenderContext state = {
.renderer = priv,
.text_info = &text_info,
};

#define THREAD_NAME "libass/render"

#if defined(__APPLE__)
pthread_setname_np(THREAD_NAME);
#elif defined(__linux__)
pthread_setname_np(pthread_self(), THREAD_NAME);
#elif defined(__FreeBSD__)
pthread_set_name_np(pthread_self(), THREAD_NAME);
#endif

int success = text_info_init(&text_info);

if (success) {
if (!(state.shaper = ass_shaper_new(priv->cache.metrics_cache)))
success = 0;
}


if (success)
success = rasterizer_init(priv->engine, &state.rasterizer, RASTERIZER_PRECISION);

pthread_mutex_lock(&priv->mutex);

if (!success) {
priv->thread_start_failed = 1;
goto fail;
} else {
priv->started_threads++;
}

pthread_cond_broadcast(&priv->main_cond);

while (!priv->shutting_down) {
while (priv->next_eimg < priv->sent_eimgs) {
EventImages *imgs = priv->eimg + priv->next_eimg++;

pthread_mutex_unlock(&priv->mutex);

success = ass_render_event(&state, imgs);

pthread_mutex_lock(&priv->mutex);

if (success)
priv->got_eimgs++;

if (!--priv->processing_eimgs)
pthread_cond_broadcast(&priv->main_cond);
}

pthread_cond_wait(&priv->pool_cond, &priv->mutex);
}

fail:

pthread_mutex_unlock(&priv->mutex);

rasterizer_done(&state.rasterizer);

if (state.shaper)
ass_shaper_free(state.shaper);

text_info_done(&text_info);

return NULL;
}
#endif

/**
* \brief Check cache limits and reset cache if they are exceeded
*/
Expand All @@ -2904,21 +3081,6 @@ static void check_cache_limits(ASS_Renderer *priv, CacheStore *cache)
ass_cache_cut(cache->outline_cache, cache->glyph_max);
}

static void setup_shaper(ASS_Shaper *shaper, ASS_Renderer *render_priv)
{
ASS_Track *track = render_priv->track;

ass_shaper_set_kerning(shaper, track->Kerning);
ass_shaper_set_language(shaper, track->Language);
ass_shaper_set_level(shaper, render_priv->settings.shaper);
#ifdef USE_FRIBIDI_EX_API
ass_shaper_set_bidi_brackets(shaper,
track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS));
#endif
ass_shaper_set_whole_text_layout(shaper,
track->parser_priv->feature_flags & FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT));
}

/**
* \brief Start a new frame
*/
Expand Down Expand Up @@ -2952,8 +3114,6 @@ ass_start_frame(ASS_Renderer *render_priv, ASS_Track *track,
render_priv->fontselect, render_priv->num_emfonts);
}

setup_shaper(render_priv->state.shaper, render_priv);

// PAR correction
double par = render_priv->settings.par;
if (par == 0.) {
Expand Down Expand Up @@ -2981,6 +3141,12 @@ static int cmp_event_layer(const void *p1, const void *p2)
{
ASS_Event *e1 = ((EventImages *) p1)->event;
ASS_Event *e2 = ((EventImages *) p2)->event;
if (!e1 && !e2)
return 0;
if (e1 && !e2)
return -1;
if (!e1 && e2)
return 1;
if (e1->Layer < e2->Layer)
return -1;
if (e1->Layer > e2->Layer)
Expand Down Expand Up @@ -3239,25 +3405,47 @@ ASS_Image *ass_render_frame(ASS_Renderer *priv, ASS_Track *track,
}

// render events separately
int cnt = 0;
int sent = 0, cnt = 0;
for (int i = 0; i < track->n_events; i++) {
ASS_Event *event = track->events + i;
if ((event->Start <= now)
&& (now < (event->Start + event->Duration))) {
if (cnt >= priv->eimg_size) {
if (sent >= priv->eimg_size) {
priv->eimg_size += 100;
priv->eimg =
realloc(priv->eimg,
priv->eimg_size * sizeof(EventImages));
}
if (ass_render_event(&priv->state, event, priv->eimg + cnt))
priv->eimg[sent++].event = event;
}
}

#if CONFIG_PTHREAD
if (priv->n_threads > 0) {
pthread_mutex_lock(&priv->mutex);
priv->next_eimg = priv->got_eimgs = 0;
priv->processing_eimgs = priv->sent_eimgs = sent;

pthread_cond_broadcast(&priv->pool_cond);

while (priv->processing_eimgs)
pthread_cond_wait(&priv->main_cond, &priv->mutex);

cnt = priv->got_eimgs;

pthread_mutex_unlock(&priv->mutex);
} else
#endif
{
for (int i = 0; i < sent; i++) {
if (ass_render_event(&priv->state, priv->eimg + i))
cnt++;
}
}

// sort by layer
if (cnt > 0)
qsort(priv->eimg, cnt, sizeof(EventImages), cmp_event_layer);
if (sent > 0)
qsort(priv->eimg, sent, sizeof(EventImages), cmp_event_layer);

// call fix_collisions for each group of events with the same layer
EventImages *last = priv->eimg;
Expand Down
19 changes: 19 additions & 0 deletions libass/ass_render.h
Expand Up @@ -28,6 +28,10 @@
#include FT_SYNTHESIS_H
#include <hb.h>

#if CONFIG_PTHREAD
#include <pthread.h>
#endif

#include "ass.h"
#include "ass_font.h"
#include "ass_bitmap.h"
Expand Down Expand Up @@ -332,6 +336,21 @@ struct ass_renderer {
const BitmapEngine *engine;

ASS_Style user_override_style;

#if CONFIG_PTHREAD
pthread_mutex_t mutex;
int mutex_inited;
pthread_cond_t main_cond, pool_cond;
int main_cond_inited, pool_cond_inited;

unsigned n_threads, started_threads;
pthread_t *threads;

int thread_start_failed;

int shutting_down;
int processing_eimgs, sent_eimgs, next_eimg, got_eimgs;
#endif
};

typedef struct render_priv {
Expand Down

0 comments on commit cb7721e

Please sign in to comment.