virtual: add tdm virtual output test to tdm_test with V option 37/186937/1
authorJunkyeong Kim <jk0430.kim@samsung.com>
Thu, 16 Aug 2018 12:00:23 +0000 (21:00 +0900)
committerJunkyeong Kim <jk0430.kim@samsung.com>
Thu, 16 Aug 2018 12:00:26 +0000 (21:00 +0900)
1. execute tdm-test-server with -V option
2. execute tdm-test-client with -V option

after voutput connect from client, server send three differnt buffers to client.
client make dump commit buffers untill 10 to /tmp/.

Change-Id: I65775c8b28ef8d08c34aca9a706b770d99974a11
Signed-off-by: Junkyeong Kim <jk0430.kim@samsung.com>
tools/tdm_test_client.c
tools/tdm_test_server.c

index 1031685..bb0ba22 100644 (file)
 #include <errno.h>
 #include <time.h>
 #include <stdint.h>
+#include <png.h>
 
 #include "tdm_client.h"
 #include "tdm_macro.h"
+#include "buffers.h"
 
 typedef struct _tdm_test_client_arg {
        char *output_name;
@@ -60,6 +62,7 @@ typedef struct _tdm_test_client {
 
        int do_query;
        int do_vblank;
+       int do_voutput;
        int waiting;
 
        tdm_client *client;
@@ -93,6 +96,7 @@ static struct typestrings typestrs[] = {
 static struct optstrings optstrs[] = {
        {OPT_QRY, "qo", "output objects info", "<output_name>", "primary"},
        {OPT_TST, "v", "vblank test", "<output_name>[,<sync>][@<fps>][~<interval>][+<offset>][*fake][^vblank_name]", "primary,0@60~1+0*1^test"},
+       {OPT_TST, "V", "virtual output test", NULL, NULL},
 };
 
 static void
@@ -181,7 +185,7 @@ parse_args(tdm_test_client *data, int argc, char *argv[])
 {
        int i;
 
-       if (argc < 3) {
+       if (argc < 2) {
                usage(argv[0]);
                exit(0);
        }
@@ -196,6 +200,8 @@ parse_args(tdm_test_client *data, int argc, char *argv[])
                } else if (!strncmp(argv[i] + 1, "v", 1)) {
                        data->do_vblank = 1;
                        parse_arg_v(data, argv[++i]);
+               } else if (!strncmp(argv[i] + 1, "V", 1)) {
+                       data->do_voutput = 1;
                } else {
                        usage(argv[0]);
                        exit(0);
@@ -374,6 +380,259 @@ done:
                tdm_client_vblank_destroy(vblank);
 }
 
+#define PNG_DEPTH 8
+
+void
+_tdm_client_get_buffer_full_size(tbm_surface_h buffer, int *buffer_w, int *buffer_h)
+{
+       tbm_surface_info_s info;
+       int ret;
+
+       TDM_RETURN_IF_FAIL(buffer != NULL);
+
+       ret = tbm_surface_get_info(buffer, &info);
+       TDM_RETURN_IF_FAIL(ret == TBM_SURFACE_ERROR_NONE);
+
+       if (buffer_w) {
+               if (IS_RGB(info.format))
+                       *buffer_w = info.planes[0].stride >> 2;
+               else
+                       *buffer_w = info.planes[0].stride;
+       }
+
+       if (buffer_h)
+               *buffer_h = info.planes[0].size / info.planes[0].stride;
+}
+
+static void
+_tdm_client_dump_png(const char *file, const void *data, int width,
+                                        int height)
+{
+       FILE *fp;
+
+       fp = fopen(file, "wb");
+       TDM_RETURN_IF_FAIL(fp != NULL);
+
+       png_structp pPngStruct =
+               png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+       if (!pPngStruct) {
+               fclose(fp);
+               return;
+       }
+
+       png_infop pPngInfo = png_create_info_struct(pPngStruct);
+       if (!pPngInfo) {
+               png_destroy_write_struct(&pPngStruct, NULL);
+               fclose(fp);
+               return;
+       }
+
+       png_init_io(pPngStruct, fp);
+       png_set_IHDR(pPngStruct,
+                                pPngInfo,
+                                width,
+                                height,
+                                PNG_DEPTH,
+                                PNG_COLOR_TYPE_RGBA,
+                                PNG_INTERLACE_NONE,
+                                PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+       png_set_bgr(pPngStruct);
+       png_write_info(pPngStruct, pPngInfo);
+
+       const int pixel_size = 4;       // RGBA
+       png_bytep *row_pointers =
+               png_malloc(pPngStruct, height * sizeof(png_byte *));
+       if (!row_pointers) {
+               png_destroy_write_struct(&pPngStruct, &pPngInfo);
+               fclose(fp);
+               return;
+       }
+
+       unsigned int *blocks = (unsigned int *)data;
+       int y = 0;
+       int x = 0;
+
+       for (; y < height; ++y) {
+               png_bytep row =
+                       png_malloc(pPngStruct, sizeof(png_byte) * width * pixel_size);
+               if (!row) {
+                       for (x = 0; x < y; x++)
+                               png_free(pPngStruct, row_pointers[x]);
+                       png_free(pPngStruct, row_pointers);
+                       png_destroy_write_struct(&pPngStruct, &pPngInfo);
+                       fclose(fp);
+                       return;
+               }
+
+               row_pointers[y] = (png_bytep)row;
+               for (x = 0; x < width; ++x) {
+                       unsigned int curBlock = blocks[y * width + x];
+                       row[x * pixel_size] = (curBlock & 0xFF);
+                       row[1 + x * pixel_size] = (curBlock >> 8) & 0xFF;
+                       row[2 + x * pixel_size] = (curBlock >> 16) & 0xFF;
+                       row[3 + x * pixel_size] = (curBlock >> 24) & 0xFF;
+               }
+       }
+
+       png_write_image(pPngStruct, row_pointers);
+       png_write_end(pPngStruct, pPngInfo);
+
+       for (y = 0; y < height; y++)
+               png_free(pPngStruct, row_pointers[y]);
+       png_free(pPngStruct, row_pointers);
+
+       png_destroy_write_struct(&pPngStruct, &pPngInfo);
+
+       fclose(fp);
+}
+
+void
+_tdm_client_dump_buffer(tbm_surface_h buffer, const char *file)
+{
+       char temp[TDM_PATH_LEN] = {0,};
+       tbm_surface_info_s info;
+       int len, ret;
+       const char *ext;
+       int bo_cnt;
+       int bw, bh;
+       char *dot, *p = temp;
+       const char *file_exts[2] = {"png", "raw"};
+
+       TDM_RETURN_IF_FAIL(buffer != NULL);
+       TDM_RETURN_IF_FAIL(file != NULL);
+
+       ret = tbm_surface_map(buffer, TBM_OPTION_READ, &info);
+       TDM_RETURN_IF_FAIL(ret == TBM_SURFACE_ERROR_NONE);
+
+       if (IS_RGB(info.format))
+               ext = file_exts[0];
+       else
+               ext = file_exts[1];
+
+       dot = strrchr(file, '.');
+       if (!dot || strlen(dot + 1) != 3 || strncmp(dot + 1, ext, 3)) {
+               len = strnlen(file, TDM_PATH_LEN - 5);
+               strncat(p, file, len);
+               p += len;
+               *(p++) = '.';
+               strncat(p, ext, 3);
+               p += 3;
+               *p = '\0';
+       } else {
+               len = strnlen(file, TDM_PATH_LEN - 1);
+               strncat(p, file, len);
+               p += len;
+               *p = '\0';
+       }
+
+       _tdm_client_get_buffer_full_size(buffer, &bw, &bh);
+
+       bo_cnt = tbm_surface_internal_get_num_bos(buffer);
+       TDM_DBG("buffer: bo_cnt(%d) %dx%d(%dx%d) %c%c%c%c, plane: (%p+%d, %d,%d) (%p+%d, %d,%d) (%p+%d, %d,%d)",
+                       bo_cnt, bw, bh, info.width, info.height, FOURCC_STR(info.format),
+                       info.planes[0].ptr, info.planes[0].offset, info.planes[0].stride, info.planes[0].size,
+                       info.planes[1].ptr, info.planes[1].offset, info.planes[1].stride, info.planes[1].size,
+                       info.planes[2].ptr, info.planes[2].offset, info.planes[2].stride, info.planes[2].size);
+
+       _tdm_client_dump_png(temp, info.planes[0].ptr, bw, bh);
+
+       tbm_surface_unmap(buffer);
+
+       printf("dump %s", temp);
+}
+
+static void
+_dump_buffer(tbm_surface_h buffer, int count)
+{
+       char temp[TDM_PATH_LEN] = {0,};
+
+       snprintf(temp, TDM_PATH_LEN, "/tmp/%c%c%c%c_%dx%d_%d",
+               FOURCC_STR(tbm_surface_get_format(buffer)),
+               tbm_surface_get_width(buffer),
+               tbm_surface_get_height(buffer),
+               count);
+       _tdm_client_dump_buffer(buffer, temp);
+}
+
+static void
+_voutput_commit(tdm_client_voutput *voutput, tbm_surface_h buffer, void *user_data)
+{
+       tdm_test_client *data = (tdm_test_client *)user_data;
+       static int count = 0;
+
+       TDM_EXIT_IF_FAIL(data != NULL);
+       TDM_EXIT_IF_FAIL(buffer != NULL);
+
+       if (count < 11)
+               _dump_buffer(buffer, count);
+       count++;
+
+       printf("client: %d commited(%p)\n", count, buffer);
+       tdm_client_voutput_commit_done(voutput);
+}
+
+ static void
+_voutput_make_available_mode(tdm_client_output_mode *modes, int count)
+{
+       int i;
+       for (i = 0; i < count; i++) {
+               modes[i].clock = 25200;
+               modes[i].hdisplay = 640;
+               modes[i].hsync_start = 656;
+               modes[i].hsync_end = 752;
+               modes[i].htotal = 800;
+               modes[i].hskew = 0;
+               modes[i].vdisplay = 480;
+               modes[i].vsync_start = 490;
+               modes[i].vsync_end = 492;
+               modes[i].vtotal = 525;
+               modes[i].vscan = 0;
+               modes[i].vrefresh = 30;
+               modes[i].flags = 0;
+               modes[i].type = 0;
+               snprintf(modes[i].name, TDM_NAME_LEN, "%dx%d_%d", modes[i].hdisplay, modes[i].vdisplay, i);
+       }
+}
+
+static void
+do_voutput(tdm_test_client *data)
+{
+       tdm_client_voutput *voutput = NULL;
+       tdm_client_output *output = NULL;
+       tdm_client_output_mode modes[2];
+       tdm_error ret = TDM_ERROR_NONE;
+
+       printf("virtual output test - client\n");
+
+       voutput = tdm_client_create_voutput(data->client, "virtual-test", &ret);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       ret = tdm_client_voutput_add_commit_handler(voutput, _voutput_commit, data);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       output = tdm_client_voutput_get_client_output(voutput, &ret);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       ret = tdm_client_voutput_set_physical_size(voutput, 300, 200);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       _voutput_make_available_mode(modes, 2);
+       ret = tdm_client_voutput_set_available_modes(voutput, modes, 2);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       ret = tdm_client_output_connect(output);
+       TDM_GOTO_IF_FAIL(ret == TDM_ERROR_NONE, done);
+
+       while (1) {
+               tdm_client_handle_events_timeout(data->client, 1000);
+       }
+
+done:
+       if (voutput)
+               tdm_client_voutput_destroy(voutput);
+}
+
 static tdm_test_client ttc_data;
 
 int
@@ -391,6 +650,16 @@ main(int argc, char *argv[])
                if (ret != 0)
                        exit(0);
        }
+
+       /* for tbm_bufmgr_init */
+       const char *s  = (const char*)getenv("TBM_DISPLAY_SERVER");
+       if (!s) {
+               char buf[32];
+               snprintf(buf, sizeof(buf), "1");
+               int ret = setenv("TBM_DISPLAY_SERVER", (const char*)buf, 1);
+               if (ret != 0)
+                       exit(0);
+       }
 #endif
 
        parse_args(data, argc, argv);
@@ -409,6 +678,8 @@ main(int argc, char *argv[])
                do_query(data);
        if (data->do_vblank)
                do_vblank(data);
+       if (data->do_voutput)
+               do_voutput(data);
 
 done:
        if (data->args.output_name)
index 535462f..b439540 100644 (file)
@@ -108,6 +108,10 @@ static struct optstrings optstrs[] = {
                "<output_idx>[,<layer_idx>]~<w>x<h>[+<x>+<y>][,<h>x<v>][@<format>][*<transform>][^stream]", NULL
        },
        {
+               OPT_TST, "V", "virtual output test.\n\t\t'-l' is used to show the result on screen.",
+               NULL, NULL
+       },
+       {
                OPT_GEN, "w", "set the property of a object",
                "<prop_name>:<value>", NULL
        },
@@ -217,6 +221,7 @@ TDM_BIT_NAME_FB(buf_flag)
 typedef struct _tdm_test_server tdm_test_server;
 typedef struct _tdm_test_server_layer tdm_test_server_layer;
 typedef struct _tdm_test_server_capture tdm_test_server_capture;
+typedef struct _tdm_test_server_voutput tdm_test_server_voutput;
 
 typedef struct _tdm_test_server_prop {
        /* args */
@@ -300,11 +305,21 @@ struct _tdm_test_server_layer {
        int buf_idx;
 };
 
+struct _tdm_test_server_voutput {
+       struct list_head link;
+       tdm_test_server *data;
+       tdm_output *output;
+       tdm_layer *layer;
+       tbm_surface_h bufs[3];
+       int buf_idx;
+};
+
 struct _tdm_test_server {
        /* args */
        int do_query;
        int do_all;
        int do_vblank;
+       int do_voutput;
        int bflags;
        int b_fill;
 
@@ -312,6 +327,7 @@ struct _tdm_test_server {
        struct list_head output_list;
        struct list_head pp_list;
        struct list_head capture_list;
+       struct list_head voutput_list;
        tdm_display *display;
 };
 
@@ -320,6 +336,7 @@ static void run_test(tdm_test_server *data);
 static void output_setup(tdm_test_server_output *o);
 static void layer_show_buffer(tdm_test_server_layer *l, tbm_surface_h b);
 static void capture_attach(tdm_test_server_capture *c, tbm_surface_h b);
+static void _vlayer_show_buffer(tdm_test_server_voutput *voutput);
 
 static char*
 parse_size(tdm_size *size, char *arg)
@@ -596,6 +613,8 @@ parse_args(tdm_test_server *data, int argc, char *argv[])
                        parse_arg_b(data, argv[++i]);
                } else if (!strncmp(argv[i] + 1, "v", 1)) {
                        data->do_vblank = 1;
+               } else if (!strncmp(argv[i] + 1, "V", 1)) {
+                       data->do_voutput = 1;
                } else {
                        usage(argv[0]);
                        destroy(data);
@@ -790,6 +809,238 @@ get_tts_buffer(tbm_surface_h b)
 }
 
 static void
+_vlayer_cb_commit(tdm_layer *layer, unsigned int sequence,
+                               unsigned int tv_sec, unsigned int tv_usec, void *user_data)
+{
+       tdm_test_server_voutput *voutput = (tdm_test_server_voutput *)user_data;
+       TDM_EXIT_IF_FAIL(voutput != NULL);
+       tdm_output_conn_status status;
+       tdm_error ret;
+
+       printf("voutput cb commit:\t %d: l(%p) b(%p)\n", voutput->buf_idx, voutput->layer, voutput->bufs[voutput->buf_idx]);
+
+       ret = tdm_output_get_conn_status(voutput->output, &status);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       if (status == TDM_OUTPUT_CONN_STATUS_DISCONNECTED) return;
+
+       voutput->buf_idx++;
+       _vlayer_show_buffer(voutput);
+}
+
+static void
+_vlayer_show_buffer(tdm_test_server_voutput *voutput)
+{
+       tdm_error ret;
+       int index;
+       if (voutput->buf_idx == 3)
+               voutput->buf_idx = 0;
+
+       index = voutput->buf_idx;
+
+       ret = tdm_layer_set_buffer(voutput->layer, voutput->bufs[index]);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       ret = tdm_layer_commit(voutput->layer, _vlayer_cb_commit, voutput);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       printf("voutput show:\t %d: l(%p) b(%p)\n", index, voutput->layer, voutput->bufs[index]);
+}
+
+static void
+_voutput_buff_init(tdm_test_server_voutput *voutput)
+{
+       tdm_output *output = voutput->output;
+       const tdm_output_mode *mode;
+       tdm_error ret = TDM_ERROR_NONE;
+       int i;
+
+       ret = tdm_output_get_mode(output, &mode);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       for (i = 0; i < 3; i++) {
+               tbm_surface_h b = tbm_surface_internal_create_with_flags(mode->hdisplay, mode->vdisplay, DEFAULT_FORMAT, 0);
+               TDM_EXIT_IF_FAIL(b != NULL);
+               tdm_test_buffer_fill(b, i);
+               voutput->bufs[i] = b;
+       }
+}
+
+static void
+_voutput_layer_init(tdm_test_server_voutput *voutput)
+{
+       tdm_output *output = voutput->output;
+       const tdm_output_mode *mode;
+       tdm_info_layer layer_info;
+       tbm_surface_info_s info;
+       tdm_error ret = TDM_ERROR_NONE;
+
+       ret = tdm_output_get_mode(output, &mode);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       TDM_ERR("modeinfo : %dx%d %d", mode->hdisplay, mode->vdisplay, mode->vrefresh);
+
+       voutput->layer = tdm_output_get_layer(output, 0, &ret);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       _voutput_buff_init(voutput);
+
+       tbm_surface_get_info(voutput->bufs[0], &info);
+       memset(&layer_info, 0x0, sizeof(tdm_info_layer));
+       if (IS_RGB(info.format)) {
+               layer_info.src_config.size.h = info.planes[0].stride >> 2;
+               layer_info.src_config.size.v  = info.height;
+       } else {
+               layer_info.src_config.size.h = info.planes[0].stride;
+               layer_info.src_config.size.v  = info.height;
+       }
+       layer_info.src_config.format = info.format;
+
+       layer_info.src_config.size.h = mode->hdisplay;
+       layer_info.src_config.size.v = mode->vdisplay;
+       layer_info.src_config.pos.x = 0;
+       layer_info.src_config.pos.y = 0;
+       layer_info.src_config.pos.w = mode->hdisplay;
+       layer_info.src_config.pos.h = mode->vdisplay;
+       layer_info.dst_pos.x = 0;
+       layer_info.dst_pos.y = 0;
+       layer_info.dst_pos.w = mode->hdisplay;
+       layer_info.dst_pos.h = mode->vdisplay;
+       layer_info.transform = TDM_TRANSFORM_NORMAL;
+
+       ret = tdm_layer_set_info(voutput->layer, &layer_info);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+}
+
+static void
+_voutput_connect(tdm_test_server_voutput *voutput)
+{
+       tdm_output *output;
+       const tdm_output_mode *modes, *found = NULL, *best = NULL, *prefer = NULL;
+       int i, count;
+       tdm_error ret;
+
+       output = voutput->output;
+
+       ret = tdm_output_get_available_modes(output, &modes, &count);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       for (i = 0; i < count; i++) {
+               if (!best)
+                       best = &modes[i];
+               if (modes[i].type & TDM_OUTPUT_MODE_TYPE_PREFERRED)
+                       prefer = &modes[i];
+       }
+       if (prefer) {
+               found = prefer;
+               printf("found prefer mode: %dx%d %d\n", found->hdisplay, found->vdisplay, found->vrefresh);
+       }
+       if (!found && best) {
+               found = best;
+               printf("found best mode: %dx%d %d\n", found->hdisplay, found->vdisplay, found->vrefresh);
+       }
+       if (!found) {
+               printf("couldn't find any mode\n");
+               exit(0);
+       }
+
+       ret = tdm_output_set_mode(output, found);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       printf("output: %s %d\n", found->name, found->vrefresh);
+
+       ret = tdm_output_set_dpms(output, TDM_OUTPUT_DPMS_ON);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+}
+
+static void
+_voutput_disconnect(tdm_test_server_voutput *voutput)
+{
+       tdm_output *output;
+       tdm_error ret;
+
+       output = voutput->output;
+
+       ret = tdm_output_set_dpms(output, TDM_OUTPUT_DPMS_OFF);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+}
+
+static void
+_tdm_test_server_cb_output_change(tdm_output *output, tdm_output_change_type type, tdm_value value, void *user_data)
+{
+       tdm_test_server_voutput *voutput = NULL;
+       tdm_output_conn_status status;
+
+       voutput = (tdm_test_server_voutput *)user_data;
+       TDM_EXIT_IF_FAIL(voutput != NULL);
+
+       switch (type) {
+       case TDM_OUTPUT_CHANGE_CONNECTION:
+               status = (tdm_output_conn_status)value.u32;
+               if (status == TDM_OUTPUT_CONN_STATUS_CONNECTED) {
+                       _voutput_connect(voutput);
+                       _voutput_layer_init(voutput);
+                       _vlayer_show_buffer(voutput);
+               } else if (status == TDM_OUTPUT_CONN_STATUS_DISCONNECTED) {
+                       _voutput_disconnect(voutput);
+               }
+               break;
+       default:
+               break;
+       }
+}
+
+static void
+_tdm_output_cb_destroy_handler(tdm_output *output, void *user_data)
+{
+       tdm_test_server_voutput *voutput = NULL;
+       int i;
+
+       voutput = (tdm_test_server_voutput *)user_data;
+       TDM_EXIT_IF_FAIL(voutput != NULL);
+
+       tdm_output_remove_change_handler(output, _tdm_test_server_cb_output_change, voutput);
+       tdm_output_remove_destroy_handler(output, _tdm_output_cb_destroy_handler, voutput);
+
+       LIST_DEL(&voutput->link);
+
+       for (i = 0; i < 3; i++) {
+               tbm_surface_destroy(voutput->bufs[i]);
+       }
+
+       free(voutput);
+}
+
+static void
+_tdm_output_cb_create_handler(tdm_display *dpy, tdm_output *output, void *user_data)
+{
+       tdm_test_server *data;
+       tdm_test_server_voutput *voutput = NULL;
+       tdm_error ret = TDM_ERROR_NONE;
+
+       printf("voutput create call\n");
+
+       data = (tdm_test_server *)user_data;
+       TDM_EXIT_IF_FAIL(data != NULL);
+
+       voutput = calloc(1, sizeof *voutput);
+       TDM_EXIT_IF_FAIL(voutput != NULL);
+
+       ret = tdm_output_add_change_handler(output, _tdm_test_server_cb_output_change, voutput);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       ret = tdm_output_add_destroy_handler(output, _tdm_output_cb_destroy_handler, voutput);
+       TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+
+       voutput->output = output;
+       voutput->data = data;
+
+       printf("voutput create done\n");
+
+       LIST_ADDTAIL(&voutput->link, &data->voutput_list);
+}
+
+static void
 destroy(tdm_test_server *data)
 {
        tdm_test_server_output *o = NULL, *oo = NULL;
@@ -797,6 +1048,7 @@ destroy(tdm_test_server *data)
        tdm_test_server_pp *p = NULL, *pp = NULL;
        tdm_test_server_capture *c = NULL, *cc = NULL;
        tdm_test_server_prop *w = NULL, *ww = NULL;
+       tdm_test_server_voutput *v = NULL, *vv = NULL;
        tdm_error ret;
        int i;
 
@@ -850,6 +1102,17 @@ destroy(tdm_test_server *data)
                free(o);
        }
 
+       LIST_FOR_EACH_ENTRY_SAFE(v, vv, &data->voutput_list, link) {
+               for (int i = 0; i < 3; i++) {
+                       tbm_surface_destroy(v->bufs[i]);
+               }
+               LIST_DEL(&v->link);
+               free(v);
+       }
+
+       if (data->do_voutput)
+               tdm_display_remove_output_create_handler(data->display, _tdm_output_cb_create_handler, data);
+
        if (data->display)
                tdm_display_deinit(data->display);
 
@@ -888,6 +1151,7 @@ main(int argc, char *argv[])
        LIST_INITHEAD(&data->output_list);
        LIST_INITHEAD(&data->pp_list);
        LIST_INITHEAD(&data->capture_list);
+       LIST_INITHEAD(&data->voutput_list);
 
        /* init value */
        data->bflags = TBM_BO_SCANOUT;
@@ -905,6 +1169,12 @@ main(int argc, char *argv[])
                goto done;
        }
 
+       if (data->do_voutput) {
+               printf("support virtual output - server\n");
+               ret = tdm_display_add_output_create_handler(data->display, _tdm_output_cb_create_handler, data);
+               TDM_EXIT_IF_FAIL(ret == TDM_ERROR_NONE);
+       }
+
        run_test(data);
 
 done: