History

lavfi: add a libplacebo filter

This filter conceptually maps the libplacebo `pl_renderer` API into libavfilter, which is a high-level image rendering API designed to work with an RGB pipeline internally. As such, there's no way to avoid e.g. chroma interpolation with this filter, although new versions of libplacebo support outputting back to subsampled YCbCr after processing is done. That being said, `pl_renderer` supports automatic integration of the majority of libplacebo's shaders, ranging from debanding to tone mapping, and also supports loading custom mpv-style user shaders, making this API a natural candidate for getting a lot of functionality out of relatively little code. In the future, I may approach this problem either by rewriting this filter to also support a non-renderer codepath, or by upgrading libplacebo's renderer to support a full YCbCr pipeline. This unfortunately requires a very new version of libplacebo (unreleased at time of writing) for timeline semaphore support. But the amount of boilerplate needed to hack in backwards compatibility would have been very unreasonable.

Niklas Haas committed 5 years ago

Commit 51e03401 parent 296cb994 files · +722 −0Browse files

Showing the changes to libavfilter (3 of 4 files). Show all

Unified · Split

3 files
  1. libavfilter/Makefile +1 −0
  2. libavfilter/allfilters.c +1 −0
  3. libavfilter/vf_libplacebo.c +717 −0
libavfilter/Makefile+1−0
@@ -323,6 +323,7 @@ OBJS-$(CONFIG_LAGFUN_FILTER) += vf_lagfun.o · 322 unmodified lines
323OBJS-$(CONFIG_LATENCY_FILTER) += f_latency.o323OBJS-$(CONFIG_LATENCY_FILTER) += f_latency.o
324OBJS-$(CONFIG_LENSCORRECTION_FILTER) += vf_lenscorrection.o324OBJS-$(CONFIG_LENSCORRECTION_FILTER) += vf_lenscorrection.o
325OBJS-$(CONFIG_LENSFUN_FILTER) += vf_lensfun.o325OBJS-$(CONFIG_LENSFUN_FILTER) += vf_lensfun.o
326OBJS-$(CONFIG_LIBPLACEBO_FILTER) += vf_libplacebo.o vulkan.o
326OBJS-$(CONFIG_LIBVMAF_FILTER) += vf_libvmaf.o framesync.o327OBJS-$(CONFIG_LIBVMAF_FILTER) += vf_libvmaf.o framesync.o
327OBJS-$(CONFIG_LIMITDIFF_FILTER) += vf_limitdiff.o framesync.o328OBJS-$(CONFIG_LIMITDIFF_FILTER) += vf_limitdiff.o framesync.o
328OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o329OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o
libavfilter/allfilters.c+1−0
@@ -308,6 +308,7 @@ extern const AVFilter ff_vf_lagfun; · 307 unmodified lines
308extern const AVFilter ff_vf_latency;308extern const AVFilter ff_vf_latency;
309extern const AVFilter ff_vf_lenscorrection;309extern const AVFilter ff_vf_lenscorrection;
310extern const AVFilter ff_vf_lensfun;310extern const AVFilter ff_vf_lensfun;
311extern const AVFilter ff_vf_libplacebo;
311extern const AVFilter ff_vf_libvmaf;312extern const AVFilter ff_vf_libvmaf;
312extern const AVFilter ff_vf_limitdiff;313extern const AVFilter ff_vf_limitdiff;
313extern const AVFilter ff_vf_limiter;314extern const AVFilter ff_vf_limiter;
libavfilter/vf_libplacebo.c+717−0
@@ -0,0 +1,717 @@
1/*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19#include "libavutil/file.h"
20#include "libavutil/opt.h"
21#include "internal.h"
22#include "vulkan.h"
23#include "scale_eval.h"
24
25#include <libplacebo/renderer.h>
26#include <libplacebo/utils/libav.h>
27#include <libplacebo/vulkan.h>
28
29typedef struct LibplaceboContext {
30 /* lavfi vulkan*/
31 FFVulkanContext vkctx;
32 int initialized;
33
34 /* libplacebo */
35 pl_log log;
36 pl_vulkan vulkan;
37 pl_gpu gpu;
38 pl_renderer renderer;
39
40 /* settings */
41 char *out_format_string;
42 char *w_expr;
43 char *h_expr;
44 AVRational target_sar;
45 float pad_crop_ratio;
46 int force_original_aspect_ratio;
47 int force_divisible_by;
48 int normalize_sar;
49 int apply_filmgrain;
50 int colorspace;
51 int color_range;
52 int color_primaries;
53 int color_trc;
54
55 /* pl_render_params */
56 char *upscaler;
57 char *downscaler;
58 int lut_entries;
59 float antiringing;
60 int sigmoid;
61 int skip_aa;
62 float polar_cutoff;
63 int disable_linear;
64 int disable_builtin;
65 int force_3dlut;
66 int force_dither;
67 int disable_fbos;
68
69 /* pl_deband_params */
70 int deband;
71 int deband_iterations;
72 float deband_threshold;
73 float deband_radius;
74 float deband_grain;
75
76 /* pl_color_adjustment */
77 float brightness;
78 float contrast;
79 float saturation;
80 float hue;
81 float gamma;
82
83 /* pl_peak_detect_params */
84 int peakdetect;
85 float smoothing;
86 float min_peak;
87 float scene_low;
88 float scene_high;
89 float overshoot;
90
91 /* pl_color_map_params */
92 int intent;
93 int tonemapping;
94 float tonemapping_param;
95 float desat_str;
96 float desat_exp;
97 float desat_base;
98 float max_boost;
99 int gamut_warning;
100 int gamut_clipping;
101
102 /* pl_dither_params */
103 int dithering;
104 int dither_lut_size;
105 int dither_temporal;
106
107 /* pl_cone_params */
108 int cones;
109 float cone_str;
110
111 /* custom shaders */
112 char *shader_path;
113 void *shader_bin;
114 int shader_bin_len;
115 const struct pl_hook *hooks[2];
116 int num_hooks;
117} LibplaceboContext;
118
119static void pl_av_log(void *log_ctx, enum pl_log_level level, const char *msg)
120{
121 int av_lev;
122
123 switch (level) {
124 case PL_LOG_FATAL: av_lev = AV_LOG_FATAL; break;
125 case PL_LOG_ERR: av_lev = AV_LOG_ERROR; break;
126 case PL_LOG_WARN: av_lev = AV_LOG_WARNING; break;
127 case PL_LOG_INFO: av_lev = AV_LOG_VERBOSE; break;
128 case PL_LOG_DEBUG: av_lev = AV_LOG_DEBUG; break;
129 case PL_LOG_TRACE: av_lev = AV_LOG_TRACE; break;
130 default: return;
131 }
132
133 av_log(log_ctx, av_lev, "%s\n", msg);
134}
135
136static int parse_shader(AVFilterContext *avctx, const void *shader, size_t len)
137{
138 LibplaceboContext *s = avctx->priv;
139 const struct pl_hook *hook;
140
141 hook = pl_mpv_user_shader_parse(s->gpu, shader, len);
142 if (!hook) {
143 av_log(s, AV_LOG_ERROR, "Failed parsing custom shader!\n");
144 return AVERROR(EINVAL);
145 }
146
147 s->hooks[s->num_hooks++] = hook;
148 return 0;
149}
150
151static int find_scaler(AVFilterContext *avctx,
152 const struct pl_filter_config **opt,
153 const char *name)
154{
155 const struct pl_filter_preset *preset;
156 if (!strcmp(name, "help")) {
157 av_log(avctx, AV_LOG_INFO, "Available scaler presets:\n");
158 for (preset = pl_scale_filters; preset->name; preset++)
159 av_log(avctx, AV_LOG_INFO, " %s\n", preset->name);
160 return AVERROR_EXIT;
161 }
162
163 for (preset = pl_scale_filters; preset->name; preset++) {
164 if (!strcmp(name, preset->name)) {
165 *opt = preset->filter;
166 return 0;
167 }
168 }
169
170 av_log(avctx, AV_LOG_ERROR, "No such scaler preset '%s'.\n", name);
171 return AVERROR(EINVAL);
172}
173
174static int libplacebo_init(AVFilterContext *avctx)
175{
176 LibplaceboContext *s = avctx->priv;
177
178 /* Create libplacebo log context */
179 s->log = pl_log_create(PL_API_VER, pl_log_params(
180 .log_level = PL_LOG_DEBUG,
181 .log_cb = pl_av_log,
182 .log_priv = s,
183 ));
184
185 if (!s->log)
186 return AVERROR(ENOMEM);
187
188 /* Note: s->vulkan etc. are initialized later, when hwctx is available */
189 return 0;
190}
191
192static int init_vulkan(AVFilterContext *avctx)
193{
194 int err = 0;
195 LibplaceboContext *s = avctx->priv;
196 const AVVulkanDeviceContext *hwctx = s->vkctx.hwctx;
197 uint8_t *buf = NULL;
198 size_t buf_len;
199
200 /* Import libavfilter vulkan context into libplacebo */
201 s->vulkan = pl_vulkan_import(s->log, pl_vulkan_import_params(
202 .instance = hwctx->inst,
203 .get_proc_addr = hwctx->get_proc_addr,
204 .phys_device = hwctx->phys_dev,
205 .device = hwctx->act_dev,
206 .extensions = hwctx->enabled_dev_extensions,
207 .num_extensions = hwctx->nb_enabled_dev_extensions,
208 .features = &hwctx->device_features,
209 .queue_graphics = {
210 .index = hwctx->queue_family_index,
211 .count = hwctx->nb_graphics_queues,
212 },
213 .queue_compute = {
214 .index = hwctx->queue_family_comp_index,
215 .count = hwctx->nb_comp_queues,
216 },
217 .queue_transfer = {
218 .index = hwctx->queue_family_tx_index,
219 .count = hwctx->nb_tx_queues,
220 },
221 /* This is the highest version created by hwcontext_vulkan.c */
222 .max_api_version = VK_API_VERSION_1_2,
223 ));
224
225 if (!s->vulkan) {
226 av_log(s, AV_LOG_ERROR, "Failed importing vulkan device to libplacebo!\n");
227 err = AVERROR_EXTERNAL;
228 goto fail;
229 }
230
231 /* Create the renderer */
232 s->gpu = s->vulkan->gpu;
233 s->renderer = pl_renderer_create(s->log, s->gpu);
234
235 /* Parse the user shaders, if requested */
236 if (s->shader_bin_len)
237 RET(parse_shader(avctx, s->shader_bin, s->shader_bin_len));
238
239 if (s->shader_path && s->shader_path[0]) {
240 RET(av_file_map(s->shader_path, &buf, &buf_len, 0, s));
241 RET(parse_shader(avctx, buf, buf_len));
242 }
243
244 /* fall through */
245fail:
246 if (buf)
247 av_file_unmap(buf, buf_len);
248 s->initialized = 1;
249 return err;
250}
251
252static void libplacebo_uninit(AVFilterContext *avctx)
253{
254 LibplaceboContext *s = avctx->priv;
255
256 for (int i = 0; i < s->num_hooks; i++)
257 pl_mpv_user_shader_destroy(&s->hooks[i]);
258 pl_renderer_destroy(&s->renderer);
259 pl_vulkan_destroy(&s->vulkan);
260 pl_log_destroy(&s->log);
261 ff_vk_filter_uninit(avctx);
262 s->initialized = 0;
263 s->gpu = NULL;
264}
265
266static int wrap_vkframe(pl_gpu gpu, const AVFrame *frame, int plane, pl_tex *tex)
267{
268 AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
269 const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
270 const AVVulkanFramesContext *vkfc = hwfc->hwctx;
271 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
272 const VkFormat *vk_fmt = av_vkfmt_from_pixfmt(hwfc->sw_format);
273 const int chroma = plane == 1 || plane == 2;
274
275 *tex = pl_vulkan_wrap(gpu, pl_vulkan_wrap_params(
276 .image = vkf->img[plane],
277 .format = vk_fmt[plane],
278 .width = AV_CEIL_RSHIFT(frame->width, chroma ? desc->log2_chroma_w : 0),
279 .height = AV_CEIL_RSHIFT(frame->height, chroma ? desc->log2_chroma_h : 0),
280 .usage = vkfc->usage,
281 ));
282
283 if (!*tex)
284 return AVERROR(ENOMEM);
285
286 pl_vulkan_release(gpu, *tex, vkf->layout[plane], (pl_vulkan_sem) {
287 .sem = vkf->sem[plane],
288 .value = vkf->sem_value[plane]
289 });
290 return 0;
291}
292
293static int unwrap_vkframe(pl_gpu gpu, AVFrame *frame, int plane, pl_tex *tex)
294{
295 AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
296 int ok = pl_vulkan_hold_raw(gpu, *tex, &vkf->layout[plane],
297 (pl_vulkan_sem) { vkf->sem[plane], vkf->sem_value[plane] + 1 });
298 vkf->access[plane] = 0;
299 vkf->sem_value[plane] += !!ok;
300 return ok ? 0 : AVERROR_EXTERNAL;
301}
302
303static void set_sample_depth(struct pl_frame *out_frame, const AVFrame *frame)
304{
305 const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data;
306 pl_fmt fmt = out_frame->planes[0].texture->params.format;
307 struct pl_bit_encoding *bits = &out_frame->repr.bits;
308 bits->sample_depth = fmt->component_depth[0];
309
310 switch (hwfc->sw_format) {
311 case AV_PIX_FMT_P010: bits->bit_shift = 6; break;
312 default: break;
313 }
314}
315
316static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in)
317{
318 int err = 0;
319 LibplaceboContext *s = avctx->priv;
320 struct pl_render_params params;
321 struct pl_frame image, target;
322 pl_frame_from_avframe(&image, in);
323 pl_frame_from_avframe(&target, out);
324
325 if (!s->apply_filmgrain)
326 image.film_grain.type = PL_FILM_GRAIN_NONE;
327
328 if (s->target_sar.num) {
329 float aspect = pl_rect2df_aspect(&target.crop) * av_q2d(s->target_sar);
330 pl_rect2df_aspect_set(&target.crop, aspect, s->pad_crop_ratio);
331 }
332
333 /* Update render params */
334 params = (struct pl_render_params) {
335 PL_RENDER_DEFAULTS
336 .lut_entries = s->lut_entries,
337 .antiringing_strength = s->antiringing,
338
339 .deband_params = !s->deband ? NULL : pl_deband_params(
340 .iterations = s->deband_iterations,
341 .threshold = s->deband_threshold,
342 .radius = s->deband_radius,
343 .grain = s->deband_grain,
344 ),
345
346 .sigmoid_params = s->sigmoid ? &pl_sigmoid_default_params : NULL,
347
348 .color_adjustment = &(struct pl_color_adjustment) {
349 .brightness = s->brightness,
350 .contrast = s->contrast,
351 .saturation = s->saturation,
352 .hue = s->hue,
353 .gamma = s->gamma,
354 },
355
356 .peak_detect_params = !s->peakdetect ? NULL : pl_peak_detect_params(
357 .smoothing_period = s->smoothing,
358 .minimum_peak = s->min_peak,
359 .scene_threshold_low = s->scene_low,
360 .scene_threshold_high = s->scene_high,
361 .overshoot_margin = s->overshoot,
362 ),
363
364 .color_map_params = pl_color_map_params(
365 .intent = s->intent,
366 .tone_mapping_algo = s->tonemapping,
367 .tone_mapping_param = s->tonemapping_param,
368 .desaturation_strength = s->desat_str,
369 .desaturation_exponent = s->desat_exp,
370 .desaturation_base = s->desat_base,
371 .max_boost = s->max_boost,
372 .gamut_warning = s->gamut_warning,
373 .gamut_clipping = s->gamut_clipping,
374 ),
375
376 .dither_params = s->dithering < 0 ? NULL : pl_dither_params(
377 .method = s->dithering,
378 .lut_size = s->dither_lut_size,
379 .temporal = s->dither_temporal,
380 ),
381
382 .cone_params = !s->cones ? NULL : pl_cone_params(
383 .cones = s->cones,
384 .strength = s->cone_str,
385 ),
386
387 .hooks = s->hooks,
388 .num_hooks = s->num_hooks,
389
390 .skip_anti_aliasing = s->skip_aa,
391 .polar_cutoff = s->polar_cutoff,
392 .disable_linear_scaling = s->disable_linear,
393 .disable_builtin_scalers = s->disable_builtin,
394 .force_3dlut = s->force_3dlut,
395 .force_dither = s->force_dither,
396 .disable_fbos = s->disable_fbos,
397 };
398
399 RET(find_scaler(avctx, &params.upscaler, s->upscaler));
400 RET(find_scaler(avctx, &params.downscaler, s->downscaler));
401
402 /* Ideally, we would persistently wrap all of these AVVkFrames into pl_tex
403 * objects, but for now we'll just create and destroy a wrapper per frame.
404 * Note that doing it this way is suboptimal, since it results in the
405 * creation and destruction of a VkSampler and VkFramebuffer per frame.
406 *
407 * FIXME: Can we do better? */
408 for (int i = 0; i < image.num_planes; i++)
409 RET(wrap_vkframe(s->gpu, in, i, &image.planes[i].texture));
410 for (int i = 0; i < target.num_planes; i++)
411 RET(wrap_vkframe(s->gpu, out, i, &target.planes[i].texture));
412
413 /* Since we-re mapping vkframes manually, the pl_frame helpers don't know
414 * about the mismatch between the sample format and the color depth. */
415 set_sample_depth(&image, in);
416 set_sample_depth(&target, out);
417
418 pl_render_image(s->renderer, &image, &target, &params);
419
420 for (int i = 0; i < image.num_planes; i++)
421 RET(unwrap_vkframe(s->gpu, in, i, &image.planes[i].texture));
422 for (int i = 0; i < target.num_planes; i++)
423 RET(unwrap_vkframe(s->gpu, out, i, &target.planes[i].texture));
424
425 /* Flush the command queues for performance */
426 pl_gpu_flush(s->gpu);
427
428 /* fall through */
429fail:
430 for (int i = 0; i < image.num_planes; i++)
431 pl_tex_destroy(s->gpu, &image.planes[i].texture);
432 for (int i = 0; i < target.num_planes; i++)
433 pl_tex_destroy(s->gpu, &target.planes[i].texture);
434 return err;
435}
436
437static int filter_frame(AVFilterLink *link, AVFrame *in)
438{
439 int err;
440 AVFilterContext *ctx = link->dst;
441 LibplaceboContext *s = ctx->priv;
442 AVFilterLink *outlink = ctx->outputs[0];
443
444 AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
445 if (!out) {
446 err = AVERROR(ENOMEM);
447 goto fail;
448 }
449
450 if (!s->initialized)
451 RET(init_vulkan(ctx));
452
453 RET(av_frame_copy_props(out, in));
454 out->width = outlink->w;
455 out->height = outlink->h;
456
457 if (s->colorspace >= 0)
458 out->colorspace = s->colorspace;
459 if (s->color_range >= 0)
460 out->color_range = s->color_range;
461 if (s->color_trc >= 0)
462 out->color_trc = s->color_trc;
463 if (s->color_primaries >= 0)
464 out->color_primaries = s->color_primaries;
465
466 RET(process_frames(ctx, out, in));
467
468 if (s->apply_filmgrain)
469 av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
470
471 av_frame_free(&in);
472
473 return ff_filter_frame(outlink, out);
474
475fail:
476 av_frame_free(&in);
477 av_frame_free(&out);
478 return err;
479}
480
481static int libplacebo_config_output(AVFilterLink *outlink)
482{
483 int err;
484 AVFilterContext *avctx = outlink->src;
485 LibplaceboContext *s = avctx->priv;
486 AVFilterLink *inlink = outlink->src->inputs[0];
487 AVHWFramesContext *hwfc;
488 AVVulkanFramesContext *vkfc;
489 AVRational scale_sar;
490 int *out_w = &s->vkctx.output_width;
491 int *out_h = &s->vkctx.output_height;
492
493 RET(ff_scale_eval_dimensions(s, s->w_expr, s->h_expr, inlink, outlink,
494 out_w, out_h));
495
496 ff_scale_adjust_dimensions(inlink, out_w, out_h,
497 s->force_original_aspect_ratio,
498 s->force_divisible_by);
499
500 scale_sar = (AVRational){outlink->h * inlink->w, *out_w * *out_h};
501 if (inlink->sample_aspect_ratio.num)
502 scale_sar = av_mul_q(scale_sar, inlink->sample_aspect_ratio);
503
504 if (s->normalize_sar) {
505 /* Apply all SAR during scaling, so we don't need to set the out SAR */
506 s->target_sar = scale_sar;
507 } else {
508 /* This is consistent with other scale_* filters, which only
509 * set the outlink SAR to be equal to the scale SAR iff the input SAR
510 * was set to something nonzero */
511 if (inlink->sample_aspect_ratio.num)
512 outlink->sample_aspect_ratio = scale_sar;
513 }
514
515 if (s->out_format_string) {
516 s->vkctx.output_format = av_get_pix_fmt(s->out_format_string);
517 if (s->vkctx.output_format == AV_PIX_FMT_NONE) {
518 av_log(avctx, AV_LOG_ERROR, "Invalid output format.\n");
519 return AVERROR(EINVAL);
520 }
521 } else {
522 /* Default to re-using the input format */
523 s->vkctx.output_format = s->vkctx.input_format;
524 }
525
526 RET(ff_vk_filter_config_output(outlink));
527 hwfc = (AVHWFramesContext *) outlink->hw_frames_ctx->data;
528 vkfc = hwfc->hwctx;
529 vkfc->usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
530
531 return 0;
532
533fail:
534 return err;
535}
536
537#define OFFSET(x) offsetof(LibplaceboContext, x)
538#define STATIC (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
539#define DYNAMIC (STATIC | AV_OPT_FLAG_RUNTIME_PARAM)
540
541static const AVOption libplacebo_options[] = {
542 { "w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, {.str = "iw"}, .flags = STATIC },
543 { "h", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, {.str = "ih"}, .flags = STATIC },
544 { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, .flags = STATIC },
545 { "force_original_aspect_ratio", "decrease or increase w/h if necessary to keep the original AR", OFFSET(force_original_aspect_ratio), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, STATIC, "force_oar" },
546 { "disable", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 0, STATIC, "force_oar" },
547 { "decrease", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 0, STATIC, "force_oar" },
548 { "increase", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 2 }, 0, 0, STATIC, "force_oar" },
549 { "force_divisible_by", "enforce that the output resolution is divisible by a defined integer when force_original_aspect_ratio is used", OFFSET(force_divisible_by), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, 256, STATIC },
550 { "normalize_sar", "force SAR normalization to 1:1", OFFSET(normalize_sar), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, STATIC },
551 { "pad_crop_ratio", "ratio between padding and cropping when normalizing SAR (0=pad, 1=crop)", OFFSET(pad_crop_ratio), AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, DYNAMIC },
552
553 {"colorspace", "select colorspace", OFFSET(colorspace), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_SPC_NB-1, DYNAMIC, "colorspace"},
554 {"auto", "keep the same colorspace", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "colorspace"},
555 {"gbr", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_RGB}, INT_MIN, INT_MAX, STATIC, "colorspace"},
556 {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT709}, INT_MIN, INT_MAX, STATIC, "colorspace"},
557 {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "colorspace"},
558 {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT470BG}, INT_MIN, INT_MAX, STATIC, "colorspace"},
559 {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "colorspace"},
560 {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "colorspace"},
561 {"ycgco", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_YCGCO}, INT_MIN, INT_MAX, STATIC, "colorspace"},
562 {"bt2020nc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_NCL}, INT_MIN, INT_MAX, STATIC, "colorspace"},
563 {"bt2020c", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_CL}, INT_MIN, INT_MAX, STATIC, "colorspace"},
564 {"ictcp", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_ICTCP}, INT_MIN, INT_MAX, STATIC, "colorspace"},
565
566 {"range", "select color range", OFFSET(color_range), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_RANGE_NB-1, DYNAMIC, "range"},
567 {"auto", "keep the same color range", 0, AV_OPT_TYPE_CONST, {.i64=-1}, 0, 0, STATIC, "range"},
568 {"unspecified", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, STATIC, "range"},
569 {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, STATIC, "range"},
570 {"limited", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
571 {"tv", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
572 {"mpeg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
573 {"full", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
574 {"pc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
575 {"jpeg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
576
577 {"color_primaries", "select color primaries", OFFSET(color_primaries), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_PRI_NB-1, DYNAMIC, "color_primaries"},
578 {"auto", "keep the same color primaries", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
579 {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
580 {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
581 {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
582 {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
583 {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
584 {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
585 {"film", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_FILM}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
586 {"bt2020", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
587 {"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE428}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
588 {"smpte431", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
589 {"smpte432", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
590 {"jedec-p22", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_JEDEC_P22}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
591 {"ebu3213", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_EBU3213}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
592
593 {"color_trc", "select color transfer", OFFSET(color_trc), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_TRC_NB-1, DYNAMIC, "color_trc"},
594 {"auto", "keep the same color transfer", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
595 {"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709}, INT_MIN, INT_MAX, STATIC, "color_trc"},
596 {"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "color_trc"},
597 {"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA22}, INT_MIN, INT_MAX, STATIC, "color_trc"},
598 {"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA28}, INT_MIN, INT_MAX, STATIC, "color_trc"},
599 {"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "color_trc"},
600 {"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "color_trc"},
601 {"linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LINEAR}, INT_MIN, INT_MAX, STATIC, "color_trc"},
602 {"iec61966-2-4", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_4}, INT_MIN, INT_MAX, STATIC, "color_trc"},
603 {"bt1361e", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT1361_ECG}, INT_MIN, INT_MAX, STATIC, "color_trc"},
604 {"iec61966-2-1", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
605 {"bt2020-10", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_10}, INT_MIN, INT_MAX, STATIC, "color_trc"},
606 {"bt2020-12", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_12}, INT_MIN, INT_MAX, STATIC, "color_trc"},
607 {"smpte2084", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084}, INT_MIN, INT_MAX, STATIC, "color_trc"},
608 {"arib-std-b67", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, INT_MIN, INT_MAX, STATIC, "color_trc"},
609
610 { "upscaler", "Upscaler function", OFFSET(upscaler), AV_OPT_TYPE_STRING, {.str = "spline36"}, .flags = DYNAMIC },
611 { "downscaler", "Downscaler function", OFFSET(downscaler), AV_OPT_TYPE_STRING, {.str = "mitchell"}, .flags = DYNAMIC },
612 { "lut_entries", "Number of scaler LUT entries", OFFSET(lut_entries), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 256, DYNAMIC },
613 { "antiringing", "Antiringing strength (for non-EWA filters)", OFFSET(antiringing), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 1.0, DYNAMIC },
614 { "sigmoid", "Enable sigmoid upscaling", OFFSET(sigmoid), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
615 { "apply_filmgrain", "Apply film grain metadata", OFFSET(apply_filmgrain), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
616
617 { "deband", "Enable debanding", OFFSET(deband), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
618 { "deband_iterations", "Deband iterations", OFFSET(deband_iterations), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, DYNAMIC },
619 { "deband_threshold", "Deband threshold", OFFSET(deband_threshold), AV_OPT_TYPE_FLOAT, {.dbl = 4.0}, 0.0, 1024.0, DYNAMIC },
620 { "deband_radius", "Deband radius", OFFSET(deband_radius), AV_OPT_TYPE_FLOAT, {.dbl = 16.0}, 0.0, 1024.0, DYNAMIC },
621 { "deband_grain", "Deband grain", OFFSET(deband_grain), AV_OPT_TYPE_FLOAT, {.dbl = 6.0}, 0.0, 1024.0, DYNAMIC },
622
623 { "brightness", "Brightness boost", OFFSET(brightness), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1.0, 1.0, DYNAMIC },
624 { "contrast", "Contrast gain", OFFSET(contrast), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
625 { "saturation", "Saturation gain", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
626 { "hue", "Hue shift", OFFSET(hue), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -M_PI, M_PI, DYNAMIC },
627 { "gamma", "Gamma adjustment", OFFSET(gamma), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
628
629 { "peak_detect", "Enable dynamic peak detection for HDR tone-mapping", OFFSET(peakdetect), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
630 { "smoothing_period", "Peak detection smoothing period", OFFSET(smoothing), AV_OPT_TYPE_FLOAT, {.dbl = 100.0}, 0.0, 1000.0, DYNAMIC },
631 { "minimum_peak", "Peak detection minimum peak", OFFSET(min_peak), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 100.0, DYNAMIC },
632 { "scene_threshold_low", "Scene change low threshold", OFFSET(scene_low), AV_OPT_TYPE_FLOAT, {.dbl = 5.5}, -1.0, 100.0, DYNAMIC },
633 { "scene_threshold_high", "Scene change high threshold", OFFSET(scene_high), AV_OPT_TYPE_FLOAT, {.dbl = 10.0}, -1.0, 100.0, DYNAMIC },
634 { "overshoot", "Tone-mapping overshoot margin", OFFSET(overshoot), AV_OPT_TYPE_FLOAT, {.dbl = 0.05}, 0.0, 1.0, DYNAMIC },
635
636 { "intent", "Rendering intent", OFFSET(intent), AV_OPT_TYPE_INT, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 3, DYNAMIC, "intent" },
637 { "perceptual", "Perceptual", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 0, STATIC, "intent" },
638 { "relative", "Relative colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
639 { "absolute", "Absolute colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_ABSOLUTE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
640 { "saturation", "Saturation mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_SATURATION}, 0, 0, STATIC, "intent" },
641 { "tonemapping", "Tone-mapping algorithm", OFFSET(tonemapping), AV_OPT_TYPE_INT, {.i64 = PL_TONE_MAPPING_BT_2390}, 0, PL_TONE_MAPPING_ALGORITHM_COUNT - 1, DYNAMIC, "tonemap" },
642 { "clip", "Hard-clipping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_CLIP}, 0, 0, STATIC, "tonemap" },
643 { "mobius", "Mobius tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_MOBIUS}, 0, 0, STATIC, "tonemap" },
644 { "reinhard", "Reinhard tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_REINHARD}, 0, 0, STATIC, "tonemap" },
645 { "hable", "Hable/Filmic tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_HABLE}, 0, 0, STATIC, "tonemap" },
646 { "gamma", "Gamma tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_GAMMA}, 0, 0, STATIC, "tonemap" },
647 { "linear", "Linear tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_LINEAR}, 0, 0, STATIC, "tonemap" },
648 { "bt.2390", "ITU-R BT.2390 tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAPPING_BT_2390}, 0, 0, STATIC, "tonemap" },
649 { "tonemapping_param", "Tunable parameter for some tone-mapping functions", OFFSET(tonemapping_param), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 100.0, .flags = DYNAMIC },
650 { "desaturation_strength", "Desaturation strength", OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.90}, 0.0, 1.0, DYNAMIC },
651 { "desaturation_exponent", "Desaturation exponent", OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = 0.2}, 0.0, 10.0, DYNAMIC },
652 { "desaturation_base", "Desaturation base", OFFSET(desat_base), AV_OPT_TYPE_FLOAT, {.dbl = 0.18}, 0.0, 10.0, DYNAMIC },
653 { "max_boost", "Tone-mapping maximum boost", OFFSET(max_boost), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 1.0, 10.0, DYNAMIC },
654 { "gamut_warning", "Highlight out-of-gamut colors", OFFSET(gamut_warning), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
655 { "gamut_clipping", "Enable colorimetric gamut clipping", OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
656
657 { "dithering", "Dither method to use", OFFSET(dithering), AV_OPT_TYPE_INT, {.i64 = PL_DITHER_BLUE_NOISE}, -1, PL_DITHER_METHOD_COUNT - 1, DYNAMIC, "dither" },
658 { "none", "Disable dithering", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, STATIC, "dither" },
659 { "blue", "Blue noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_BLUE_NOISE}, 0, 0, STATIC, "dither" },
660 { "ordered", "Ordered LUT", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_LUT}, 0, 0, STATIC, "dither" },
661 { "ordered_fixed", "Fixed function ordered", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_FIXED}, 0, 0, STATIC, "dither" },
662 { "white", "White noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_WHITE_NOISE}, 0, 0, STATIC, "dither" },
663 { "dither_lut_size", "Dithering LUT size", OFFSET(dither_lut_size), AV_OPT_TYPE_INT, {.i64 = 6}, 1, 8, STATIC },
664 { "dither_temporal", "Enable temporal dithering", OFFSET(dither_temporal), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
665
666 { "cones", "Colorblindness adaptation model", OFFSET(cones), AV_OPT_TYPE_FLAGS, {.i64 = 0}, 0, PL_CONE_LMS, DYNAMIC, "cone" },
667 { "l", "L cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_L}, 0, 0, STATIC, "cone" },
668 { "m", "M cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_M}, 0, 0, STATIC, "cone" },
669 { "s", "S cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_S}, 0, 0, STATIC, "cone" },
670 { "cone-strength", "Colorblindness adaptation strength", OFFSET(cone_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 10.0, DYNAMIC },
671
672 { "custom_shader_path", "Path to custom user shader (mpv .hook format)", OFFSET(shader_path), AV_OPT_TYPE_STRING, .flags = STATIC },
673 { "custom_shader_bin", "Custom user shader as binary (mpv .hook format)", OFFSET(shader_bin), AV_OPT_TYPE_BINARY, .flags = STATIC },
674
675 /* Performance/quality tradeoff options */
676 { "skip_aa", "Skip anti-aliasing", OFFSET(skip_aa), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 0, DYNAMIC },
677 { "polar_cutoff", "Polar LUT cutoff", OFFSET(polar_cutoff), AV_OPT_TYPE_FLOAT, {.i64 = 0}, 0.0, 1.0, DYNAMIC },
678 { "disable_linear", "Disable linear scaling", OFFSET(disable_linear), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
679 { "disable_builtin", "Disable built-in scalers", OFFSET(disable_builtin), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
680 { "force_3dlut", "Force the use of a full 3DLUT", OFFSET(force_3dlut), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
681 { "force_dither", "Force dithering", OFFSET(force_dither), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
682 { "disable_fbos", "Force-disable FBOs", OFFSET(disable_fbos), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
683 { NULL },
684};
685
686AVFILTER_DEFINE_CLASS(libplacebo);
687
688static const AVFilterPad libplacebo_inputs[] = {
689 {
690 .name = "default",
691 .type = AVMEDIA_TYPE_VIDEO,
692 .filter_frame = &filter_frame,
693 .config_props = &ff_vk_filter_config_input,
694 },
695};
696
697static const AVFilterPad libplacebo_outputs[] = {
698 {
699 .name = "default",
700 .type = AVMEDIA_TYPE_VIDEO,
701 .config_props = &libplacebo_config_output,
702 },
703};
704
705AVFilter ff_vf_libplacebo = {
706 .name = "libplacebo",
707 .description = NULL_IF_CONFIG_SMALL("Apply various GPU filters from libplacebo"),
708 .priv_size = sizeof(LibplaceboContext),
709 .init = &libplacebo_init,
710 .uninit = &libplacebo_uninit,
711 .process_command = &ff_filter_process_command,
712 FILTER_INPUTS(libplacebo_inputs),
713 FILTER_OUTPUTS(libplacebo_outputs),
714 FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
715 .priv_class = &libplacebo_class,
716 .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
717};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t