commit 35c8ec02fa548b6ec4c4b37ffc7a5e00cbfd26c5
parent 89160c3b72d929aadb031f96b2d006cd8076764f
Author: Samdal <samdal@protonmail.com>
Date: Sun, 8 Jan 2023 23:23:34 +0100
more ridgidity, remove mutexes and use atomic semantics
Diffstat:
2 files changed, 78 insertions(+), 66 deletions(-)
diff --git a/source/gs_avdecode.h b/source/gs_avdecode.h
@@ -7,9 +7,6 @@
* before including header in one and only one source file
* to implement declared functions.
*
- * #define GS_AVDECODE_NO_DECLARE_CLOCK_GETTIME
- * if it's being redefined on windows (used for timing for multi-threaded decoding)
- *
* requirers linking with ffmpeg stuff.
* on gcc/clang that would for example be
* -lavcodec -lavformat -lavcodec -lswresample -lswscale -lavutil
@@ -22,7 +19,6 @@
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
-#include <libavutil/timestamp.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/version.h>
#include <libavformat/avformat.h>
@@ -33,6 +29,7 @@
#include <pthread.h>
#include <time.h>
+#include <stdatomic.h>
_Static_assert(LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 38, 100), "FFmpeg major version too low: " LIBAVCODEC_IDENT " needs Lavc55.38.100");
@@ -56,20 +53,46 @@ typedef struct gs_avdecode_ctx_s {
void* img[1];
} gs_avdecode_ctx_t;
+// return zero on success
+// TODO: specify weather <0 is error or if its just non-zero
+// TODO: gs_avdecode_rewind
+extern int gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out);
+extern int gs_avdecode_next_frame(gs_avdecode_ctx_t* ctx); // -1 if all frames read
+extern void gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex);
+
+
+// Multi-threading usage:
+// Atomic integers are used instead of locks (for performance reasons)
+//
+// if new_frame is 0, then the "main" thread(s) shall not access anything
+// if new_frame is -1 then the "decoder" thread shall not access anything
+// if new_frame is 1, then a frame is complete and either thread can do a cmpxchg to aquire it.
+//
+// when the decoder thread exits it sets done to 1
typedef struct gs_avdecode_pthread_s {
- pthread_mutex_t lock;
+ pthread_attr_t attr;
gs_avdecode_ctx_t video;
- int new_frame;
+ _Atomic int new_frame;
+ _Atomic int loop; // TODO...
_Atomic int done;
} gs_avdecode_pthread_t;
+extern int gs_avdecode_pthread_play_video(gs_avdecode_pthread_t* ctxp, pthread_t* thread, const char* path,
+ const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out);
+extern void gs_avdecode_pthread_destroy(gs_avdecode_pthread_t* ctxp, pthread_t* thread, gs_asset_texture_t* tex);
+
+#define gs_avdecode_aquire_m(_ctxp, ...) \
+ do { \
+ int __check = 1; \
+ if (atomic_compare_exchange_strong(&(_ctxp)->new_frame, &__check, -1)) { \
+ __VA_ARGS__ \
+ (_ctxp)->new_frame = 0; \
+ } \
+ } while(0)
+
+
-// return zero on success
-// TODO: specify weather <0 is error or if its just non-zero
-extern int gs_avdecode_init(const char* path, gs_avdecode_ctx_t* ctx, const gs_graphics_texture_desc_t* desc, gs_asset_texture_t* out);
-extern int gs_avdecode_next_frame(gs_avdecode_ctx_t* ctx); // -1 if all frames read
-extern void gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex);
#ifdef GS_AVDECODE_IMPL
@@ -345,79 +368,58 @@ gs_avdecode_destroy(gs_avdecode_ctx_t* ctx, gs_asset_texture_t* tex)
}
}
-#ifdef _WIN32
-#ifndef GS_AVDECODE_NO_DECLARE_CLOCK_GETTIME
-// untested
-struct timespec { long tv_sec; long tv_nsec; };
-static int
-clock_gettime(int, struct timespec *spec)
-{
- __int64 wintime;
- GetSystemTimeAsFileTime((FILETIME*)&wintime);
- wintime -= 116444736000000000i64; // 1jan1601 to 1jan1970
- spec->tv_sec = wintime / 10000000i64; // seconds
- spec->tv_nsec = wintime % 10000000i64 * 100; // nano-seconds
- return 0;
-}
-#endif // GS_AVDECODE_NO_DECLARE_CLOCK_GETTIME
-#endif // _WIN32
-
static void*
_gs_avdecode_pthread_player(void* data)
{
gs_avdecode_pthread_t* ctxp = data;
gs_avdecode_ctx_t* ctx = &ctxp->video;
+ ctxp->new_frame = 0;
- pthread_mutex_lock(&ctxp->lock);
const float frametime = ctx->frametime * 1e3;
- pthread_mutex_unlock(&ctxp->lock);
int frames = 0;
- struct timespec ts_start;
- clock_gettime(CLOCK_MONOTONIC, &ts_start);
+ const float t_start = gs_platform_elapsed_time();
int res = 0;
int prerendered = 0;
- const float dt = 0.001;
+ const float dt = 0.05;
for (;;) {
- if (!prerendered) {
- pthread_mutex_lock(&ctxp->lock);
- if (ctxp->new_frame == 0) {
- res = gs_avdecode_next_frame(ctx);
- prerendered = 1;
- }
- pthread_mutex_unlock(&ctxp->lock);
+ if (!prerendered && ctxp->new_frame == 0) {
+ res = gs_avdecode_next_frame(ctx);
+ prerendered = 1;
}
- float diff = 0.0;
- struct timespec ts_check;
- clock_gettime(CLOCK_MONOTONIC, &ts_check);
- diff = (ts_check.tv_sec - ts_start.tv_sec) * 1e3;
- diff += (ts_check.tv_nsec - ts_start.tv_nsec) * 1e-6;
+ const float diff = gs_platform_elapsed_time() - t_start;
- if (diff + dt < frametime * frames) {
+ if (diff + dt < frametime * frames
+ // also wait for the first frame, this prevents possible jitter
+ || (frames == 1 && ctxp->new_frame)) {
gs_platform_sleep(dt);
continue;
}
- pthread_mutex_lock(&ctxp->lock);
+ int locked = 0;
+ if (ctxp->new_frame == 0) {
+ locked = 1;
+ } else {
+ int check = 1;
+ locked = atomic_compare_exchange_strong(&ctxp->new_frame, &check, 0);
+ }
+ if (!locked)
+ continue;
+
if (!prerendered) res = gs_avdecode_next_frame(ctx);
+
ctxp->new_frame = 1;
- pthread_mutex_unlock(&ctxp->lock);
frames++;
+
prerendered = 0;
if (res < 0) break;
}
- pthread_mutex_lock(&ctxp->lock);
ctxp->done = 1;
- pthread_mutex_unlock(&ctxp->lock);
- gs_platform_sleep(0.001f);
- pthread_mutex_lock(&ctxp->lock);
- gs_avdecode_destroy(ctx, NULL);
- pthread_mutex_destroy(&ctxp->lock);
return NULL;
}
@@ -428,16 +430,25 @@ gs_avdecode_pthread_play_video(gs_avdecode_pthread_t* ctxp, pthread_t* thread, c
if (!ctxp) return 2;
*ctxp = (gs_avdecode_pthread_t){0};
- pthread_mutex_init(&ctxp->lock, NULL);
+ pthread_attr_init(&ctxp->attr);
+ pthread_attr_setdetachstate(&ctxp->attr, PTHREAD_CREATE_DETACHED);
int res = gs_avdecode_init(path, &ctxp->video, desc, out);
if (res) return res;
- pthread_create(thread, NULL, &_gs_avdecode_pthread_player, ctxp);
+ pthread_create(thread, &ctxp->attr, &_gs_avdecode_pthread_player, ctxp);
// TODO: error code from pthread functions as well
return res;
}
+void
+gs_avdecode_pthread_destroy(gs_avdecode_pthread_t* ctxp, pthread_t* thread, gs_asset_texture_t* tex)
+{
+
+ pthread_attr_destroy(&ctxp->attr);
+ gs_avdecode_destroy(&ctxp->video, tex);
+}
+
#endif // GS_AVDECODE_IMPL
#endif // GS_AVDECODE_H_
diff --git a/source/main.c b/source/main.c
@@ -12,7 +12,7 @@ static gs_asset_texture_t tex;
static gs_asset_texture_t ptex;
static gs_avdecode_ctx_t video;
static gs_avdecode_pthread_t pvideo;
-static pthread_t t;
+static pthread_t video_thread;
void app_update()
{
@@ -37,16 +37,17 @@ void app_update()
gsi_rectvd(&gsi, gs_v2s(0.0f), fb, gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES);
if (!pvideo.done) {
- pthread_mutex_lock(&pvideo.lock);
- if (pvideo.new_frame) {
- pvideo.new_frame = 0;
+ gs_avdecode_aquire_m(&pvideo,
memcpy(*ptex.desc.data, *pvideo.video.img, pvideo.video.img_sz);
gs_graphics_texture_request_update(&cb, ptex.hndl, &ptex.desc);
- }
- pthread_mutex_unlock(&pvideo.lock);
+ );
+
+ gsi_texture(&gsi, ptex.hndl);
+ gsi_rectvd(&gsi, gs_v2(fb.x/2, fb.y/2), gs_v2(fb.x/2, fb.y/2), gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES);
+ } else if (pvideo.done > 0) {
+ gs_avdecode_pthread_destroy(&pvideo, &video_thread, &ptex);
+ pvideo.done = -1;
}
- gsi_texture(&gsi, ptex.hndl);
- gsi_rectvd(&gsi, gs_v2(fb.x/2, fb.y/2), gs_v2(fb.x/2, fb.y/2), gs_v2s(0.f), gs_v2s(1.f), GS_COLOR_WHITE, GS_GRAPHICS_PRIMITIVE_TRIANGLES);
gsi_renderpass_submit(&gsi, &cb, gs_v4(0, 0, fb.x, fb.y), gs_color(10, 10, 10, 255));
gs_graphics_command_buffer_submit(&cb);
@@ -59,7 +60,7 @@ void app_init()
int res = gs_avdecode_init(filename, &video, NULL, &tex);
- int res2 = gs_avdecode_pthread_play_video(&pvideo, &t, filename, NULL, &ptex);
+ int res2 = gs_avdecode_pthread_play_video(&pvideo, &video_thread, filename, NULL, &ptex);
if (res) {
gs_println("Unable to initialize video '%s' (error code %d)", filename, res);
@@ -70,6 +71,7 @@ void app_init()
void app_shutdown()
{
gs_avdecode_destroy(&video, &tex);
+
gs_immediate_draw_free(&gsi);
gs_command_buffer_free(&cb);
}
@@ -88,7 +90,6 @@ gs_main(int32_t argc, char** argv)
.window = {
.width = 800,
.height = 600,
- .frame_rate = 30,
},
.init = app_init,
.update = app_update,