1 #include "demo.h"
2 #include <pthread.h>
3 #include <stdatomic.h>
4
5 // this can take a long time, especially in large terminals; we cap execution
6 // at fifteen seconds (the true intended runtime), and drop frames when behind.
7 #define MAX_SECONDS 15
8
9 // issue #2390 adds ncvisual_frame_count(). until then...FIXME
10 #define VIDEO_FRAMES 862
11
12 static const char* leg[] = {
13 " 88 88 88 88 88 88 88 ",
14 " \"\" 88 88 88 88 \"\" \"\" ,d ",
15 " 88 88 88 88 88 ",
16 " ,adPPYYba, 8b,dPPYba, 88 ,adPPYba, 88 ,d8 88,dPPYba, 88 ,adPPYYba, ,adPPYba, 88 ,d8 88 ,adPPYba, 88 8b,dPPYba, MM88MMM ",
17 " \"\" `Y8 88P' `\"8a 88 a8\" \"\" 88 ,a8\" 88P' \"8a 88 \"\" `Y8 a8\" \"\" 88 ,a8\" 88 a8\" \"8a 88 88P' `\"8a 88 ",
18 " ,adPPPPP88 88 88 88 8b 8888[ 88 d8 88 ,adPPPPP88 8b 8888[ 88 8b d8 88 88 88 88 ",
19 " 88, ,88 88 88 88 \"8a, ,aa 88`\"Yba, 88b, ,a8\" 88 88, ,88 \"8a, ,aa 88`\"Yba, 88 \"8a, ,a8\" 88 88 88 88, ",
20 " `\"8bbdP\"Y8 88 88 88 `\"Ybbd8\"' 88 `Y8a 8Y\"Ybbd8\"' 88 `\"8bbdP\"Y8 `\"Ybbd8\"' 88 `Y8a 88 `\"YbbdP\"' 88 88 88 \"Y888 ",
21 " ,88 ",
22 " 888P ",
23 };
24
25 static struct ncplane*
make_slider(struct notcurses * nc,int dimx)26 make_slider(struct notcurses* nc, int dimx){
27 const int len = strlen(leg[0]);
28 // 862 frames in the video
29 const int REPS = 862 / len + dimx / len + 2;
30 struct ncplane_options nopts = {
31 .y = 1,
32 .x = 0,
33 .rows = sizeof(leg) / sizeof(*leg),
34 .cols = len * REPS,
35 .name = "scrl",
36 };
37 struct ncplane* n = ncplane_create(notcurses_stdplane(nc), &nopts);
38 uint64_t channels = 0;
39 ncchannels_set_fg_alpha(&channels, NCALPHA_TRANSPARENT);
40 ncchannels_set_bg_alpha(&channels, NCALPHA_TRANSPARENT);
41 ncplane_set_base(n, " ", 0, channels);
42 ncplane_set_scrolling(n, true);
43 int r = 0x5f;
44 int g = 0xaf;
45 int b = 0x84;
46 ncplane_set_bg_alpha(n, NCALPHA_TRANSPARENT);
47 for(int x = 0 ; x < REPS ; ++x){
48 for(size_t l = 0 ; l < sizeof(leg) / sizeof(*leg) ; ++l){
49 ncplane_set_fg_rgb8_clipped(n, r + 0x8 * l, g + 0x8 * l, b + 0x8 * l);
50 if(ncplane_set_bg_rgb8(n, (l + 1) * 0x2, 0x20, (l + 1) * 0x2)){
51 ncplane_destroy(n);
52 return NULL;
53 }
54 if(ncplane_putstr_yx(n, l, x * len, leg[l]) != len){
55 ncplane_destroy(n);
56 return NULL;
57 }
58 }
59 int t = r;
60 r = g;
61 g = b;
62 b = t;
63 }
64 return n;
65 }
66
67 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
68 static pthread_mutex_t render_lock = PTHREAD_MUTEX_INITIALIZER;
69
70 struct marsh {
71 struct notcurses* nc;
72 struct ncvisual* ncv; // video stream, one copy per thread
73 struct ncplane* slider; // text plane at top, sliding to the left
74 float dm; // delay multiplier
75 int next_frame;
76 uint64_t startns; // when we started (CLOCK_MONOTONIC) for frame dropping
77 int* frame_to_render; // shared; protected by renderlock
78 int* dropped; // shared; protected by renderlock
79 struct ncplane** lplane; // shared; plane to destroy, renderlocked
80 };
81
82 // make a plane on a new pile suitable for rendering a frame of the video
83 static int
make_plane(struct notcurses * nc,struct ncplane ** t)84 make_plane(struct notcurses* nc, struct ncplane** t){
85 unsigned dimy, dimx;
86 notcurses_stddim_yx(nc, &dimy, &dimx);
87 // FIXME want a resizecb
88 struct ncplane_options opts = {
89 .rows = dimy - 1,
90 .cols = dimx,
91 .name = "bmap",
92 };
93 *t = ncpile_create(nc, &opts);
94 if(!*t){
95 return -1;
96 }
97 return 0;
98 }
99
100 // returns the index of the next frame, which can immediately begin to be
101 // rendered onto the thread's plane. if we're behind the clock, we don't
102 // bother blitting it, and drop the plane to signal as much.
103 static int
get_next_frame(struct marsh * m,struct ncvisual_options * vopts)104 get_next_frame(struct marsh* m, struct ncvisual_options* vopts){
105 // one does the odds, and one the evens. load two unless we're the even,
106 // and it's the first frame.
107 if(m->next_frame){
108 if(ncvisual_decode(m->ncv)){
109 return -1;
110 }
111 }
112 if(ncvisual_decode(m->ncv)){
113 return -1;
114 }
115 uint64_t ns = clock_getns(CLOCK_MONOTONIC);
116 int ret = m->next_frame;
117 uint64_t deadline = m->startns + (m->next_frame + 1) * MAX_SECONDS * (NANOSECS_IN_SEC / VIDEO_FRAMES);
118 // if we've missed the deadline, drop the frame 99% of the time (you've still
119 // got to draw now and again, or else there's just the initial frame hanging
120 // there for ~900 frames of crap).
121 if(ns > deadline && (rand() % 100)){
122 ncplane_destroy(vopts->n);
123 vopts->n = NULL;
124 }else if(ncvisual_blit(m->nc, m->ncv, vopts) == NULL){
125 return -1;
126 }
127 m->next_frame += 2;
128 return ret;
129 }
130
131 static void*
xray_thread(void * vmarsh)132 xray_thread(void *vmarsh){
133 struct marsh* m = vmarsh;
134 struct ncplane* stdn = notcurses_stdplane(m->nc);
135 int frame = -1;
136 struct ncvisual_options vopts = {
137 .x = NCALIGN_CENTER,
138 .y = NCALIGN_CENTER,
139 .scaling = NCSCALE_SCALE_HIRES,
140 .blitter = NCBLIT_PIXEL,
141 .flags = NCVISUAL_OPTION_VERALIGNED | NCVISUAL_OPTION_HORALIGNED
142 | NCVISUAL_OPTION_ADDALPHA,
143 };
144 int ret;
145 do{
146 if(make_plane(m->nc, &vopts.n)){
147 return NULL;
148 }
149 // if we're behind where we want to be time-wise, this will return the
150 // expected frame id, but vopts.n will be NULL (and the plane we created
151 // will have been destroyed). that frame has been dropped.
152 if((frame = get_next_frame(m, &vopts)) < 0){
153 ncplane_destroy(vopts.n);
154 // FIXME need to cancel other one; it won't be able to progress
155 return NULL;
156 }
157 ret = -1;
158 // only one thread can render the standard pile at a time
159 pthread_mutex_lock(&render_lock);
160 while(*m->frame_to_render != frame){
161 pthread_cond_wait(&cond, &render_lock);
162 }
163 int x = ncplane_x(m->slider);
164 if(ncplane_move_yx(m->slider, 1, x - 1) == 0){
165 // FIXME otherwise, increment a visible drop count?
166 if(vopts.n){
167 ncplane_reparent(vopts.n, notcurses_stdplane(m->nc));
168 ncplane_move_top(vopts.n);
169 ncplane_destroy(*m->lplane);
170 *m->lplane = vopts.n;
171 ncplane_set_fg_rgb8_clipped(stdn, 96 + *m->dropped / 2, 0, 0x80);
172 ncplane_printf_aligned(stdn, 1 + ncplane_dim_y(m->slider),
173 NCALIGN_RIGHT, "%d dropped frame%s",
174 *m->dropped, *m->dropped == 0 ? "s " :
175 *m->dropped == 1 ? " " :
176 *m->dropped < 10 ? "s " :
177 *m->dropped < 100 ? "s " :
178 *m->dropped < 250 ? "s " :
179 *m->dropped < 450 ? "s " :
180 *m->dropped < 700 ? "s " : "s ");
181 ret = demo_render(m->nc);
182 }else{
183 // FIXME i'd like to at least render the updated drop count and the
184 // moved slider, but even that's too slow with stupid shitty sixel,
185 // due to redrawing far too much of it see #2380
186 ++*m->dropped;
187 ret = 0;
188 }
189 }
190 *m->frame_to_render = frame + 1;
191 pthread_mutex_unlock(&render_lock);
192 pthread_cond_signal(&cond);
193 vopts.n = NULL;
194 }while(ret == 0);
195 return NULL;
196 }
197
xray_demo(struct notcurses * nc,uint64_t startns)198 int xray_demo(struct notcurses* nc, uint64_t startns){
199 if(!notcurses_canopen_videos(nc)){
200 return 0;
201 }
202 unsigned dimx, dimy;
203 notcurses_term_dim_yx(nc, &dimy, &dimx);
204 ncplane_erase(notcurses_stdplane(nc));
205 char* path = find_data("notcursesIII.mov");
206 struct ncvisual* ncv1 = ncvisual_from_file(path);
207 struct ncvisual* ncv2 = ncvisual_from_file(path);
208 free(path);
209 if(ncv1 == NULL || ncv2 == NULL){
210 return -1;
211 }
212 struct ncplane* slider = make_slider(nc, dimx);
213 if(slider == NULL){
214 ncvisual_destroy(ncv1);
215 ncvisual_destroy(ncv2);
216 return -1;
217 }
218 uint64_t stdc = 0;
219 ncchannels_set_bg_rgb(&stdc, 0);
220 ncplane_set_base(notcurses_stdplane(nc), "", 0, stdc);
221 ncplane_set_bg_rgb(notcurses_stdplane(nc), 0);
222 // returns non-zero if the selected blitter isn't available
223 pthread_t tid1, tid2;
224 int last_frame = 0;
225 int dropped = 0;
226 struct ncplane* kplane = NULL; // to kill
227 struct marsh m1 = {
228 .slider = slider,
229 .nc = nc,
230 .next_frame = 0,
231 .frame_to_render = &last_frame,
232 .dm = notcurses_check_pixel_support(nc) ? 0 : 0.5 * delaymultiplier,
233 .ncv = ncv1,
234 .startns = startns,
235 .lplane = &kplane,
236 .dropped = &dropped,
237 };
238 struct marsh m2 = {
239 .slider = slider,
240 .nc = nc,
241 .next_frame = 1,
242 .frame_to_render = &last_frame,
243 .dm = notcurses_check_pixel_support(nc) ? 0 : 0.5 * delaymultiplier,
244 .ncv = ncv2,
245 .startns = startns,
246 .lplane = &kplane,
247 .dropped = &dropped,
248 };
249 int ret = -1;
250 if(pthread_create(&tid1, NULL, xray_thread, &m1)){
251 goto err;
252 }
253 if(pthread_create(&tid2, NULL, xray_thread, &m2)){
254 pthread_join(tid1, NULL);
255 goto err;
256 }
257 ret = pthread_join(tid1, NULL) | pthread_join(tid2, NULL);
258
259 err:
260 ncvisual_destroy(ncv1);
261 ncvisual_destroy(ncv2);
262 ncplane_destroy(slider);
263 if(kplane){
264 ncplane_destroy(kplane);
265 }
266 return ret;
267 }
268