require explicit check for pixel support

Add `notcurses_check_pixel_support()` and
`ncdirect_check_pixel_support()` per #1367. Removes
NCOPTION_VERIFY_SIXEL, again per #1367. Adds
`free_terminfo_cache()`, and calls it from both
`notcurses_stop_minimal()` and `ncdirect_stop()`.
Update all documentation. Closes #1371 and #1367.
This commit is contained in:
nick black 2021-02-27 14:52:16 -05:00 committed by Nick Black
parent 4533d42fa0
commit 6c7c9be6d2
14 changed files with 115 additions and 39 deletions

View File

@ -3,11 +3,15 @@ rearrangements of Notcurses.
* 2.2.3 (not yet released) * 2.2.3 (not yet released)
* Add `SIGILL` to the set of fatal signals we handle. * Add `SIGILL` to the set of fatal signals we handle.
* Added `NCKEY_SIGNAL`. `NCKEY_RESIZE` is now an alias for `NCKEY_SIGNAL`.
* `SIGCONT` now synthesizes a `NCKEY_SIGNAL`.
* Add the `nctree` widget for line-oriented hierarchical data. See * Add the `nctree` widget for line-oriented hierarchical data. See
the new `notcurses_tree(3)` man page for complete information. the new `notcurses_tree(3)` man page for complete information.
* Implemented `NCBLIT_PIXEL` for terminals reporting Sixel support. * Implemented `NCBLIT_PIXEL` for terminals reporting Sixel support.
Attempt Sixel detection iff `NCOPTION_VERIFY_SIXEL` is provided to Added `notcurses_check_pixel_support()` and its companion
`notcurses_init()`. Sixel detection can delay or even block initialization. `ncdirect_check_pixel_support()`, which must be called (and must return
success) before `NCBLIT_PIXEL` will be available. `NCBLIT_PIXEL` degrades
to `NCBLIT_3x2` until support is verified.
* 2.2.2 (2021-02-18): * 2.2.2 (2021-02-18):
* `notcurses_stats()` no longer qualifies its `notcurses*` argument with * `notcurses_stats()` no longer qualifies its `notcurses*` argument with

View File

@ -89,13 +89,14 @@ typedef enum {
// prior to notcurses_init(), you should not set this bit. Even if you are // prior to notcurses_init(), you should not set this bit. Even if you are
// invoking setlocale(), this behavior shouldn't be an issue unless you're // invoking setlocale(), this behavior shouldn't be an issue unless you're
// doing something weird (setting a locale not based on LANG). // doing something weird (setting a locale not based on LANG).
#define NCOPTION_INHIBIT_SETLOCALE 0x0001 #define NCOPTION_INHIBIT_SETLOCALE 0x0001
// Checking for Sixel support requires writing an escape, and then reading an // Checking for pixel support might require writing a control sequence, and
// inline reply from the terminal. Since this can interact poorly with actual // then reading a reply directly from the terminal. If the terminal doesn't
// user input, it's not done unless Sixel will actually be used. Set this flag // support this, the application will lock up. If you'll be using pixels, set
// to unconditionally test for Sixel support in notcurses_init(). // this flag to perform the check in notcurses_init(). You must otherwise call
#define NCOPTION_VERIFY_SIXEL 0x0002 // notcurses_check_pixel() before NCBLIT_PIXEL will become available.
#define NCOPTION_VERIFY_PIXEL 0x0002ull
// We typically install a signal handler for SIGWINCH that generates a resize // We typically install a signal handler for SIGWINCH that generates a resize
// event in the notcurses_getc() queue. Set to inhibit this handler. // event in the notcurses_getc() queue. Set to inhibit this handler.
@ -306,6 +307,11 @@ bool notcurses_cansextants(const struct notcurses* nc);
// Can we draw Braille? The Linux console cannot. // Can we draw Braille? The Linux console cannot.
bool notcurses_canbraille(const struct notcurses* nc); bool notcurses_canbraille(const struct notcurses* nc);
// If NCOPTION_VERIFY_PIXEL was not supplied to notcurses_init(), this
// function must successfully return before NCBLIT_PIXEL is available. Returns
// -1 on error, 0 if pixel mode is not supported, or 1 if it is supported.
int notcurses_check_pixel(struct notcurses* nc);
``` ```
## Direct mode ## Direct mode

View File

@ -72,6 +72,8 @@ notcurses_direct - minimal notcurses instances for styling text
**bool ncdirect_canutf8(const struct ncdirect* ***n***);** **bool ncdirect_canutf8(const struct ncdirect* ***n***);**
**int ncdirect_check_pixel_support(struct ncdirect* ***n***);**
**int ncdirect_hline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);** **int ncdirect_hline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);**
**int ncdirect_vline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);** **int ncdirect_vline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);**
@ -144,6 +146,10 @@ information, consult **readline(3)**. If you want input echoed to the
terminal while using **ncdirect_readline**, **NCDIRECT_OPTION_INHIBIT_CBREAK** terminal while using **ncdirect_readline**, **NCDIRECT_OPTION_INHIBIT_CBREAK**
must be supplied to **ncdirect_init**. must be supplied to **ncdirect_init**.
**ncdirect_check_pixel_support** must be called (and successfully return)
before **NCBLIT_PIXEL** can be used to render images; see
**notcurses_visual(3)** for more details.
# RETURN VALUES # RETURN VALUES
**ncdirect_init** returns **NULL** on failure. Otherwise, the return value **ncdirect_init** returns **NULL** on failure. Otherwise, the return value
@ -153,6 +159,9 @@ to **ncdirect_stop**.
**ncdirect_putstr** and **ncdirect_printf_aligned** return the number of bytes **ncdirect_putstr** and **ncdirect_printf_aligned** return the number of bytes
written on success. On failure, they return some negative number. written on success. On failure, they return some negative number.
**ncdirect_check_pixel_support** returns -1 on error, 0 if there is no pixel
support, and 1 if pixel support is successfully detected.
All other functions return 0 on success, and non-zero on error. All other functions return 0 on success, and non-zero on error.
# SEE ALSO # SEE ALSO
@ -161,5 +170,6 @@ All other functions return 0 on success, and non-zero on error.
**readline(3)** **readline(3)**
**notcurses(3)**, **notcurses(3)**,
**notcurses_plane(3)**, **notcurses_plane(3)**,
**notcurses_visual(3)**,
**terminfo(5)**, **terminfo(5)**,
**termios(3)** **termios(3)**

View File

@ -12,7 +12,6 @@ notcurses_init - initialize a notcurses instance
```c ```c
#define NCOPTION_INHIBIT_SETLOCALE 0x0001ull #define NCOPTION_INHIBIT_SETLOCALE 0x0001ull
#define NCOPTION_VERIFY_SIXEL 0x0002ull
#define NCOPTION_NO_WINCH_SIGHANDLER 0x0004ull #define NCOPTION_NO_WINCH_SIGHANDLER 0x0004ull
#define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008ull #define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008ull
#define NCOPTION_SUPPRESS_BANNERS 0x0020ull #define NCOPTION_SUPPRESS_BANNERS 0x0020ull
@ -111,12 +110,6 @@ zero. The following flags are defined:
the **LANG** environment variable. Your program should call **setlocale(3)** the **LANG** environment variable. Your program should call **setlocale(3)**
itself, usually as one of the first lines. itself, usually as one of the first lines.
* **NCOPTION_VERIFY_SIXEL**: Checking for Sixel support requires writing an
escape, and then reading an inline reply from the terminal. Since this can
interact poorly with actual user input, it's not done unless Sixel will
actually be used. Set this flag to unconditionally test for Sixel support
in **notcurses_init**.
* **NCOPTION_NO_WINCH_SIGHANDLER**: A signal handler will usually be installed * **NCOPTION_NO_WINCH_SIGHANDLER**: A signal handler will usually be installed
for **SIGWINCH**, resulting in **NCKEY_RESIZE** events being generated on for **SIGWINCH**, resulting in **NCKEY_RESIZE** events being generated on
input. With this flag, the handler will not be installed. input. With this flag, the handler will not be installed.

View File

@ -92,6 +92,8 @@ typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*);
**int ncplane_qrcode(struct ncplane* ***n***, int* ***ymax***, int* ***xmax***, const void* ***data***, size_t ***len***)** **int ncplane_qrcode(struct ncplane* ***n***, int* ***ymax***, int* ***xmax***, const void* ***data***, size_t ***len***)**
**int notcurses_check_pixel_support(struct notcurses* ***nc***);**
# DESCRIPTION # DESCRIPTION
An **ncvisual** is a virtual pixel framebuffer. They can be created from An **ncvisual** is a virtual pixel framebuffer. They can be created from
@ -203,6 +205,16 @@ Finally, rendering operates slightly differently when two planes have both been
blitted, and one lies atop the other. See **notcurses_render(3)** for more blitted, and one lies atop the other. See **notcurses_render(3)** for more
information. information.
# PIXEL BLITTING
Some terminals support pixel-based output, either via Sixel or some bespoke
mechanism. Checking for Sixel support requires interrogating the terminal and
reading a response. This takes time, and will never complete if the terminal
doesn't respond. Before **NCBLIT_PIXEL** can be used, it is thus necessary to
check for support with **notcurses_check_pixel_support**. If this function has
not successfully returned, attempts to use **NCBLIT_PIXEL** will fall back to
**NCBLIT_3x2** (or fail, if **NCVISUAL_OPTION_NODEGRADE** is used).
# RETURN VALUES # RETURN VALUES
**ncvisual_from_file** returns an **ncvisual** object on success, or **NULL** **ncvisual_from_file** returns an **ncvisual** object on success, or **NULL**

View File

@ -217,6 +217,11 @@ namespace ncpp
return ncdirect_canutf8 (direct); return ncdirect_canutf8 (direct);
} }
int check_pixel_support() noexcept
{
return ncdirect_check_pixel_support (direct);
}
private: private:
ncdirect *direct; ncdirect *direct;
}; };

View File

@ -216,6 +216,11 @@ API bool ncdirect_canopen_images(const struct ncdirect* n);
// Is our encoding UTF-8? Requires LANG being set to a UTF8 locale. // Is our encoding UTF-8? Requires LANG being set to a UTF8 locale.
API bool ncdirect_canutf8(const struct ncdirect* n); API bool ncdirect_canutf8(const struct ncdirect* n);
// This function must successfully return before NCBLIT_PIXEL is available.
// Returns -1 on error, 0 for no support, or 1 if pixel output is supported.
// Must not be called concurrently with either input or rasterization.
API int ncdirect_check_pixel_support(struct ncdirect* n);
// Draw horizontal/vertical lines using the specified channels, interpolating // Draw horizontal/vertical lines using the specified channels, interpolating
// between them as we go. The EGC may not use more than one column. For a // between them as we go. The EGC may not use more than one column. For a
// horizontal line, |len| cannot exceed the screen width minus the cursor's // horizontal line, |len| cannot exceed the screen width minus the cursor's

View File

@ -9,7 +9,7 @@ extern "C" {
// Special composed key definitions. These values are added to 0x100000. // Special composed key definitions. These values are added to 0x100000.
#define NCKEY_INVALID suppuabize(0) #define NCKEY_INVALID suppuabize(0)
#define NCKEY_SIGNAL suppuabize(1) // generated internally in response to SIGWINCH / SIGCONT #define NCKEY_SIGNAL suppuabize(1) // we received either SIGWINCH or SIGCONT
#define NCKEY_UP suppuabize(2) #define NCKEY_UP suppuabize(2)
#define NCKEY_RIGHT suppuabize(3) #define NCKEY_RIGHT suppuabize(3)
#define NCKEY_DOWN suppuabize(4) #define NCKEY_DOWN suppuabize(4)

View File

@ -826,11 +826,7 @@ typedef enum {
// doing something weird (setting a locale not based on LANG). // doing something weird (setting a locale not based on LANG).
#define NCOPTION_INHIBIT_SETLOCALE 0x0001ull #define NCOPTION_INHIBIT_SETLOCALE 0x0001ull
// Checking for Sixel support requires writing an escape, and then reading an // NCOPTION_VERIFY_PIXEL was removed in 2.2.3. It ought be repurposed. FIXME.
// inline reply from the terminal. Since this can interact poorly with actual
// user input, it's not done unless Sixel will actually be used. Set this flag
// to unconditionally test for Sixel support in notcurses_init().
#define NCOPTION_VERIFY_SIXEL 0x0002ull
// We typically install a signal handler for SIGWINCH that generates a resize // We typically install a signal handler for SIGWINCH that generates a resize
// event in the notcurses_getc() queue. Set to inhibit this handler. // event in the notcurses_getc() queue. Set to inhibit this handler.
@ -1237,6 +1233,11 @@ API bool notcurses_canbraille(const struct notcurses* nc);
// Can we blit to pixel graphics? // Can we blit to pixel graphics?
API bool notcurses_canpixel(const struct notcurses* nc); API bool notcurses_canpixel(const struct notcurses* nc);
// This function must successfully return before NCBLIT_PIXEL is available.
// Returns -1 on error, 0 for no support, or 1 if pixel output is supported.
// Must not be called concurrently with either input or rasterization.
API int notcurses_check_pixel_support(struct notcurses* nc);
typedef struct ncstats { typedef struct ncstats {
// purely increasing stats // purely increasing stats
uint64_t renders; // successful ncpile_render() runs uint64_t renders; // successful ncpile_render() runs

View File

@ -637,6 +637,7 @@ ncdirect_stop_minimal(void* vnc){
ret |= close(nc->ctermfd); ret |= close(nc->ctermfd);
} }
ret |= ncdirect_flush(nc); ret |= ncdirect_flush(nc);
free_terminfo_cache(&nc->tcache);
return ret; return ret;
} }
@ -1102,3 +1103,13 @@ int ncdirect_flush(const ncdirect* nc){
} }
return 0; return 0;
} }
int ncdirect_check_pixel_support(ncdirect* n){
if(query_term(&n->tcache, n->ctermfd)){
return -1;
}
if(n->tcache.pixelon){
return 1;
}
return 0;
}

View File

@ -286,11 +286,12 @@ typedef struct tinfo {
// background_opaque is in use. detect this, and avoid the default if so. // background_opaque is in use. detect this, and avoid the default if so.
// bg_collides_default is either 0x0000000 or 0x1RRGGBB. // bg_collides_default is either 0x0000000 or 0x1RRGGBB.
uint32_t bg_collides_default; uint32_t bg_collides_default;
bool sextants; // do we have (good, vetted) Unicode 13 sextant support? pthread_mutex_t pixel_query; // only query for pixel support once
bool braille; // do we have Braille support? (linux console does not)
char* pixelon; // enter pixel graphics mode char* pixelon; // enter pixel graphics mode
char* pixeloff; // leave pixel graphics mode char* pixeloff; // leave pixel graphics mode
bool sixel; // do we have Sixel support? bool pixel_query_done; // have we yet performed pixel query?
bool sextants; // do we have (good, vetted) Unicode 13 sextant support?
bool braille; // do we have Braille support? (linux console does not)
} tinfo; } tinfo;
typedef struct ncinputlayer { typedef struct ncinputlayer {
@ -386,6 +387,8 @@ int terminfostr(char** gseq, const char* name);
// initialized. set |utf8| if we've verified UTF8 output encoding. // initialized. set |utf8| if we've verified UTF8 output encoding.
int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8); int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8);
void free_terminfo_cache(tinfo* ti);
// perform queries that require writing to the terminal, and reading a // perform queries that require writing to the terminal, and reading a
// response, rather than simply reading the terminfo database. can result // response, rather than simply reading the terminfo database. can result
// in a lengthy delay or even block if the terminal doesn't respond. // in a lengthy delay or even block if the terminal doesn't respond.

View File

@ -777,14 +777,13 @@ init_banner(const notcurses* nc){
term_fg_palindex(nc, stdout, nc->tcache.colors <= 256 ? 50 % nc->tcache.colors : 0x20e080); term_fg_palindex(nc, stdout, nc->tcache.colors <= 256 ? 50 % nc->tcache.colors : 0x20e080);
printf("\n notcurses %s by nick black et al", notcurses_version()); printf("\n notcurses %s by nick black et al", notcurses_version());
term_fg_palindex(nc, stdout, nc->tcache.colors <= 256 ? 12 % nc->tcache.colors : 0x2080e0); term_fg_palindex(nc, stdout, nc->tcache.colors <= 256 ? 12 % nc->tcache.colors : 0x2080e0);
printf("\n %d rows %d cols (%sB) %zuB cells %d colors%s%s\n" printf("\n %d rows %d cols (%sB) %zuB cells %d colors%s\n"
" compiled with gcc-%s, %s-endian\n" " compiled with gcc-%s, %s-endian\n"
" terminfo from %s\n", " terminfo from %s\n",
nc->stdplane->leny, nc->stdplane->lenx, nc->stdplane->leny, nc->stdplane->lenx,
bprefix(nc->stats.fbbytes, 1, prefixbuf, 0), sizeof(nccell), bprefix(nc->stats.fbbytes, 1, prefixbuf, 0), sizeof(nccell),
nc->tcache.colors, nc->tcache.colors,
nc->tcache.RGBflag ? "+RGB" : "", nc->tcache.RGBflag ? "+RGB" : "",
nc->tcache.sixel ? "+Sixel" : "",
__VERSION__, __VERSION__,
#ifdef __BYTE_ORDER__ #ifdef __BYTE_ORDER__
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@ -908,6 +907,16 @@ recursive_lock_init(pthread_mutex_t *lock){
#endif #endif
} }
int notcurses_check_pixel_support(notcurses* nc){
if(query_term(&nc->tcache, nc->ttyfd)){
return -1;
}
if(nc->tcache.pixelon){
return 1;
}
return 0;
}
// FIXME cut this up into a few distinct pieces, yearrrgh // FIXME cut this up into a few distinct pieces, yearrrgh
notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
notcurses_options defaultopts; notcurses_options defaultopts;
@ -1033,12 +1042,6 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
goto err; goto err;
} }
if(ret->ttyfd >= 0){ if(ret->ttyfd >= 0){
if(opts->flags & NCOPTION_VERIFY_SIXEL){
if(query_term(&ret->tcache, ret->ttyfd)){
free_plane(ret->stdplane);
goto err;
}
}
if(ret->tcache.smkx && tty_emit(ret->tcache.smkx, ret->ttyfd)){ if(ret->tcache.smkx && tty_emit(ret->tcache.smkx, ret->ttyfd)){
free_plane(ret->stdplane); free_plane(ret->stdplane);
goto err; goto err;
@ -1215,6 +1218,7 @@ int notcurses_stop(notcurses* nc){
del_curterm(cur_term); del_curterm(cur_term);
ret |= pthread_mutex_destroy(&nc->statlock); ret |= pthread_mutex_destroy(&nc->statlock);
ret |= pthread_mutex_destroy(&nc->pilelock); ret |= pthread_mutex_destroy(&nc->pilelock);
free_terminfo_cache(&nc->tcache);
free(nc); free(nc);
} }
return ret; return ret;
@ -2073,7 +2077,7 @@ bool notcurses_canchangecolor(const notcurses* nc){
} }
bool notcurses_canpixel(const notcurses* nc){ bool notcurses_canpixel(const notcurses* nc){
return nc->tcache.sixel; return nc->tcache.pixelon;
} }
palette256* palette256_new(notcurses* nc){ palette256* palette256_new(notcurses* nc){

View File

@ -71,6 +71,10 @@ apply_term_heuristics(tinfo* ti, const char* termname){
return 0; return 0;
} }
void free_terminfo_cache(tinfo* ti){
pthread_mutex_destroy(&ti->pixel_query);
}
// termname is just the TERM environment variable. some details are not // termname is just the TERM environment variable. some details are not
// exposed via terminfo, and we must make heuristic decisions based on // exposed via terminfo, and we must make heuristic decisions based on
// the detected terminal type, yuck :/. // the detected terminal type, yuck :/.
@ -184,6 +188,8 @@ int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8){
ti->fgop = "\x1b[39m"; ti->fgop = "\x1b[39m";
ti->bgop = "\x1b[49m"; ti->bgop = "\x1b[49m";
} }
pthread_mutex_init(&ti->pixel_query, NULL);
ti->pixel_query_done = false;
if(apply_term_heuristics(ti, termname)){ if(apply_term_heuristics(ti, termname)){
return -1; return -1;
} }
@ -229,8 +235,8 @@ query_sixel(tinfo* ti, int fd){
state = DONE; state = DONE;
}else if(in == '4'){ }else if(in == '4'){
if(!ti->pixelon){ if(!ti->pixelon){
ti->pixelon = strdup("\ePq"); ti->pixelon = "\ePq";
ti->pixeloff = strdup("\e\\"); ti->pixeloff = "\e\\";
} // FIXME else warning? } // FIXME else warning?
} }
break; break;
@ -245,10 +251,18 @@ query_sixel(tinfo* ti, int fd){
return 0; return 0;
} }
// fd must be a real terminal, and must not be in nonblocking mode // fd must be a real terminal, and must not be in nonblocking mode. uses the
// pthread_mutex_t of |ti| to only act once.
int query_term(tinfo* ti, int fd){ int query_term(tinfo* ti, int fd){
if(query_sixel(ti, fd)){ if(fd < 0){
return -1; return -1;
} }
return 0; int ret = 0;
pthread_mutex_lock(&ti->pixel_query);
if(!ti->pixel_query_done){
ret = query_sixel(ti, fd);
ti->pixel_query_done = true;
}
pthread_mutex_unlock(&ti->pixel_query);
return ret;
} }

View File

@ -143,6 +143,9 @@ auto perframe(struct ncvisual* ncv, struct ncvisual_options* vopts,
}else if(keyp >= '0' && keyp <= '8'){ // FIXME eliminate ctrl/alt }else if(keyp >= '0' && keyp <= '8'){ // FIXME eliminate ctrl/alt
marsh->blitter = static_cast<ncblitter_e>(keyp - '0'); marsh->blitter = static_cast<ncblitter_e>(keyp - '0');
vopts->blitter = marsh->blitter; vopts->blitter = marsh->blitter;
if(vopts->blitter == NCBLIT_PIXEL){
notcurses_check_pixel_support(nc);
}
continue; continue;
}else if(keyp == NCKey::Up){ }else if(keyp == NCKey::Up){
// FIXME // FIXME
@ -280,6 +283,9 @@ int direct_mode_player(int argc, char** argv, ncscale_e scalemode, ncblitter_e b
return -1; return -1;
} }
bool failed = false; bool failed = false;
if(blitter == NCBLIT_PIXEL){
dm.check_pixel_support();
}
{ {
for(auto i = 0 ; i < argc ; ++i){ for(auto i = 0 ; i < argc ; ++i){
try{ try{
@ -303,7 +309,6 @@ auto main(int argc, char** argv) -> int {
float timescale, displaytime; float timescale, displaytime;
ncscale_e scalemode; ncscale_e scalemode;
notcurses_options ncopts{}; notcurses_options ncopts{};
ncopts.flags = NCOPTION_VERIFY_SIXEL;
ncblitter_e blitter = NCBLIT_PIXEL; ncblitter_e blitter = NCBLIT_PIXEL;
bool quiet = false; bool quiet = false;
bool loop = false; bool loop = false;
@ -348,6 +353,9 @@ auto main(int argc, char** argv) -> int {
vopts.n = *stdn; vopts.n = *stdn;
vopts.scaling = scalemode; vopts.scaling = scalemode;
vopts.blitter = blitter; vopts.blitter = blitter;
if(vopts.blitter == NCBLIT_PIXEL){
notcurses_check_pixel_support(nc);
}
do{ do{
struct marshal marsh = { struct marshal marsh = {
.subtitle_plane = nullptr, .subtitle_plane = nullptr,