Skip to content

Commit

Permalink
vo_vulkan: initial implementation
Browse files Browse the repository at this point in the history
This time based on RA. 2017 is the year of the vulkan desktop!

Current problems / limitations / improvement opportunities:

1. The entire thing depends on VK_NV_glsl_shader, which is a god-awful
   nvidia-exclusive hack that barely works and is held together with
   duct tape and prayers. Long-term, we really, REALLY need to figure
   out a way to use a GLSL->SPIR-V middleware like glslang. The problem
   with glslang in particular is that it's a gigantic pile of awful, but
   maybe time will help here..

2. We don't use async transfer at all. This is very difficult, but
   doable in theory with the newer design. Would require refactoring
   vk_cmdpool slightly, and also expanding ra_vk.active_cmd to include
   commands on the async queue as well. Also, async compute is pretty
   much impossible to benefit from because we need to pingpong with
   serial dependencies anyway. (Sorry AMD users, you fell for the async
   compute meme)

3. The custom memory allocator is pretty naive. It's prone to
   under-allocating memory, allocation thrashing, freeing slabs too
   aggressively, and general slowness due to allocating from the same
   thread. In addition to making it smarter, we should also make it
   multi-threaded: ideally it would free slabs from a different thread,
   and also pre-allocate slabs from a different thread if it reaches
   some critical "low" threshold on the amount of available bytes.
   (Perhaps relative to the current heap size). These limitations
   manifest themselves as occasional choppy performance when changing
   the window size.

4. The swapchain code and ANGLE's swapchain code could share common
   options somehow. Left away for now because I don't want to deal with
   that headache for the time being.

5. The swapchain/flipping code violates the vulkan spec, by assuming
   that the presentation queue will be bounded (in cases where rendering
   is significantly faster than vsync). But apparently, there's simply
   no better way to do this right now, to the point where even the
   stupid cube.c examples from LunarG etc. do it wrong.
   (cf. KhronosGroup/Vulkan-Docs#370)
  • Loading branch information
haasn committed Sep 13, 2017
1 parent 2f41b83 commit c2d769a
Show file tree
Hide file tree
Showing 15 changed files with 3,559 additions and 5 deletions.
9 changes: 5 additions & 4 deletions video/out/opengl/ra.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ enum ra_buf_type {
RA_BUF_TYPE_TEX_UPLOAD, // texture upload buffer (pixel buffer object)
RA_BUF_TYPE_SHADER_STORAGE, // shader buffer (SSBO), for RA_VARTYPE_BUF_RW
RA_BUF_TYPE_UNIFORM, // uniform buffer (UBO), for RA_VARTYPE_BUF_RO
RA_BUF_TYPE_VERTEX, // not publicly usable (RA-internal usage)
};

struct ra_buf_params {
Expand Down Expand Up @@ -369,10 +370,10 @@ struct ra_fns {

void (*buf_destroy)(struct ra *ra, struct ra_buf *buf);

// Update the contents of a buffer, starting at a given offset and up to a
// given size, with the contents of *data. This is an extremely common
// operation. Calling this while the buffer is considered "in use" is an
// error. (See: buf_poll)
// Update the contents of a buffer, starting at a given offset (*must* be a
// multiple of 4) and up to a given size, with the contents of *data. This
// is an extremely common operation. Calling this while the buffer is
// considered "in use" is an error. (See: buf_poll)
void (*buf_update)(struct ra *ra, struct ra_buf *buf, ptrdiff_t offset,
const void *data, size_t size);

Expand Down
3 changes: 2 additions & 1 deletion video/out/opengl/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ static bool ra_buf_pool_grow(struct ra *ra, struct ra_buf_pool *pool)
return false;

MP_TARRAY_INSERT_AT(NULL, pool->buffers, pool->num_buffers, pool->index, buf);
MP_VERBOSE(ra, "Resized buffer pool to size %d\n", pool->num_buffers);
MP_VERBOSE(ra, "Resized buffer pool of type %u to size %d\n",
pool->current_params.type, pool->num_buffers);
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions video/out/vo.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ extern const struct vo_driver video_out_drm;
extern const struct vo_driver video_out_direct3d;
extern const struct vo_driver video_out_sdl;
extern const struct vo_driver video_out_vaapi;
extern const struct vo_driver video_out_vulkan;
extern const struct vo_driver video_out_wayland;
extern const struct vo_driver video_out_rpi;
extern const struct vo_driver video_out_tct;
Expand All @@ -78,6 +79,9 @@ const struct vo_driver *const video_out_drivers[] =
#if HAVE_DIRECT3D
&video_out_direct3d,
#endif
#if HAVE_VULKAN
&video_out_vulkan,
#endif
#if HAVE_WAYLAND
&video_out_wayland,
#endif
Expand Down
335 changes: 335 additions & 0 deletions video/out/vo_vulkan.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
/*
* This file is part of mpv.
*
* mpv 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.
*
* mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
*/

#include "mpv_talloc.h"
#include "options/m_config.h"
#include "osdep/timer.h"
#include "video/mp_image.h"
#include "video/out/x11_common.h"
#include "vo.h"
#include "sub/osd.h"

#include "opengl/ra.h"
#include "opengl/video.h"

#include "vulkan/common.h"
#include "vulkan/utils.h"
#include "vulkan/ra_vk.h"

struct vo_vulkan_opts {
int debug; // whether to load the validation layers or not
int allow_sw; // whether to allow software devices
char *device; // force a specific GPU
int swsize; // swapchain size
int swdepth; // swapchain depth
};

struct vk_priv {
struct vo *vo;
struct mp_log *log;

struct vo_vulkan_opts opts;

struct mpvk_ctx vk;
struct ra *ra;
struct gl_video *renderer;

struct vk_swchain swchain;
int frames_in_flight;
};

static bool resize(struct vo *vo)
{
struct vk_priv *p = vo->priv;

MP_VERBOSE(vo, "Resize: %dx%d\n", vo->dwidth, vo->dheight);

if (!vk_swchain_resize(&p->swchain, vo->dwidth, vo->dheight)) {
MP_ERR(vo, "Failed resizing swapchain!\n");
return false;
}

struct mp_rect src, dst;
struct mp_osd_res osd;
vo_get_src_dst_rects(vo, &src, &dst, &osd);

gl_video_resize(p->renderer, &src, &dst, &osd);

vo->want_redraw = true;
return true;
}

static int reconfig(struct vo *vo, struct mp_image_params *params)
{
struct vk_priv *p = vo->priv;

if (vo->x11)
vo_x11_config_vo_window(vo);

if (!resize(vo))
return VO_ERROR;

gl_video_config(p->renderer, params);

return 0;
}

static void uninit(struct vo *vo)
{
struct vk_priv *p = vo->priv;
struct mpvk_ctx *vk = &p->vk;

gl_video_uninit(p->renderer);

if (p->ra) {
vk_swchain_uninit(p->ra, &p->swchain);
p->ra->fns->destroy(p->ra);
}

// Clean up platform-specific windowing stuff. Do this first to prevent
// keeping around the window for long, then we can uninit the device etc.
// afterwards
if (vo->x11)
vo_x11_uninit(vo);

mpvk_uninit(vk);
}

static int preinit(struct vo *vo)
{
struct vk_priv *p = vo->priv;
struct mpvk_ctx *vk = &p->vk;
p->vo = vo;
p->log = vk->log = vo->log;

if (!mpvk_instance_init(vk, p->opts.debug))
goto error;
if (!mpvk_surface_init(vo, vk))
goto error;
if (!mpvk_find_phys_device(vk, p->opts.device, p->opts.allow_sw))
goto error;
if (!mpvk_pick_surface_format(vk))
goto error;
if (!mpvk_device_init(vk))
goto error;
p->ra = ra_create_vk(vk, p->log);
if (!p->ra)
goto error;
if (!vk_swchain_init(vk, p->ra, p->opts.swsize, &p->swchain))
goto error;

p->renderer = gl_video_init(p->ra, vo->log, vo->global);
gl_video_set_osd_source(p->renderer, vo->osd);
gl_video_configure_queue(p->renderer, vo);

return 0;

error:
uninit(vo);
return -1;
}

static int control(struct vo *vo, uint32_t request, void *data)
{
struct vk_priv *p = vo->priv;

switch (request) {
case VOCTRL_SET_PANSCAN:
return resize(vo) ? VO_TRUE : VO_ERROR;
case VOCTRL_SET_EQUALIZER:
vo->want_redraw = true;
return VO_TRUE;
case VOCTRL_UPDATE_RENDER_OPTS: {
gl_video_update_options(p->renderer);
gl_video_configure_queue(p->renderer, p->vo);
p->vo->want_redraw = true;
return true;
}
case VOCTRL_RESET:
gl_video_reset(p->renderer);
return true;
case VOCTRL_PAUSE:
if (gl_video_showing_interpolated_frame(p->renderer))
vo->want_redraw = true;
return true;
case VOCTRL_PERFORMANCE_DATA:
gl_video_perfdata(p->renderer, (struct voctrl_performance_data *)data);
return true;
}

int events = 0, r = 0;

if (vo->x11)
r |= vo_x11_control(vo, &events, request, data);

if (events & VO_EVENT_RESIZE)
r |= resize(vo) ? 0 : VO_ERROR;

if (events & VO_EVENT_EXPOSE)
vo->want_redraw = true;

vo_event(vo, events);
return r;
}

static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct vk_priv *p = vo->priv;
struct vk_swimg swimg;
if (!vk_swchain_get(&p->swchain, &swimg))
goto error;

struct fbodst target = {
.tex = swimg.image,
.flip = false,
};

gl_video_render_frame(p->renderer, frame, target);
if (!ra_vk_present_frame(p->ra, &swimg, &p->frames_in_flight)) {
MP_ERR(vo, "Failed presenting frame!\n");
goto error;
}

error:
return;
}

static void flip_page(struct vo *vo)
{
struct vk_priv *p = vo->priv;
while (p->frames_in_flight >= p->opts.swdepth)
mpvk_poll_cmds(&p->vk, p->vk.pool, UINT64_MAX);
}

static int query_format(struct vo *vo, int format)
{
struct vk_priv *p = vo->priv;
if (!gl_video_check_format(p->renderer, format))
return 0;
return 1;
}

static void wakeup(struct vo *vo)
{
if (vo->x11)
vo_x11_wakeup(vo);
}

static void wait_events(struct vo *vo, int64_t until_time_us)
{
if (vo->x11) {
vo_x11_wait_events(vo, until_time_us);
} else {
vo_wait_default(vo, until_time_us);
}
}

static struct mp_image *get_image(struct vo *vo, int imgfmt, int w, int h,
int stride_align)
{
struct vk_priv *p = vo->priv;
return gl_video_get_image(p->renderer, imgfmt, w, h, stride_align);
}

static int vk_validate_dev(struct mp_log *log, const struct m_option *opt,
struct bstr name, struct bstr param)
{
int ret = M_OPT_INVALID;
VkResult res;

// Create a dummy instance to validate/list the devices
VkInstanceCreateInfo info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
};

VkInstance inst;
VkPhysicalDevice *devices = NULL;
uint32_t num = 0;

res = vkCreateInstance(&info, MPVK_ALLOCATOR, &inst);
if (res != VK_SUCCESS)
goto error;

res = vkEnumeratePhysicalDevices(inst, &num, NULL);
if (res != VK_SUCCESS)
goto error;

devices = talloc_array(NULL, VkPhysicalDevice, num);
vkEnumeratePhysicalDevices(inst, &num, devices);
if (res != VK_SUCCESS)
goto error;

bool help = bstr_equals0(param, "help");
if (help) {
mp_info(log, "Available vulkan devices:\n");
ret = M_OPT_EXIT;
}

for (int i = 0; i < num; i++) {
VkPhysicalDeviceProperties prop;
vkGetPhysicalDeviceProperties(devices[i], &prop);

if (help) {
mp_info(log, " '%s' (GPU %d, ID %x:%x)\n", prop.deviceName, i,
prop.vendorID, prop.deviceID);
} else if (bstr_equals0(param, prop.deviceName)) {
ret = 0;
break;
}
}

if (!help)
mp_err(log, "No device with name '%.*s'!\n", BSTR_P(param));

error:
talloc_free(devices);
return ret;
}

#define OPT_BASE_STRUCT struct vk_priv

const struct vo_driver video_out_vulkan = {
.description = "Vulkan Renderer",
.name = "vulkan",
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
.get_image = get_image,
.draw_frame = draw_frame,
.flip_page = flip_page,
.wait_events = wait_events,
.wakeup = wakeup,
.uninit = uninit,
.priv_size = sizeof(struct vk_priv),
.options = (const m_option_t[]) {
OPT_FLAG("vulkan-debug", opts.debug, 0),
OPT_FLAG("vulkan-sw", opts.allow_sw, 0),
OPT_STRING_VALIDATE("vulkan-device", opts.device, 0, vk_validate_dev),
OPT_INTRANGE("vulkan-swapchain-size", opts.swsize, 0, 1,
MPVK_MAX_STREAMING_DEPTH),
OPT_INTRANGE("vulkan-swapchain-depth", opts.swdepth, 0, 1,
MPVK_MAX_STREAMING_DEPTH),
{0}
},
.priv_defaults = &(const struct vk_priv) {
.opts = {
.swsize = 8,
.swdepth = 1,
},
},
};
Loading

0 comments on commit c2d769a

Please sign in to comment.