avfilter/v360_vulkan: support GoPro Max inputs

Add GoPro Max 360 projection for input.
Add tentative Equi-Angular Cubemap (EAC) projection for output
(for testing).

Signed-off-by: TADANO Tokumei <aimingoff@pc.nifty.jp>
This commit is contained in:
TADANO Tokumei authored and Lynne committed 2026-10-01 14:57:38 +00:00
1 parent cc6003d5f5
commit dce56e75eb
4 files changed
+322 -42

No files matched your search

+1 -1
View File
@@ -560,7 +560,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_V360_VULKAN_FILTER) += vf_v360_vulkan.o framesync.o
OBJS-$(CONFIG_VAGUEDENOISER_FILTER) += vf_vaguedenoiser.o
OBJS-$(CONFIG_VARBLUR_FILTER) += vf_varblur.o framesync.o
OBJS-$(CONFIG_VECTORSCOPE_FILTER) += vf_vectorscope.o
+1
View File
@@ -55,6 +55,7 @@ enum Projections {
ORTHOGRAPHIC,
OCTAHEDRON,
CYLINDRICALEA,
GOPROMAX,
NB_PROJECTIONS,
};
+155 -39
View File
@@ -22,6 +22,7 @@
#include "v360.h"
#include "filters.h"
#include "video.h"
#include "framesync.h"
extern const unsigned char ff_v360_comp_spv_data[];
extern const unsigned int ff_v360_comp_spv_len;
@@ -35,6 +36,7 @@ struct PushData {
typedef struct V360ulkanContext {
FFVulkanContext vkctx;
FFFrameSync fs;
int initialized;
FFVkExecPool e;
@@ -51,6 +53,7 @@ typedef struct V360ulkanContext {
float yaw, pitch, roll;
char *rorder;
int rotation_order[3];
int overlap;
} V360VulkanContext;
static int get_rorder(char c)
@@ -198,6 +201,7 @@ static void config_params(AVFilterContext *ctx, AVFilterLink *inlink)
s->iv_fov = 180.f;
break;
case EQUIRECTANGULAR: /* unchangeable */
case GOPROMAX:
s->ih_fov = 360.f;
s->iv_fov = 180.f;
break;
@@ -335,6 +339,9 @@ static av_cold int calculate_output_size(AVFilterContext *ctx)
case DUAL_FISHEYE:
hf = wf * sar / 2.f;
break;
case EQUIANGULAR:
hf = wf * sar / 3.f * 2.f;
break;
case STEREOGRAPHIC:
case FISHEYE:
hf = wf * sar;
@@ -353,6 +360,9 @@ static av_cold int calculate_output_size(AVFilterContext *ctx)
case FLAT:
hf = (float)inlink->h / s->pd.iflat_range[1] / 2.f;
break;
case GOPROMAX:
hf = (float)inlink->h * 2.f;
break;
default:
hf = (float)inlink->h;
break;
@@ -376,6 +386,9 @@ static av_cold int calculate_output_size(AVFilterContext *ctx)
case DUAL_FISHEYE:
wf = hf * 2.f;
break;
case EQUIANGULAR:
wf = hf * 3.f / 2.f;
break;
case STEREOGRAPHIC:
case FISHEYE:
wf = hf;
@@ -404,6 +417,10 @@ static av_cold int calculate_output_size(AVFilterContext *ctx)
min_w = 2;
min_h = 1;
break;
case EQUIANGULAR:
min_w = 3;
min_h = 2;
break;
default:
min_w = 1;
min_h = 1;
@@ -438,7 +455,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, FF_VK_DEFAULT_EXEC_CONTEXTS, 0, 0, 0, NULL));
SPEC_LIST_CREATE(sl, 10, 7*sizeof(int) + 3*sizeof(float))
SPEC_LIST_CREATE(sl, 17, 14*sizeof(int) + 3*sizeof(float))
SPEC_LIST_ADD(sl, 0, 32, s->out);
SPEC_LIST_ADD(sl, 1, 32, s->in);
@@ -453,6 +470,19 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
SPEC_LIST_ADD(sl, 8, 32, FF_CEIL_RSHIFT(in->width, desc->log2_chroma_w));
SPEC_LIST_ADD(sl, 9, 32, FF_CEIL_RSHIFT(in->height, desc->log2_chroma_h));
if (s->in == GOPROMAX) {
int cube_size = in->height;
int gopro_cube_width = (in->width - cube_size) / 2;
SPEC_LIST_ADD(sl, 10, 32, cube_size);
SPEC_LIST_ADD(sl, 11, 32, gopro_cube_width);
SPEC_LIST_ADD(sl, 12, 32, s->overlap);
SPEC_LIST_ADD(sl, 13, 32, gopro_cube_width + cube_size);
SPEC_LIST_ADD(sl, 14, 32, gopro_cube_width / 2 - s->overlap);
SPEC_LIST_ADD(sl, 15, 32, gopro_cube_width / 2);
SPEC_LIST_ADD(sl, 16, 32, gopro_cube_width / 2 + s->overlap);
}
ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT,
sl, (uint32_t []) { 16, 16, 1 }, 0);
@@ -460,19 +490,25 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
VK_SHADER_STAGE_COMPUTE_BIT);
const FFVulkanDescriptorSetBinding desc_set[] = {
{ /* output_img */
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.elems = planes,
},
{ /* 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,
{ /* input2_img */
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.elems = planes,
.samplers = DUP_SAMPLER(s->sampler),
},
};
ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 2, 0);
ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 3, 0);
RET(ff_vk_shader_link(vkctx, &s->shd,
ff_v360_comp_spv_data,
@@ -486,13 +522,20 @@ fail:
return err;
}
static int v360_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
static int v360_vulkan_filter_frame(FFFrameSync *fs)
{
int err;
AVFrame *out = NULL;
AVFilterContext *ctx = link->dst;
V360VulkanContext *s = ctx->priv;
AVFilterContext *ctx = fs->parent;
V360VulkanContext *s = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
AVFrame *in[2] = { NULL };
AVFrame *out = NULL;
for (int i = 0; i < ctx->nb_inputs; i++) {
RET(ff_framesync_get_frame(fs, i, &in[i], 0));
if (!in[i])
return 0;
}
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
@@ -500,27 +543,37 @@ static int v360_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
goto fail;
}
if (!s->initialized)
RET(init_filter(ctx, in));
if (!s->initialized) {
AVHWFramesContext *fc0 = (AVHWFramesContext*)in[0]->hw_frames_ctx->data;
for (int i = 1; i < ctx->nb_inputs; i++) {
AVHWFramesContext *fci = (AVHWFramesContext*)in[i]->hw_frames_ctx->data;
if (fc0->sw_format != fci->sw_format) {
av_log(ctx, AV_LOG_ERROR, "Input %d has a different format "
"from input 0\n", i);
err = AVERROR(EINVAL);
goto fail;
}
}
RET(init_filter(ctx, in[0]));
}
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd,
out, in, s->sampler, 1,
&s->pd, sizeof(s->pd)));
err = av_frame_copy_props(out, in);
if (err < 0)
goto fail;
av_frame_free(&in);
RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd, out,
in, ctx->nb_inputs, s->sampler, 1,
&s->pd, sizeof(s->pd)));
RET(av_frame_copy_props(out, in[0]));
return ff_filter_frame(outlink, out);
fail:
av_frame_free(&in);
av_frame_free(&out);
return err;
}
static int v360_vulkan_activate(AVFilterContext *ctx)
{
V360VulkanContext *s = ctx->priv;
return ff_framesync_activate(&s->fs);
}
static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
char *res, int res_len, int flags)
@@ -539,13 +592,80 @@ static av_cold int v360_vulkan_config_output(AVFilterLink *outlink)
{
int err;
AVFilterContext *ctx = outlink->src;
V360VulkanContext *s = ctx->priv;
AVFilterLink *inlink = ctx->inputs[0];
if (s->in == GOPROMAX) {
if (s->overlap == 0) {
if (inlink->h <= 960) {
s->overlap = 32;
}
else if (inlink->h < 1920) {
s->overlap = 64;
}
else
s->overlap = 96;
}
if (ctx->nb_inputs != 2) {
av_log(ctx, AV_LOG_ERROR, "GoPro Max. requires 2 input streams.\n");
return AVERROR(EINVAL);
}
if ((inlink->w != ctx->inputs[1]->w) ||
(inlink->h != ctx->inputs[1]->h) ||
(inlink->w < inlink->h * 3) ||
(inlink->w > inlink->h * 3 + s->overlap * 2)) {
av_log(ctx, AV_LOG_ERROR, "Incompatible inputs for GoPro Max. (%dx%d + %dx%d)\n",
inlink->w, inlink->h, ctx->inputs[1]->w, ctx->inputs[1]->h);
return AVERROR(EINVAL);
}
}
else if (ctx->nb_inputs != 1) {
av_log(ctx, AV_LOG_ERROR, "Too many inputs (%d)\n", ctx->nb_inputs);
return AVERROR(EINVAL);
}
config_params(ctx, inlink);
RET(calculate_output_size(ctx));
RET(ff_vk_filter_config_output(outlink));
RET(ff_framesync_init(&s->fs, ctx, ctx->nb_inputs));
for (int i = 0; i < ctx->nb_inputs; i++) {
s->fs.in[i].time_base = ctx->inputs[i]->time_base;
s->fs.in[i].sync = 1;
s->fs.in[i].before = EXT_STOP;
s->fs.in[i].after = EXT_STOP;
}
s->fs.on_event = &v360_vulkan_filter_frame;
RET(ff_framesync_configure(&s->fs));
outlink->time_base = s->fs.time_base;
fail:
return err;
}
static av_cold int v360_vulkan_init(AVFilterContext *avctx)
{
int err;
V360VulkanContext *s = avctx->priv;
AVFilterPad pad = {
.type = AVMEDIA_TYPE_VIDEO,
.config_props = ff_vk_filter_config_input,
};
if (s->in == GOPROMAX) {
pad.name = "front";
RET(ff_append_inpad(avctx, &pad));
pad.name = "rear";
RET(ff_append_inpad(avctx, &pad));
}
else {
pad.name = "default";
RET(ff_append_inpad(avctx, &pad));
}
return ff_vk_filter_init(avctx);
fail:
return err;
}
@@ -556,6 +676,7 @@ static void v360_vulkan_uninit(AVFilterContext *avctx)
FFVulkanContext *vkctx = &s->vkctx;
FFVulkanFunctions *vk = &vkctx->vkfn;
ff_framesync_uninit(&s->fs);
ff_vk_exec_pool_free(vkctx, &s->e);
ff_vk_shader_free(vkctx, &s->shd);
@@ -579,10 +700,12 @@ static const AVOption v360_vulkan_options[] = {
{ "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" },
{ "gopromax", "GoPro Max (two inputs)", 0, AV_OPT_TYPE_CONST, {.i64=GOPROMAX}, 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" },
{ "eac", "equi-angular cubemap", 0, AV_OPT_TYPE_CONST, {.i64=EQUIANGULAR}, 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" },
@@ -598,26 +721,18 @@ static const AVOption v360_vulkan_options[] = {
{ "v_fov", "set output vertical FOV angle", OFFSET(v_fov), AV_OPT_TYPE_FLOAT, {.dbl = 0.0f}, 0.0f, 360.0f, DYNAMIC, "v_fov" },
{ "ih_fov", "set input horizontal FOV angle", OFFSET(ih_fov), AV_OPT_TYPE_FLOAT, {.dbl = 0.0f}, 0.0f, 360.0f, DYNAMIC, "ih_fov" },
{ "iv_fov", "set input vertical FOV angle", OFFSET(iv_fov), AV_OPT_TYPE_FLOAT, {.dbl = 0.0f}, 0.0f, 360.0f, DYNAMIC, "iv_fov" },
{ "overlap", "overlapped pixels for GoPro Max", OFFSET(overlap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1024, FLAGS, "overlap" },
{ 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,
},
};
FRAMESYNC_DEFINE_CLASS(v360_vulkan, V360VulkanContext, fs);
static const AVFilterPad v360_vulkan_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = &v360_vulkan_config_output,
.config_props = v360_vulkan_config_output,
},
};
@@ -625,13 +740,14 @@ 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,
.p.flags = AVFILTER_FLAG_HWDEVICE | AVFILTER_FLAG_DYNAMIC_INPUTS,
.priv_size = sizeof(V360VulkanContext),
.init = &ff_vk_filter_init,
.uninit = &v360_vulkan_uninit,
FILTER_INPUTS(v360_vulkan_inputs),
.preinit = v360_vulkan_framesync_preinit,
.init = v360_vulkan_init,
.uninit = v360_vulkan_uninit,
.activate = v360_vulkan_activate,
FILTER_OUTPUTS(v360_vulkan_outputs),
FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
.process_command = &process_command,
.process_command = process_command,
};
+165 -2
View File
@@ -25,10 +25,12 @@
layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
#define EQUIRECTANGULAR 0
#define EQUIANGULAR 3
#define FLAT 4
#define DUAL_FISHEYE 5
#define STEREOGRAPHIC 8
#define FISHEYE 13
#define GOPROMAX 25
layout (constant_id = 0) const int out_transform = 0;
layout (constant_id = 1) const int in_transform = 0;
@@ -42,8 +44,17 @@ layout (constant_id = 7) const int in_h = 0;
layout (constant_id = 8) const int in_chroma_w = 0;
layout (constant_id = 9) const int in_chroma_h = 0;
layout (set = 0, binding = 0) uniform sampler2D input_img[];
layout (set = 0, binding = 1) uniform writeonly image2D output_img[];
layout (constant_id = 10) const int in_cube_size = 0;
layout (constant_id = 11) const int gopro_cube_width = 0;
layout (constant_id = 12) const int gopro_overlap = 0;
layout (constant_id = 13) const int gopro_face_offset_2 = 0;
layout (constant_id = 14) const int gopro_blend_x_0 = 0;
layout (constant_id = 15) const int gopro_blend_x_1 = 0;
layout (constant_id = 16) const int gopro_blend_x_2 = 0;
layout (set = 0, binding = 0) uniform writeonly image2D output_img[];
layout (set = 0, binding = 1) uniform sampler2D input_img[];
layout (set = 0, binding = 2) uniform sampler2D input2_img[];
layout (push_constant, scalar) uniform pushConstants {
mat4 rot_mat;
@@ -58,8 +69,27 @@ ivec2 in_img_size[4] = {
ivec2(in_w, in_h)
};
ivec3 gopro_face_offset_x = ivec3(0,
gopro_cube_width,
gopro_face_offset_2);
ivec3 gopro_face_size_x = ivec3(gopro_cube_width,
in_cube_size,
gopro_cube_width);
ivec3 gopro_blend_x = ivec3(gopro_blend_x_0,
gopro_blend_x_1,
gopro_blend_x_2);
#define IS_WITHIN(v1, v2) any(lessThan(v1, v2))
float atan2(in float y, in float x)
{
return abs(x) < 1e-6 ? sign(y) * m_pi2 : atan(y, x);
}
vec2 atan2(in vec2 v, in float z)
{
return abs(z) < 1e-6 ? sign(v) * m_pi2 : atan(v, vec2(z));
}
void xyz_to_flat(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
{
const float r = tan(acos(v[2]));
@@ -166,6 +196,133 @@ void xyz_to_dfisheye(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
imageStore(output_img[idx], pos, res);
}
// FIXME: no padding
vec3 eac_to_xyz(in ivec2 out_size, in ivec2 pos)
{
const ivec2 cube_size = out_size / ivec2(3, 2);
const ivec2 face = pos / cube_size;
const vec2 e_uv = vec2(pos % cube_size) / vec2(cube_size);
const int f = face.y * 3 + face.x;
vec3 v;
vec2 uv;
uv.x = (e_uv.x >= 1.0) ? 1.0 : tan((e_uv.x - 0.5) * m_pi2);
uv.y = (e_uv.y >= 1.0) ? 1.0 : tan((e_uv.y - 0.5) * m_pi2);
// limited cube_to_xyz
switch (f) {
case 0:
v = vec3(-1.0, uv.y, uv.x);
break;
case 1:
v = vec3(uv.x, uv.y, 1.0);
break;
case 2:
v = vec3(1.0, uv.y, -uv.x);
break;
case 3:
v = vec3(-uv.y, 1.0, -uv.x);
break;
case 4:
v = vec3(-uv.y, -uv.x, -1.0);
break;
case 5:
v = vec3(-uv.y, -1.0, uv.x);
break;
}
return normalize(v);
}
// face order : LEFT, FRONT, RIGHT, DOWN, BACK, UP
vec3 xzy_to_eac_cube(in vec3 v)
{
vec3 a = abs(v);
int face;
vec3 view;
vec2 uv;
if (a.x >= a.y && a.x >= a.z) {
if (v.x >= 0.0) { // +X
face = 2;
view = vec3(-v.z, v.y, a.x);
}
else { // -X
face = 0;
view = vec3( v.z, v.y, a.x);
}
}
else if (a.y >= a.z) {
if (v.y >= 0.0) { // +Y
face = 3;
view = vec3(-v.z, -v.x, a.y);
}
else { // -Y
face = 5;
view = vec3( v.z, -v.x, a.y);
}
}
else {
if (v.z >= 0.0) { // +Z
face = 1;
view = vec3( v.x, v.y, a.z);
}
else { // -Z
face = 4;
view = vec3(-v.y, -v.x, a.z);
}
}
uv = atan2(view.xy, view.z) / m_pi2 + 0.5;
return vec3(uv.x, uv.y, float(face));
}
void xyz_to_gopro(uint idx, in vec3 v, in ivec2 pos, in ivec2 in_size)
{
vec3 uvf = xzy_to_eac_cube(v);
vec2 uv = vec2(uvf.xy);
ivec2 grid = ivec2(int(uvf.z) % 3, int(uvf.z) / 3);
vec3 face_offset = vec3(gopro_face_offset_x) / float(in_img_size[0].x);
vec2 face_size = vec2(float(gopro_face_size_x[grid.x]) / float(in_img_size[0].x), 1.0);
vec2 uv_start = vec2(face_offset[grid.x], 0.0);
vec2 uv_end = uv_start + face_size;
vec4 res;
if (grid.x == 1) {
res = (grid.y == 0) ? texture(input_img[idx], mix(uv_start, uv_end, uv))
: texture(input2_img[idx], mix(uv_start, uv_end, uv));
} else {
float cube_width = float(gopro_cube_width);
float overlap = float(gopro_overlap);
float gratio = (cube_width - overlap) / cube_width;
float blend_width = overlap / (cube_width - overlap);
vec3 blend_u = vec3(gopro_blend_x) / cube_width;
vec2 uv1 = vec2(uv.x * gratio, uv.y);
vec2 uv2 = vec2((uv.x + blend_width) * gratio, uv.y);
if (uv1.x < blend_u[0]) {
res = (grid.y == 0) ? texture(input_img[idx], mix(uv_start, uv_end, uv1))
: texture(input2_img[idx], mix(uv_start, uv_end, uv1));
} else if (uv2.x > blend_u[2]) {
res = (grid.y == 0) ? texture(input_img[idx], mix(uv_start, uv_end, uv2))
: texture(input2_img[idx], mix(uv_start, uv_end, uv2));
} else {
vec4 res2;
if (grid.y == 0) {
res = texture(input_img[idx], mix(uv_start, uv_end, uv1));
res2 = texture(input_img[idx], mix(uv_start, uv_end, uv2));
}
else {
res = texture(input2_img[idx], mix(uv_start, uv_end, uv1));
res2 = texture(input2_img[idx], mix(uv_start, uv_end, uv2));
}
float a = (uv1.x - blend_u[0]) / (blend_u[1] - blend_u[0]);
res = mix(res, res2, a);
}
}
imageStore(output_img[idx], pos, res);
}
void main()
{
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
@@ -179,6 +336,9 @@ void main()
case EQUIRECTANGULAR:
v = equirect_to_xyz(out_size, pos);
break;
case EQUIANGULAR:
v = eac_to_xyz(out_size, pos);
break;
case FLAT:
v = flat_to_xyz(out_size, pos);
break;
@@ -211,6 +371,9 @@ void main()
case FISHEYE:
xyz_to_fisheye(i, v, pos, in_size);
break;
case GOPROMAX:
xyz_to_gopro(i, v, pos, in_size);
break;
}
}
}