mirror of
https://mirror.skon.top/https://github.com/FFmpeg/FFmpeg
synced 2026-04-20 21:00:41 +08:00
lavfi/v360: add a Vulkan-compute based filter
This just adds a Vulkan compute-based 360-degree video conversion. It implements a sufficient subset of the most popular 360-degree video formats. Options such as rotation are dynamic and can be adjusted during runtime. Some of the work was based on Paul B. Mahol's patch from 2020. There were spots where the arithmetic conversion was incorrect.
This commit is contained in:
1
configure
vendored
1
configure
vendored
@@ -4284,6 +4284,7 @@ transpose_vt_filter_deps="videotoolbox VTPixelRotationSessionCreate"
|
||||
transpose_vulkan_filter_deps="vulkan spirv_library"
|
||||
unsharp_opencl_filter_deps="opencl"
|
||||
uspp_filter_deps="gpl avcodec"
|
||||
v360_vulkan_filter_deps="vulkan spirv_compiler"
|
||||
vaguedenoiser_filter_deps="gpl"
|
||||
vflip_vulkan_filter_deps="vulkan spirv_library"
|
||||
vidstabdetect_filter_deps="libvidstab"
|
||||
|
||||
@@ -559,6 +559,7 @@ OBJS-$(CONFIG_UNSHARP_OPENCL_FILTER) += vf_unsharp_opencl.o opencl.o \
|
||||
OBJS-$(CONFIG_UNTILE_FILTER) += vf_untile.o
|
||||
OBJS-$(CONFIG_USPP_FILTER) += vf_uspp.o qp_table.o
|
||||
OBJS-$(CONFIG_V360_FILTER) += vf_v360.o
|
||||
OBJS-$(CONFIG_V360_VULKAN_FILTER) += vf_v360_vulkan.o
|
||||
OBJS-$(CONFIG_VAGUEDENOISER_FILTER) += vf_vaguedenoiser.o
|
||||
OBJS-$(CONFIG_VARBLUR_FILTER) += vf_varblur.o framesync.o
|
||||
OBJS-$(CONFIG_VECTORSCOPE_FILTER) += vf_vectorscope.o
|
||||
|
||||
@@ -525,6 +525,7 @@ extern const FFFilter ff_vf_unsharp_opencl;
|
||||
extern const FFFilter ff_vf_untile;
|
||||
extern const FFFilter ff_vf_uspp;
|
||||
extern const FFFilter ff_vf_v360;
|
||||
extern const FFFilter ff_vf_v360_vulkan;
|
||||
extern const FFFilter ff_vf_vaguedenoiser;
|
||||
extern const FFFilter ff_vf_varblur;
|
||||
extern const FFFilter ff_vf_vectorscope;
|
||||
|
||||
386
libavfilter/vf_v360_vulkan.c
Normal file
386
libavfilter/vf_v360_vulkan.c
Normal file
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
#include "vulkan_filter.h"
|
||||
|
||||
#include "v360.h"
|
||||
#include "filters.h"
|
||||
#include "video.h"
|
||||
|
||||
extern const unsigned char ff_v360_comp_spv_data[];
|
||||
extern const unsigned int ff_v360_comp_spv_len;
|
||||
|
||||
typedef struct V360ulkanContext {
|
||||
FFVulkanContext vkctx;
|
||||
|
||||
int initialized;
|
||||
FFVkExecPool e;
|
||||
AVVulkanDeviceQueueFamily *qf;
|
||||
FFVulkanShader shd;
|
||||
VkSampler sampler;
|
||||
|
||||
/* Options */
|
||||
int planewidth[4], planeheight[4];
|
||||
int inplanewidth[4], inplaneheight[4];
|
||||
int in, out;
|
||||
int width, height;
|
||||
float h_fov, v_fov;
|
||||
float ih_fov, iv_fov;
|
||||
float yaw, pitch, roll;
|
||||
char *rorder;
|
||||
int rotation_order[3];
|
||||
} V360VulkanContext;
|
||||
|
||||
/* Push constants */
|
||||
struct PushData {
|
||||
float rot_mat[4][4];
|
||||
int in_img_size[4][2];
|
||||
float iflat_range[2];
|
||||
float flat_range[2];
|
||||
};
|
||||
|
||||
static int get_rorder(char c)
|
||||
{
|
||||
switch (c) {
|
||||
case 'Y':
|
||||
case 'y':
|
||||
return YAW;
|
||||
case 'P':
|
||||
case 'p':
|
||||
return PITCH;
|
||||
case 'R':
|
||||
case 'r':
|
||||
return ROLL;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
|
||||
{
|
||||
int err;
|
||||
V360VulkanContext *s = ctx->priv;
|
||||
FFVulkanContext *vkctx = &s->vkctx;
|
||||
const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
|
||||
|
||||
for (int order = 0; order < NB_RORDERS; order++) {
|
||||
const char c = s->rorder[order];
|
||||
int rorder;
|
||||
|
||||
if (c == '\0') {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Incomplete rorder option. "
|
||||
"Direction for all 3 rotation orders should be specified. "
|
||||
"Switching to default rorder.\n");
|
||||
s->rotation_order[0] = YAW;
|
||||
s->rotation_order[1] = PITCH;
|
||||
s->rotation_order[2] = ROLL;
|
||||
break;
|
||||
}
|
||||
|
||||
rorder = get_rorder(c);
|
||||
if (rorder == -1) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Incorrect rotation order symbol '%c' in rorder option. "
|
||||
"Switching to default rorder.\n", c);
|
||||
s->rotation_order[0] = YAW;
|
||||
s->rotation_order[1] = PITCH;
|
||||
s->rotation_order[2] = ROLL;
|
||||
break; }
|
||||
|
||||
s->rotation_order[order] = rorder;
|
||||
}
|
||||
|
||||
RET(ff_vk_init_sampler(vkctx, &s->sampler, 0, VK_FILTER_LINEAR));
|
||||
|
||||
s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
|
||||
if (!s->qf) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n");
|
||||
err = AVERROR(ENOTSUP);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, 2, 0, 0, 0, NULL));
|
||||
|
||||
SPEC_LIST_CREATE(sl, 4, 2*sizeof(int) + 2*sizeof(float))
|
||||
SPEC_LIST_ADD(sl, 0, 32, s->out);
|
||||
SPEC_LIST_ADD(sl, 1, 32, s->in);
|
||||
|
||||
const float m_pi = M_PI, m_pi2 = M_PI_2;
|
||||
SPEC_LIST_ADD(sl, 2, 32, av_float2int(m_pi));
|
||||
SPEC_LIST_ADD(sl, 3, 32, av_float2int(m_pi2));
|
||||
|
||||
ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
sl, (uint32_t []) { 16, 16, 1 }, 0);
|
||||
|
||||
ff_vk_shader_add_push_const(&s->shd, 0, sizeof(struct PushData),
|
||||
VK_SHADER_STAGE_COMPUTE_BIT);
|
||||
|
||||
const FFVulkanDescriptorSetBinding desc_set[] = {
|
||||
{ /* input_img */
|
||||
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.elems = planes,
|
||||
.samplers = DUP_SAMPLER(s->sampler),
|
||||
},
|
||||
{ /* output_img */
|
||||
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
|
||||
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
|
||||
.elems = planes,
|
||||
},
|
||||
};
|
||||
ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 2, 0, 0);
|
||||
|
||||
RET(ff_vk_shader_link(vkctx, &s->shd,
|
||||
ff_v360_comp_spv_data,
|
||||
ff_v360_comp_spv_len, "main"));
|
||||
|
||||
RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
|
||||
|
||||
s->initialized = 1;
|
||||
|
||||
fail:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void multiply_matrix(float c[4][4], const float a[4][4], const float b[4][4])
|
||||
{
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
float sum = 0.0f;
|
||||
for (int k = 0; k < 3; k++)
|
||||
sum += a[i][k] * b[k][j];
|
||||
c[i][j] = sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void calculate_iflat_range(int in, float ih_fov, float iv_fov,
|
||||
float *iflat_range)
|
||||
{
|
||||
switch (in) {
|
||||
case FLAT:
|
||||
iflat_range[0] = tanf(0.5f * ih_fov * M_PI / 180.f);
|
||||
iflat_range[1] = tanf(0.5f * iv_fov * M_PI / 180.f);
|
||||
break;
|
||||
case STEREOGRAPHIC:
|
||||
iflat_range[0] = tanf(FFMIN(ih_fov, 359.f) * M_PI / 720.f);
|
||||
iflat_range[1] = tanf(FFMIN(iv_fov, 359.f) * M_PI / 720.f);
|
||||
break;
|
||||
case DUAL_FISHEYE:
|
||||
case FISHEYE:
|
||||
iflat_range[0] = ih_fov / 180.f;
|
||||
iflat_range[1] = iv_fov / 180.f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void calculate_flat_range(int out, float h_fov, float v_fov,
|
||||
float *flat_range)
|
||||
{
|
||||
switch (out) {
|
||||
case FLAT:
|
||||
flat_range[0] = tanf(0.5f * h_fov * M_PI / 180.f);
|
||||
flat_range[1] = tanf(0.5f * v_fov * M_PI / 180.f);
|
||||
break;
|
||||
case STEREOGRAPHIC:
|
||||
flat_range[0] = tanf(FFMIN(h_fov, 359.f) * M_PI / 720.f);
|
||||
flat_range[1] = tanf(FFMIN(v_fov, 359.f) * M_PI / 720.f);
|
||||
break;
|
||||
case DUAL_FISHEYE:
|
||||
case FISHEYE:
|
||||
flat_range[0] = h_fov / 180.f;
|
||||
flat_range[1] = v_fov / 180.f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void calculate_rotation_matrix(float yaw, float pitch, float roll,
|
||||
float rot_mat[4][4],
|
||||
const int rotation_order[3])
|
||||
{
|
||||
const float yaw_rad = yaw * M_PI / 180.f;
|
||||
const float pitch_rad = pitch * M_PI / 180.f;
|
||||
const float roll_rad = roll * M_PI / 180.f;
|
||||
|
||||
const float sin_yaw = sinf(yaw_rad);
|
||||
const float cos_yaw = cosf(yaw_rad);
|
||||
const float sin_pitch = sinf(pitch_rad);
|
||||
const float cos_pitch = cosf(pitch_rad);
|
||||
const float sin_roll = sinf(roll_rad);
|
||||
const float cos_roll = cosf(roll_rad);
|
||||
|
||||
float m[3][4][4];
|
||||
float temp[4][4];
|
||||
|
||||
m[0][0][0] = cos_yaw; m[0][0][1] = 0; m[0][0][2] = sin_yaw;
|
||||
m[0][1][0] = 0; m[0][1][1] = 1; m[0][1][2] = 0;
|
||||
m[0][2][0] = -sin_yaw; m[0][2][1] = 0; m[0][2][2] = cos_yaw;
|
||||
|
||||
m[1][0][0] = 1; m[1][0][1] = 0; m[1][0][2] = 0;
|
||||
m[1][1][0] = 0; m[1][1][1] = cos_pitch; m[1][1][2] = -sin_pitch;
|
||||
m[1][2][0] = 0; m[1][2][1] = sin_pitch; m[1][2][2] = cos_pitch;
|
||||
|
||||
m[2][0][0] = cos_roll; m[2][0][1] = -sin_roll; m[2][0][2] = 0;
|
||||
m[2][1][0] = sin_roll; m[2][1][1] = cos_roll; m[2][1][2] = 0;
|
||||
m[2][2][0] = 0; m[2][2][1] = 0; m[2][2][2] = 1;
|
||||
|
||||
multiply_matrix(temp, m[rotation_order[0]], m[rotation_order[1]]);
|
||||
multiply_matrix(rot_mat, temp, m[rotation_order[2]]);
|
||||
}
|
||||
|
||||
static int v360_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
|
||||
{
|
||||
int err;
|
||||
AVFrame *out = NULL;
|
||||
AVFilterContext *ctx = link->dst;
|
||||
V360VulkanContext *s = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
|
||||
if (!out) {
|
||||
err = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!s->initialized)
|
||||
RET(init_filter(ctx, in));
|
||||
|
||||
/* Push constants */
|
||||
struct PushData pd = { 0 };
|
||||
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->vkctx.input_format);
|
||||
pd.in_img_size[0][0] = pd.in_img_size[3][0] = in->width;
|
||||
pd.in_img_size[0][1] = pd.in_img_size[3][1] = in->height;
|
||||
pd.in_img_size[1][0] = pd.in_img_size[2][0] =
|
||||
FF_CEIL_RSHIFT(in->width, desc->log2_chroma_w);
|
||||
pd.in_img_size[1][1] = pd.in_img_size[2][1] =
|
||||
FF_CEIL_RSHIFT(in->height, desc->log2_chroma_h);
|
||||
|
||||
calculate_iflat_range(s->in, s->ih_fov, s->iv_fov, pd.iflat_range);
|
||||
calculate_flat_range(s->out, s->h_fov, s->v_fov, pd.flat_range);
|
||||
calculate_rotation_matrix(s->yaw, s->pitch, s->roll,
|
||||
pd.rot_mat, s->rotation_order);
|
||||
|
||||
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd,
|
||||
out, in, s->sampler,
|
||||
&pd, sizeof(pd)));
|
||||
|
||||
err = av_frame_copy_props(out, in);
|
||||
if (err < 0)
|
||||
goto fail;
|
||||
|
||||
av_frame_free(&in);
|
||||
|
||||
return ff_filter_frame(outlink, out);
|
||||
|
||||
fail:
|
||||
av_frame_free(&in);
|
||||
av_frame_free(&out);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void v360_vulkan_uninit(AVFilterContext *avctx)
|
||||
{
|
||||
V360VulkanContext *s = avctx->priv;
|
||||
FFVulkanContext *vkctx = &s->vkctx;
|
||||
FFVulkanFunctions *vk = &vkctx->vkfn;
|
||||
|
||||
ff_vk_exec_pool_free(vkctx, &s->e);
|
||||
ff_vk_shader_free(vkctx, &s->shd);
|
||||
|
||||
if (s->sampler)
|
||||
vk->DestroySampler(vkctx->hwctx->act_dev, s->sampler,
|
||||
vkctx->hwctx->alloc);
|
||||
|
||||
ff_vk_uninit(&s->vkctx);
|
||||
|
||||
s->initialized = 0;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(V360VulkanContext, x)
|
||||
#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
|
||||
#define DYNAMIC (FLAGS | AV_OPT_FLAG_RUNTIME_PARAM)
|
||||
static const AVOption v360_vulkan_options[] = {
|
||||
{ "input", "set input projection", OFFSET(in), AV_OPT_TYPE_INT, {.i64=EQUIRECTANGULAR}, 0, NB_PROJECTIONS-1, FLAGS, "in" },
|
||||
{ "e", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, 0, FLAGS, "in" },
|
||||
{ "equirect", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, 0, FLAGS, "in" },
|
||||
{ "flat", "regular video", 0, AV_OPT_TYPE_CONST, {.i64=FLAT}, 0, 0, FLAGS, "in" },
|
||||
{ "dfisheye", "dual fisheye", 0, AV_OPT_TYPE_CONST, {.i64=DUAL_FISHEYE}, 0, 0, FLAGS, "in" },
|
||||
{ "sg", "stereographic", 0, AV_OPT_TYPE_CONST, {.i64=STEREOGRAPHIC}, 0, 0, FLAGS, "in" },
|
||||
{ "fisheye", "fisheye", 0, AV_OPT_TYPE_CONST, {.i64=FISHEYE}, 0, 0, FLAGS, "in" },
|
||||
|
||||
{ "output", "set output projection", OFFSET(out), AV_OPT_TYPE_INT, {.i64=FLAT}, 0, NB_PROJECTIONS-1, FLAGS, "out" },
|
||||
{ "e", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, 0, FLAGS, "out" },
|
||||
{ "equirect", "equirectangular", 0, AV_OPT_TYPE_CONST, {.i64=EQUIRECTANGULAR}, 0, 0, FLAGS, "out" },
|
||||
{ "flat", "regular video", 0, AV_OPT_TYPE_CONST, {.i64=FLAT}, 0, 0, FLAGS, "out" },
|
||||
{ "dfisheye", "dual fisheye", 0, AV_OPT_TYPE_CONST, {.i64=DUAL_FISHEYE}, 0, 0, FLAGS, "out" },
|
||||
{ "sg", "stereographic", 0, AV_OPT_TYPE_CONST, {.i64=STEREOGRAPHIC}, 0, 0, FLAGS, "out" },
|
||||
{ "fisheye", "fisheye", 0, AV_OPT_TYPE_CONST, {.i64=FISHEYE}, 0, 0, FLAGS, "out" },
|
||||
|
||||
{ "w", "output width", OFFSET(width), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT16_MAX, FLAGS, "w" },
|
||||
{ "h", "output height", OFFSET(height), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT16_MAX, FLAGS, "h" },
|
||||
{ "yaw", "yaw rotation", OFFSET(yaw), AV_OPT_TYPE_FLOAT, {.dbl = 0.0f}, -180.f, 180.f, DYNAMIC, "yaw" },
|
||||
{ "pitch", "pitch rotation", OFFSET(pitch), AV_OPT_TYPE_FLOAT, {.dbl = 0.0f}, -180.f, 180.f, DYNAMIC, "pitch" },
|
||||
{ "roll", "roll rotation", OFFSET(roll), AV_OPT_TYPE_FLOAT, {.dbl = 0.0f}, -180.f, 180.f, DYNAMIC, "roll" },
|
||||
{ "rorder", "rotation order", OFFSET(rorder), AV_OPT_TYPE_STRING, {.str = "ypr"}, 0, 0, FLAGS, "rorder" },
|
||||
{ "h_fov", "set output horizontal FOV angle", OFFSET(h_fov), AV_OPT_TYPE_FLOAT, {.dbl = 90.0f}, 0.00001f, 360.0f, DYNAMIC, "h_fov" },
|
||||
{ "v_fov", "set output vertical FOV angle", OFFSET(v_fov), AV_OPT_TYPE_FLOAT, {.dbl = 45.0f}, 0.00001f, 360.0f, DYNAMIC, "v_fov" },
|
||||
{ "ih_fov", "set input horizontal FOV angle", OFFSET(ih_fov), AV_OPT_TYPE_FLOAT, {.dbl = 90.0f}, 0.00001f, 360.0f, DYNAMIC, "ih_fov" },
|
||||
{ "iv_fov", "set input vertical FOV angle", OFFSET(iv_fov), AV_OPT_TYPE_FLOAT, {.dbl = 45.0f}, 0.00001f, 360.0f, DYNAMIC, "iv_fov" },
|
||||
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(v360_vulkan);
|
||||
|
||||
static const AVFilterPad v360_vulkan_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = &v360_vulkan_filter_frame,
|
||||
.config_props = &ff_vk_filter_config_input,
|
||||
},
|
||||
};
|
||||
|
||||
static const AVFilterPad v360_vulkan_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = &ff_vk_filter_config_output,
|
||||
},
|
||||
};
|
||||
|
||||
const FFFilter ff_vf_v360_vulkan = {
|
||||
.p.name = "v360_vulkan",
|
||||
.p.description = NULL_IF_CONFIG_SMALL("Convert 360 projection of video."),
|
||||
.p.priv_class = &v360_vulkan_class,
|
||||
.p.flags = AVFILTER_FLAG_HWDEVICE,
|
||||
.priv_size = sizeof(V360VulkanContext),
|
||||
.init = &ff_vk_filter_init,
|
||||
.uninit = &v360_vulkan_uninit,
|
||||
FILTER_INPUTS(v360_vulkan_inputs),
|
||||
FILTER_OUTPUTS(v360_vulkan_outputs),
|
||||
FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
|
||||
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
|
||||
};
|
||||
@@ -4,3 +4,4 @@ clean::
|
||||
OBJS-$(CONFIG_AVGBLUR_VULKAN_FILTER) += vulkan/avgblur.comp.spv.o
|
||||
OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o
|
||||
OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o
|
||||
OBJS-$(CONFIG_V360_VULKAN_FILTER) += vulkan/v360.comp.spv.o
|
||||
|
||||
203
libavfilter/vulkan/v360.comp.glsl
Normal file
203
libavfilter/vulkan/v360.comp.glsl
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* FFmpeg is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with FFmpeg; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma shader_stage(compute)
|
||||
|
||||
#extension GL_EXT_shader_image_load_formatted : require
|
||||
#extension GL_EXT_scalar_block_layout : require
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
|
||||
layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
|
||||
|
||||
#define EQUIRECTANGULAR 0
|
||||
#define FLAT 4
|
||||
#define DUAL_FISHEYE 5
|
||||
#define STEREOGRAPHIC 8
|
||||
#define FISHEYE 13
|
||||
layout (constant_id = 0) const int out_transform = 0;
|
||||
layout (constant_id = 1) const int in_transform = 0;
|
||||
|
||||
layout (constant_id = 2) const float m_pi = 0;
|
||||
layout (constant_id = 3) const float m_pi2 = 0;
|
||||
|
||||
layout (set = 0, binding = 0) uniform sampler2D input_img[];
|
||||
layout (set = 0, binding = 1) uniform writeonly image2D output_img[];
|
||||
|
||||
layout (push_constant, scalar) uniform pushConstants {
|
||||
mat4 rot_mat;
|
||||
ivec2 in_img_size[4];
|
||||
vec2 iflat_range;
|
||||
vec2 flat_range;
|
||||
};
|
||||
|
||||
#define IS_WITHIN(v1, v2) any(lessThan(v1, v2))
|
||||
|
||||
void xyz_to_flat(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
|
||||
{
|
||||
const float r = tan(acos(v[2]));
|
||||
const float rr = abs(r) < 1e+6f ? r : length(in_size);
|
||||
const float h = length(vec2(v[0], v[1]));
|
||||
const float c = h <= 1e-6f ? 1.0f : rr / h;
|
||||
vec2 p = vec2(v[0], v[1]) / iflat_range * c;
|
||||
p = IS_WITHIN(abs(p), vec2(1.0f)) ? (p/2.0f) + 0.5f : vec2(0.0f);
|
||||
p = v[2] >= 0.0f ? p : vec2(0.0f);
|
||||
vec4 res = texture(input_img[idx], p);
|
||||
imageStore(output_img[idx], pos, res);
|
||||
}
|
||||
|
||||
vec3 flat_to_xyz(in ivec2 out_size, in ivec2 pos)
|
||||
{
|
||||
vec2 fpos = vec2(pos) + vec2(0.5f, 0.5f);
|
||||
vec2 p = ((fpos / vec2(out_size)) - 0.5f)*2.0f;
|
||||
vec3 v = vec3(p[0], p[1], 1.0f) * vec3(flat_range, 1.0f);
|
||||
return normalize(v);
|
||||
}
|
||||
|
||||
vec3 equirect_to_xyz(in ivec2 out_size, in ivec2 pos)
|
||||
{
|
||||
vec2 fpos = 2.0f * vec2(pos) + 0.5f;
|
||||
vec2 p = fpos / vec2(out_size) - 1.0f;
|
||||
p = vec2(p[0] * m_pi, p[1] * m_pi2);
|
||||
return vec3(cos(p[1]) * sin(p[0]), sin(p[1]), cos(p[1])*cos(p[0]));
|
||||
}
|
||||
|
||||
void xyz_to_equirect(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
|
||||
{
|
||||
vec2 p = vec2(atan(v[0], v[2]) / m_pi, asin(v[1]) / m_pi2);
|
||||
vec4 res = texture(input_img[idx], (p/2.0f) + 0.5f);
|
||||
imageStore(output_img[idx], pos, res);
|
||||
}
|
||||
|
||||
vec3 stereographic_to_xyz(in ivec2 out_size, in ivec2 pos)
|
||||
{
|
||||
vec2 fpos = vec2(pos) + vec2(0.5f, 0.5f);
|
||||
vec2 p = (fpos / vec2(out_size) - 0.5f) * 2.0f * flat_range;
|
||||
const float r = length(p);
|
||||
const float lr = r > 0.0f ? r : 1.0f;
|
||||
const float theta = atan(r) * 2.0f;
|
||||
vec3 v = vec3(p[0] / lr*sin(theta), p[1] / lr*sin(theta), cos(theta));
|
||||
return normalize(v);
|
||||
}
|
||||
|
||||
void xyz_to_stereographic(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
|
||||
{
|
||||
const float theta = acos(v[2]);
|
||||
const float r = tan(theta * 0.5f);
|
||||
const vec2 c = (r / length(vec2(v[0], v[1]))) / iflat_range;
|
||||
vec2 p = vec2(v[0], v[1]) * c;
|
||||
p = IS_WITHIN(abs(p), vec2(1.0f)) ? (p/2.0f)+0.5f:vec2(0.0f);
|
||||
vec4 res = texture(input_img[idx], p);
|
||||
imageStore(output_img[idx], pos, res);
|
||||
}
|
||||
|
||||
vec3 fisheye_to_xyz(in ivec2 out_size, in ivec2 pos)
|
||||
{
|
||||
vec2 fpos = vec2(pos) + vec2(0.5f, 0.5f);
|
||||
vec2 p = (fpos / vec2(out_size) - 0.5f) * 2.0f * flat_range;
|
||||
const float r = length(p);
|
||||
const float phi = atan(p[1], p[0]);
|
||||
const float theta = (1.0f - r) * m_pi2;
|
||||
return vec3(cos(theta)*cos(phi), cos(theta)*sin(phi), sin(theta));
|
||||
}
|
||||
|
||||
void xyz_to_fisheye(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
|
||||
{
|
||||
const float h = length(vec2(v[0], v[1]));
|
||||
const float lh = h > 0.0f ? h / 2.0f : 1.0f;
|
||||
const float phi = atan(h, v[2]) / m_pi;
|
||||
vec2 p = vec2(v[0], v[1]) * phi / lh / iflat_range;
|
||||
p = (length(p) <= 1.0f) ? (p/2.0f) + 0.5f:vec2(0.0f);
|
||||
vec4 res = texture(input_img[idx], p);
|
||||
imageStore(output_img[idx], pos, res);
|
||||
}
|
||||
|
||||
vec3 dfisheye_to_xyz(in ivec2 out_size, in ivec2 pos)
|
||||
{
|
||||
const float m = pos[0] >= out_size[0] / 2 ? 1.0f : -1.0f;
|
||||
vec2 npos = m == 1.0f ? vec2(out_size[0] / 2, 0.0f) : vec2(0.0f);
|
||||
vec2 fpos = vec2(pos) - npos + vec2(0.5f, 0.5f);
|
||||
vec2 osize = vec2(out_size) * vec2(0.5f, 1.0f);
|
||||
vec2 p = (fpos / osize - 0.5f) * 2.0f * flat_range;
|
||||
const float h = length(p);
|
||||
const float lh = h > 0.0f ? h : 1.0f;
|
||||
const float theta = m * m_pi2 * (1.0f - h);
|
||||
p = p / lh;
|
||||
vec3 v = vec3(cos(theta)*m*p[0], cos(theta)*p[1], sin(theta));
|
||||
return normalize(v);
|
||||
}
|
||||
|
||||
void xyz_to_dfisheye(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
|
||||
{
|
||||
const float h = length(vec2(v[0], v[1]));
|
||||
const float lh = h > 0.0f ? h : 1.0f;
|
||||
const float theta = acos(abs(v[2])) / m_pi;
|
||||
vec2 p = (vec2(v[0], v[1]) * theta) / lh / iflat_range + 0.5f;
|
||||
p = p * vec2(0.5f, 1.0f);
|
||||
p = v[2] >= 0.0f ? vec2(p[0] + 0.5f, p[1]) : vec2(0.5f - p[0], p[1]);
|
||||
vec4 res = texture(input_img[idx], p);
|
||||
imageStore(output_img[idx], pos, res);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ivec2 in_size = in_img_size[i];
|
||||
ivec2 out_size = imageSize(output_img[i]);
|
||||
|
||||
vec3 v = vec3(0);
|
||||
switch (out_transform) {
|
||||
case EQUIRECTANGULAR:
|
||||
v = equirect_to_xyz(out_size, pos);
|
||||
break;
|
||||
case FLAT:
|
||||
v = flat_to_xyz(out_size, pos);
|
||||
break;
|
||||
case DUAL_FISHEYE:
|
||||
v = dfisheye_to_xyz(out_size, pos);
|
||||
break;
|
||||
case STEREOGRAPHIC:
|
||||
v = stereographic_to_xyz(out_size, pos);
|
||||
break;
|
||||
case FISHEYE:
|
||||
v = fisheye_to_xyz(out_size, pos);
|
||||
break;
|
||||
}
|
||||
|
||||
v = normalize((rot_mat * vec4(v, 0.0f)).xyz);
|
||||
|
||||
switch (in_transform) {
|
||||
case EQUIRECTANGULAR:
|
||||
xyz_to_equirect(i, v, pos, in_size);
|
||||
break;
|
||||
case FLAT:
|
||||
xyz_to_flat(i, v, pos, in_size);
|
||||
break;
|
||||
case DUAL_FISHEYE:
|
||||
xyz_to_dfisheye(i, v, pos, in_size);
|
||||
break;
|
||||
case STEREOGRAPHIC:
|
||||
xyz_to_stereographic(i, v, pos, in_size);
|
||||
break;
|
||||
case FISHEYE:
|
||||
xyz_to_fisheye(i, v, pos, in_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user