tags tweak, rework canvas lock/refresh, vgui/cgui segv if closed while playing
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / vdevicex11.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008-2017 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "assets.h"
23 #include "autos.h"
24 #include "bccapture.h"
25 #include "bccmodels.h"
26 #include "bcsignals.h"
27 #include "canvas.h"
28 #include "edl.h"
29 #include "edlsession.h"
30 #include "file.h"
31 #include "maskauto.h"
32 #include "maskautos.h"
33 #include "mwindow.h"
34 #include "mwindowgui.h"
35 #include "playback3d.h"
36 #include "playbackconfig.h"
37 #include "preferences.h"
38 #include "recordconfig.h"
39 #include "strategies.inc"
40 #include "vdevicex11.h"
41 #include "vframe.h"
42 #include "videodevice.h"
43 #include "videowindow.h"
44 #include "videowindowgui.h"
45
46 #include <string.h>
47 #include <unistd.h>
48
49 VDeviceX11::VDeviceX11(VideoDevice *device, Canvas *output)
50  : VDeviceBase(device)
51 {
52         reset_parameters();
53         this->output = output;
54 }
55
56 VDeviceX11::~VDeviceX11()
57 {
58         close_all();
59 }
60
61 int VDeviceX11::reset_parameters()
62 {
63         output_frame = 0;
64         window_id = 0;
65         bitmap = 0;
66         bitmap_type = 0;
67         bitmap_w = 0;
68         bitmap_h = 0;
69         output_x1 = 0;
70         output_y1 = 0;
71         output_x2 = 0;
72         output_y2 = 0;
73         canvas_x1 = 0;
74         canvas_y1 = 0;
75         canvas_x2 = 0;
76         canvas_y2 = 0;
77         capture_bitmap = 0;
78         color_model_selected = 0;
79         is_cleared = 0;
80         return 0;
81 }
82
83 int VDeviceX11::open_input()
84 {
85 //printf("VDeviceX11::open_input 1\n");
86         capture_bitmap = new BC_Capture(device->in_config->w,
87                 device->in_config->h,
88                 device->in_config->screencapture_display);
89 //printf("VDeviceX11::open_input 2\n");
90         capture_bitmap->bars_on(SCREENCAP_PIXELS, SCREENCAP_COLOR,
91                 device->input_x, device->input_y,
92                 device->in_config->w, device->in_config->h);
93
94         return 0;
95 }
96
97 int VDeviceX11::open_output()
98 {
99         if( !output ) return 0;
100         BC_WindowBase *window =
101                 output->lock_canvas("VDeviceX11::open_output");
102         if( window ) {
103                 if( !device->single_frame )
104                         output->start_video();
105                 else
106                         output->start_single();
107         }
108         output->unlock_canvas();
109         return 0;
110 }
111
112
113 int VDeviceX11::output_visible()
114 {
115         int ret = 0;
116         if( output ) {
117                 BC_WindowBase *window =
118                         output->lock_canvas("VDeviceX11::output_visible");
119                 ret = !window || window->get_hidden() ? 0 : 1;
120                 output->unlock_canvas();
121         }
122         return ret;
123 }
124
125
126 int VDeviceX11::close_all()
127 {
128         if( output ) {
129                 BC_WindowBase *window =
130                         output->lock_canvas("VDeviceX11::close_all");
131                 int video_on = window ? window->get_video_on() : 0;
132 // Update the status bug
133                 if( !device->single_frame )
134                         output->stop_video();
135                 else
136                         output->stop_single();
137
138                 if( output_frame )
139                         output->update_refresh(device, output_frame);
140 // if the last frame is good, don't draw over it
141                 if( !video_on || output->need_overlays() )
142                         output->draw_refresh(1);
143         }
144
145         delete bitmap;          bitmap = 0;
146         delete output_frame;    output_frame = 0;
147         delete capture_bitmap;  capture_bitmap = 0;
148
149         if( output )
150                 output->unlock_canvas();
151
152         reset_parameters();
153
154         return 0;
155 }
156
157 int VDeviceX11::read_buffer(VFrame *frame)
158 {
159         capture_bitmap->bars_reposition(
160                 device->input_x, device->input_y,
161                 device->in_config->w, device->in_config->h);
162
163         capture_bitmap->capture_frame(frame,
164                 device->input_x, device->input_y, device->do_cursor);
165         return 0;
166 }
167
168
169 int VDeviceX11::get_best_colormodel(Asset *asset)
170 {
171         return File::get_best_colormodel(asset, SCREENCAPTURE);
172 //      return BC_RGB888;
173 }
174
175
176 int VDeviceX11::get_display_colormodel(int file_colormodel)
177 {
178         int result = -1;
179
180         if( device->out_config->driver == PLAYBACK_X11_GL ) {
181                 if( file_colormodel == BC_RGB888 ||
182                     file_colormodel == BC_RGBA8888 ||
183                     file_colormodel == BC_YUV888 ||
184                     file_colormodel == BC_YUVA8888 ||
185                     file_colormodel == BC_RGB_FLOAT ||
186                     file_colormodel == BC_RGBA_FLOAT ) {
187                         return file_colormodel;
188                 }
189
190                 return BC_RGB888;
191         }
192
193         if( !device->single_frame ) {
194                 switch( file_colormodel ) {
195                 case BC_YUV420P:
196                 case BC_YUV422P:
197                 case BC_YUV422:
198                         result = file_colormodel;
199                         break;
200                 }
201         }
202
203         if( result < 0 ) {
204                 switch( file_colormodel ) {
205                 case BC_RGB888:
206                 case BC_RGBA8888:
207                 case BC_YUV888:
208                 case BC_YUVA8888:
209                 case BC_RGB_FLOAT:
210                 case BC_RGBA_FLOAT:
211                         result = file_colormodel;
212                         break;
213
214                 default: {
215                         BC_WindowBase *window = output->get_canvas();
216                         result = window ? window->get_color_model() : BC_RGB888;
217                         break; }
218                 }
219         }
220
221         return result;
222 }
223
224
225 void VDeviceX11::new_output_buffer(VFrame **result, int file_colormodel, EDL *edl)
226 {
227 // printf("VDeviceX11::new_output_buffer %d hardware_scaling=%d\n",
228 // __LINE__, bitmap ? bitmap->hardware_scaling() : 0);
229         BC_WindowBase *window =
230                 output->lock_canvas("VDeviceX11::new_output_buffer");
231 // Get the best colormodel the display can handle.
232         int display_colormodel = get_display_colormodel(file_colormodel);
233
234 //printf("VDeviceX11::new_output_buffer %d file_colormodel=%d display_colormodel=%d\n",
235 // __LINE__, file_colormodel, display_colormodel);
236 // Only create OpenGL Pbuffer and texture.
237         if( device->out_config->driver == PLAYBACK_X11_GL ) {
238 // Create bitmap for initial load into texture.
239 // Not necessary to do through Playback3D.....yet
240                 if( !output_frame ) {
241                         output_frame = new VFrame(device->out_w, device->out_h, file_colormodel);
242                 }
243
244                 window_id = window->get_id();
245                 output_frame->set_opengl_state(VFrame::RAM);
246         }
247         else {
248                 output->get_transfers(edl,
249                         output_x1, output_y1, output_x2, output_y2,
250                         canvas_x1, canvas_y1, canvas_x2, canvas_y2,
251 // Canvas may be a different size than the temporary bitmap for pure software
252                         -1, -1);
253                 canvas_w = canvas_x2 - canvas_x1;
254                 canvas_h = canvas_y2 - canvas_y1;
255 // can the direct frame be used?
256                 int direct_supported =
257                         device->out_config->use_direct_x11 &&
258                         !output->xscroll && !output->yscroll &&
259                         output_x1 == 0 && output_x2 == device->out_w &&
260                         output_y1 == 0 && output_y2 == device->out_h;
261
262 // file wants direct frame but we need a temp
263                 if( !direct_supported && file_colormodel == BC_BGR8888 )
264                         file_colormodel = BC_RGB888;
265
266 // Conform existing bitmap to new colormodel and output size
267                 if( bitmap ) {
268 // printf("VDeviceX11::new_output_buffer %d bitmap=%dx%d canvas=%dx%d canvas=%dx%d\n",
269 // __LINE__, bitmap->get_w(), bitmap->get_h(), canvas_w, canvas_h,);
270                         int size_change = (
271                                 bitmap->get_w() != canvas_w ||
272                                 bitmap->get_h() != canvas_h );
273
274 // Restart if output size changed or output colormodel changed.
275 // May have to recreate if transferring between windowed and fullscreen.
276                         if( !color_model_selected ||
277                             file_colormodel != output_frame->get_color_model() ||
278                             (!bitmap->hardware_scaling() && size_change) ) {
279 //printf("VDeviceX11::new_output_buffer %d file_colormodel=%d prev "
280 // "file_colormodel=%d bitmap=%p output_frame=%p\n", __LINE__,
281 // file_colormodel, output_frame->get_color_model(), bitmap, output_frame);
282                                 delete bitmap;        bitmap = 0;
283                                 delete output_frame;  output_frame = 0;
284 // Clear borders if size changed
285                                 if( size_change ) {
286 //printf("VDeviceX11::new_output_buffer %d w=%d h=%d "
287 // "canvas_x1=%d canvas_y1=%d canvas_x2=%d canvas_y2=%d\n",
288 // __LINE__, // (int)output->w, (int)output->h,
289 // (int)canvas_x1, (int)canvas_y1, (int)canvas_x2, (int)canvas_y2);
290                                         window->set_color(BLACK);
291
292                                         if( canvas_y1 > 0 ) {
293                                                 window->draw_box(0, 0, output->w, canvas_y1);
294                                                 window->flash(0, 0, output->w, canvas_y1);
295                                         }
296
297                                         if( canvas_y2 < output->h ) {
298                                                 window->draw_box(0, canvas_y2, output->w, output->h - canvas_y2);
299                                                 window->flash(0, canvas_y2, output->w, output->h - canvas_y2);
300                                         }
301
302                                         if( canvas_x1 > 0 ) {
303                                                 window->draw_box(0, canvas_y1, canvas_x1, canvas_y2 - canvas_y1);
304                                                 window->flash(0, canvas_y1, canvas_x1, canvas_y2 - canvas_y1);
305                                         }
306
307                                         if( canvas_x2 < output->w ) {
308                                                 window->draw_box(canvas_x2, canvas_y1,
309                                                         output->w - canvas_x2, canvas_y2 - canvas_y1);
310                                                 window->flash(canvas_x2, canvas_y1,
311                                                         output->w - canvas_x2, canvas_y2 - canvas_y1);
312                                         }
313                                 }
314                         }
315                 }
316
317 // Create new bitmap
318                 if( !bitmap ) {
319                         int use_direct = 0;
320                         bitmap_type = BITMAP_TEMP;
321 //printf("VDeviceX11::new_output_buffer %d file_colormodel=%d display_colormodel=%d\n",
322 // __LINE__, file_colormodel, display_colormodel);
323
324 // Try hardware accelerated
325                         switch( display_colormodel ) {
326 // blit from the codec directly to the window, using the standard X11 color model.
327 // Must scale in the codec.  No cropping
328                         case BC_BGR8888:
329                                 if( direct_supported ) {
330                                         bitmap_type = BITMAP_PRIMARY;
331                                         use_direct = 1;
332                                 }
333                                 break;
334
335                         case BC_YUV420P:
336                                 if( device->out_config->driver == PLAYBACK_X11_XV &&
337                                     window->accel_available(display_colormodel, 0) &&
338                                     !output->use_scrollbars )
339                                         bitmap_type = BITMAP_PRIMARY;
340                                 break;
341
342                         case BC_YUV422P:
343                                 if( device->out_config->driver == PLAYBACK_X11_XV &&
344                                     window->accel_available(display_colormodel, 0) &&
345                                     !output->use_scrollbars )
346                                         bitmap_type = BITMAP_PRIMARY;
347                                 else if( device->out_config->driver == PLAYBACK_X11_XV &&
348                                     window->accel_available(BC_YUV422, 0) ) {
349                                         bitmap = new BC_Bitmap(window,
350                                                 device->out_w, device->out_h, BC_YUV422, 1);
351                                 }
352                                 break;
353
354                         case BC_YUV422:
355                                 if( device->out_config->driver == PLAYBACK_X11_XV &&
356                                     window->accel_available(display_colormodel, 0) &&
357                                     !output->use_scrollbars ) {
358                                         bitmap_type = BITMAP_PRIMARY;
359                                 }
360                                 else if( device->out_config->driver == PLAYBACK_X11_XV &&
361                                     window->accel_available(BC_YUV422P, 0) ) {
362                                         bitmap = new BC_Bitmap(window,
363                                                 device->out_w, device->out_h, BC_YUV422P, 1);
364                                 }
365                                 break;
366                         }
367                         if( bitmap_type == BITMAP_PRIMARY ) {
368                                 int bitmap_w = use_direct ? canvas_w : device->out_w;
369                                 int bitmap_h = use_direct ? canvas_h : device->out_h;
370                                 bitmap = new BC_Bitmap(window,
371                                         bitmap_w, bitmap_h,  display_colormodel, -1);
372                                 output_frame = new VFrame(bitmap,
373                                         bitmap_w, bitmap_h, display_colormodel, -1);
374                         }
375 // Make an intermediate frame
376                         if( !bitmap ) {
377                                 display_colormodel = window->get_color_model();
378 // printf("VDeviceX11::new_output_buffer %d creating temp display_colormodel=%d "
379 // "file_colormodel=%d %dx%d %dx%d %dx%d\n", __LINE__,
380 // display_colormodel, file_colormodel, device->out_w, device->out_h,
381 // window->get_w(), window->get_h(), canvas_w, canvas_h);
382                                 bitmap = new BC_Bitmap(window,
383                                         canvas_w, canvas_h, display_colormodel, 1);
384                                 bitmap_type = BITMAP_TEMP;
385                         }
386
387                         if( bitmap_type == BITMAP_TEMP ) {
388 // Intermediate frame
389 //printf("VDeviceX11::new_output_buffer %d creating output_frame\n", __LINE__);
390                                 output_frame = new VFrame(device->out_w, device->out_h, file_colormodel);
391                         }
392                         color_model_selected = 1;
393                 }
394                 else if( bitmap_type == BITMAP_PRIMARY ) {
395                         output_frame->set_memory(bitmap);
396                 }
397         }
398
399         *result = output_frame;
400 //printf("VDeviceX11::new_output_buffer 10 %d\n", window->get_window_lock());
401
402         output->unlock_canvas();
403 }
404
405
406 int VDeviceX11::start_playback()
407 {
408 // Record window is initialized when its monitor starts.
409         if( !device->single_frame )
410                 output->start_video();
411         return 0;
412 }
413
414 int VDeviceX11::stop_playback()
415 {
416         if( !device->single_frame )
417                 output->stop_video();
418 // Record window goes back to monitoring
419 // get the last frame played and store it in the video_out
420         return 0;
421 }
422
423 int VDeviceX11::write_buffer(VFrame *output_channels, EDL *edl)
424 {
425         BC_WindowBase *window =
426                 output->lock_canvas("VDeviceX11::write_buffer");
427 //      if( device->out_config->driver == PLAYBACK_X11_GL &&
428 //          output_frame->get_color_model() != BC_RGB888 ) {
429 // this is handled by overlay call in virtualvnode, using flatten alpha
430 // invoked when is_nested = -1 is passed to vdevicex11->overlay(...)
431 //      }
432
433 // printf("VDeviceX11::write_buffer %d %d bitmap_type=%d\n",
434 // __LINE__,
435 // window->get_video_on(),
436 // bitmap_type);
437
438 //      int use_bitmap_extents = 0;
439 //      canvas_w = -1;  canvas_h = -1;
440 // // Canvas may be a different size than the temporary bitmap for pure software
441 //      if( bitmap_type == BITMAP_TEMP && !bitmap->hardware_scaling() ) {
442 //              canvas_w = bitmap->get_w();
443 //              canvas_h = bitmap->get_h();
444 //      }
445 //
446 //      output->get_transfers(edl,
447 //              output_x1, output_y1, output_x2, output_y2,
448 //              canvas_x1, canvas_y1, canvas_x2, canvas_y2,
449 //              canvas_w, canvas_h);
450
451 // Convert colormodel
452         if( bitmap_type == BITMAP_TEMP ) {
453 // printf("VDeviceX11::write_buffer 1 %d %d, %d %d %d %d -> %d %d %d %d\n",
454 //   output->w, output->h, in_x, in_y, in_w, in_h, out_x, out_y, out_w, out_h);
455 // fflush(stdout);
456
457 // printf("VDeviceX11::write_buffer %d output_channels=%p\n", __LINE__, output_channels);
458 // printf("VDeviceX11::write_buffer %d input color_model=%d output color_model=%d\n",
459 // __LINE__, output_channels->get_color_model(), bitmap->get_color_model());
460                 if( bitmap->hardware_scaling() ) {
461                         BC_CModels::transfer(bitmap->get_row_pointers(), output_channels->get_rows(), 0, 0, 0,
462                                 output_channels->get_y(), output_channels->get_u(), output_channels->get_v(),
463                                 0, 0, output_channels->get_w(), output_channels->get_h(),
464                                 0, 0, bitmap->get_w(), bitmap->get_h(),
465                                 output_channels->get_color_model(), bitmap->get_color_model(),
466                                 -1, output_channels->get_w(), bitmap->get_w());
467                 }
468                 else {
469                         BC_CModels::transfer(bitmap->get_row_pointers(), output_channels->get_rows(), 0, 0, 0,
470                                 output_channels->get_y(), output_channels->get_u(), output_channels->get_v(),
471                                 (int)output_x1, (int)output_y1, (int)(output_x2 - output_x1), (int)(output_y2 - output_y1),
472                                 0, 0, (int)(canvas_x2 - canvas_x1), (int)(canvas_y2 - canvas_y1),
473                                 output_channels->get_color_model(), bitmap->get_color_model(),
474                                 -1, output_channels->get_w(), bitmap->get_w());
475                 }
476         }
477
478 //printf("VDeviceX11::write_buffer 4 %p\n", bitmap);
479 //for( i = 0; i < 1000; i += 4 ) bitmap->get_data()[i] = 128;
480 //printf("VDeviceX11::write_buffer 2 %d %d %d\n", bitmap_type,
481 //      bitmap->get_color_model(),
482 //      output->get_color_model());fflush(stdout);
483
484 // printf("VDeviceX11::write_buffer %d %dx%d %f %f %f %f -> %f %f %f %f\n",
485 // __LINE__, // output->w, output->h,
486 // output_x1, output_y1, output_x2, output_y2,
487 // canvas_x1, canvas_y1, canvas_x2, canvas_y2);
488
489 // Cause X server to display it
490         if( device->out_config->driver == PLAYBACK_X11_GL ) {
491 // Output is drawn in close_all if no video.
492                 if( window->get_video_on() ) {
493                         canvas_w = -1;  canvas_h = -1;
494 // Canvas may be a different size than the temporary bitmap for pure software
495                         if( bitmap_type == BITMAP_TEMP &&
496                                 !bitmap->hardware_scaling() ) {
497                                 canvas_w = bitmap->get_w();
498                                 canvas_h = bitmap->get_h();
499                         }
500
501                         output->get_transfers(edl,
502                                 output_x1, output_y1, output_x2, output_y2,
503                                 canvas_x1, canvas_y1, canvas_x2, canvas_y2,
504                                 canvas_w, canvas_h);
505
506 //printf("VDeviceX11::write_buffer %d\n", __LINE__);
507 // Draw output frame directly.  Not used for compositing.
508                         output->unlock_canvas();
509                         output->mwindow->playback_3d->write_buffer(output, output_frame,
510                                 output_x1, output_y1, output_x2, output_y2,
511                                 canvas_x1, canvas_y1, canvas_x2, canvas_y2,
512                                 is_cleared);
513                         is_cleared = 0;
514                         return 0;
515                 }
516         }
517         else {
518                 if( bitmap->hardware_scaling() ) {
519                         window->draw_bitmap(bitmap, !device->single_frame,
520                                 (int)canvas_x1, (int)canvas_y1,
521                                 (int)(canvas_x2 - canvas_x1), (int)(canvas_y2 - canvas_y1),
522                                 (int)output_x1, (int)output_y1,
523                                 (int)(output_x2 - output_x1), (int)(output_y2 - output_y1),
524                                 0);
525                 }
526                 else {
527 //printf("VDeviceX11::write_buffer %d x=%d y=%d w=%d h=%d\n",
528 // __LINE__, (int)canvas_x1, (int)canvas_y1,
529 // window->get_w(), window->get_h());
530                         window->draw_bitmap(bitmap, !device->single_frame,
531                                 (int)canvas_x1, (int)canvas_y1,
532                                 (int)(canvas_x2 - canvas_x1), (int)(canvas_y2 - canvas_y1),
533                                 0, 0,
534                                 (int)(canvas_x2 - canvas_x1), (int)(canvas_y2 - canvas_y1),
535                                 0);
536 //printf("VDeviceX11::write_buffer %d bitmap=%p\n", __LINE__, bitmap);
537                 }
538         }
539
540         output->unlock_canvas();
541         return 0;
542 }
543
544
545 void VDeviceX11::clear_output()
546 {
547         is_cleared = 1;
548         output->mwindow->playback_3d->clear_output(output, 0);
549         output->mwindow->playback_3d->clear_output(output, output_frame);
550 }
551
552
553 void VDeviceX11::clear_input(VFrame *frame)
554 {
555         this->output->mwindow->playback_3d->clear_input(this->output, frame);
556 }
557
558 void VDeviceX11::convert_cmodel(VFrame *output, int dst_cmodel)
559 {
560         this->output->mwindow->playback_3d->convert_cmodel(this->output,
561                 output, 
562                 dst_cmodel);
563 }
564
565 void VDeviceX11::do_camera(VFrame *output, VFrame *input,
566         float in_x1, float in_y1, float in_x2, float in_y2,
567         float out_x1, float out_y1, float out_x2, float out_y2)
568 {
569         this->output->mwindow->playback_3d->do_camera(this->output,
570                 output, input,
571                 in_x1, in_y1, in_x2, in_y2,
572                 out_x1, out_y1, out_x2, out_y2);
573 }
574
575
576 void VDeviceX11::do_fade(VFrame *output_temp, float fade)
577 {
578         this->output->mwindow->playback_3d->do_fade(this->output, output_temp, fade);
579 }
580
581 bool VDeviceX11::can_mask(int64_t start_position_project, MaskAutos *keyframe_set)
582 {
583         Auto *current = 0;
584         MaskAuto *keyframe = (MaskAuto*)keyframe_set->
585                 get_prev_auto(start_position_project, PLAY_FORWARD, current);
586         return keyframe->disable_opengl_masking ? 0 : 1;
587 }
588
589 void VDeviceX11::do_mask(VFrame *output_temp, int64_t start_position_project,
590                 MaskAutos *keyframe_set, MaskAuto *keyframe, MaskAuto *default_auto)
591 {
592         this->output->mwindow->playback_3d->do_mask(output, output_temp,
593                 start_position_project, keyframe_set, keyframe, default_auto);
594 }
595
596 void VDeviceX11::overlay(VFrame *output_frame, VFrame *input,
597                 float in_x1, float in_y1, float in_x2, float in_y2,
598                 float out_x1, float out_y1, float out_x2, float out_y2,
599                 float alpha, int mode, EDL *edl, int is_nested)
600 {
601         int interpolation_type = edl->session->interpolation_type;
602         output->mwindow->playback_3d->overlay(output, input,
603                 in_x1, in_y1, in_x2, in_y2,
604                 out_x1, out_y1, out_x2, out_y2, alpha, // 0 - 1
605                 mode, interpolation_type, output_frame, is_nested);
606 }
607
608 void VDeviceX11::run_plugin(PluginClient *client)
609 {
610         output->mwindow->playback_3d->run_plugin(output, client);
611 }
612
613 void VDeviceX11::copy_frame(VFrame *dst, VFrame *src)
614 {
615         output->mwindow->playback_3d->copy_from(output, dst, src, 1);
616 }
617