diff --git a/fftools/ffplay.c b/fftools/ffplay.c
index 3ae9c47509..bd88fed8d6 100644
--- a/fftools/ffplay.c
+++ b/fftools/ffplay.c
@@ -352,6 +352,7 @@ static int autorotate = 1;
 static int find_stream_info = 1;
 static int filter_nbthreads = 0;
 static int enable_vulkan = 0;
+static const char *hwaccel = NULL;
 
 /* current context */
 static int is_full_screen;
@@ -2557,6 +2558,29 @@ static int audio_open(void *opaque, AVChannelLayout *wanted_channel_layout, int
     return spec.size;
 }
 
+static int create_hwaccel(AVBufferRef **device_ctx)
+{
+    enum AVHWDeviceType type;
+    int ret;
+
+    *device_ctx = NULL;
+
+    if (!hwaccel)
+        return 0;
+
+    if (!vk_renderer) {
+        av_log(NULL, AV_LOG_WARNING, "hwaccel only works with -enable_vulkan\n");
+        return 0;
+    }
+
+    type = av_hwdevice_find_type_by_name(hwaccel);
+    if (type == AV_HWDEVICE_TYPE_NONE)
+        return AVERROR(ENOTSUP);
+
+    ret = av_hwdevice_ctx_create(device_ctx, type, NULL, NULL, 0);
+    return ret;
+}
+
 /* open a given stream. Return 0 if OK */
 static int stream_component_open(VideoState *is, int stream_index)
 {
@@ -2624,6 +2648,12 @@ static int stream_component_open(VideoState *is, int stream_index)
 
     av_dict_set(&opts, "flags", "+copy_opaque", AV_DICT_MULTIKEY);
 
+    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {
+        ret = create_hwaccel(&avctx->hw_device_ctx);
+        if (ret < 0)
+            goto fail;
+    }
+
     if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {
         goto fail;
     }
@@ -3623,6 +3653,7 @@ static const OptionDef options[] = {
         "read and decode the streams to fill missing information with heuristics" },
     { "filter_threads", HAS_ARG | OPT_INT | OPT_EXPERT, { &filter_nbthreads }, "number of filter threads per graph" },
     { "enable_vulkan", OPT_BOOL, { &enable_vulkan }, "enable vulkan render" },
+    { "hwaccel", HAS_ARG | OPT_STRING | OPT_EXPERT, { &hwaccel }, "use HW accelerated decoding" },
     { NULL, },
 };
 
diff --git a/fftools/ffplay_renderer.c b/fftools/ffplay_renderer.c
index b03729c064..10de78c32b 100644
--- a/fftools/ffplay_renderer.c
+++ b/fftools/ffplay_renderer.c
@@ -48,6 +48,12 @@ typedef struct RendererContext {
     pl_tex tex[4];
 
     pl_log vk_log;
+
+    AVBufferRef *hw_device;
+    AVBufferRef *hw_frame;
+    int hw_failed;
+
+    AVFrame *vk_frame;
 } RendererContext;
 
 static void vk_log_cb(void *log_priv, enum pl_log_level level, const char *msg) {
@@ -131,6 +137,13 @@ static int create(VkRenderer *renderer, SDL_Window *window)
         ret = AVERROR_EXTERNAL;
         goto out;
     }
+
+    ctx->vk_frame = av_frame_alloc();
+    if (!ctx->vk_frame) {
+        ret = AVERROR(ENOMEM);
+        goto out;
+    }
+
     ret = 0;
 
 out:
@@ -138,12 +151,115 @@ out:
     return ret;
 }
 
+static int create_hw(VkRenderer *renderer, AVFrame *frame)
+{
+    RendererContext *ctx = (RendererContext *)renderer;
+    AVHWFramesContext *src_hw_frame = (AVHWFramesContext *)frame->hw_frames_ctx->data;
+    AVBufferRef *src_dev = src_hw_frame->device_ref;
+    int ret;
+
+    if (ctx->hw_failed)
+        return ctx->hw_failed;
+
+    if (!ctx->hw_device) {
+        ret = av_hwdevice_ctx_create_derived(&ctx->hw_device, AV_HWDEVICE_TYPE_VULKAN, src_dev, 0);
+        if (ret < 0) {
+            av_log(renderer, AV_LOG_ERROR, "Derive hwaccel failed, %s\n", av_err2str(ret));
+            ctx->hw_failed = ret;
+            return ret;
+        }
+    }
+
+    if (!ctx->hw_frame) {
+        AVHWFramesContext *hw_frame;
+
+        ctx->hw_frame = av_hwframe_ctx_alloc(ctx->hw_device);
+        if (!ctx->hw_frame) {
+            ctx->hw_failed = AVERROR(ENOMEM);
+            return AVERROR(ENOMEM);
+        }
+
+        hw_frame = (AVHWFramesContext *)ctx->hw_frame->data;
+        hw_frame->format = AV_PIX_FMT_VULKAN;
+        hw_frame->sw_format = src_hw_frame->sw_format;
+        hw_frame->width = frame->width;
+        hw_frame->height = frame->height;
+
+        ret = av_hwframe_ctx_init(ctx->hw_frame);
+        if (ret < 0) {
+            av_log(renderer, AV_LOG_ERROR, "Create hwframe context failed, %s\n", av_err2str(ret));
+            ctx->hw_failed = ret;
+            return ret;
+        }
+    }
+
+    return 0;
+}
+
+static int transfer_frame(VkRenderer *renderer, AVFrame *frame)
+{
+    RendererContext *ctx = (RendererContext *)renderer;
+    int ret;
+
+    if (!frame->hw_frames_ctx)
+        return 0;
+
+    if (frame->format == AV_PIX_FMT_VULKAN)
+        return 0;
+
+    ret = create_hw(renderer, frame);
+    if (ret < 0) {
+        if (ret != AVERROR(ENOSYS))
+            return ret;
+        // Fallback to slow path
+        goto try_transfer;
+    }
+
+    // Try map data first
+    av_frame_unref(ctx->vk_frame);
+    ctx->vk_frame->hw_frames_ctx = av_buffer_ref(ctx->hw_frame);
+    ret = av_hwframe_map(ctx->vk_frame, frame, AV_HWFRAME_MAP_READ);
+    if (!ret) {
+        goto out;
+    }
+
+    if (ret != AVERROR(ENOSYS)) {
+        av_log(NULL, AV_LOG_FATAL, "Map data to vulkan failed: %s\n", av_err2str(ret));
+        return ret;
+    }
+
+try_transfer:
+    // Try transfer data
+    av_frame_unref(ctx->vk_frame);
+    if (ctx->hw_frame)
+        av_hwframe_get_buffer(ctx->hw_frame, ctx->vk_frame, 0);
+    ret = av_hwframe_transfer_data(ctx->vk_frame, frame, 0);
+    if (ret < 0) {
+        av_log(NULL, AV_LOG_FATAL, "Transfer data to vulkan failed: %s\n", av_err2str(ret));
+        return ret;
+    }
+
+out:
+    ret = av_frame_copy_props(ctx->vk_frame, frame);
+    if (ret < 0)
+        return ret;
+    av_frame_unref(frame);
+    av_frame_move_ref(frame, ctx->vk_frame);
+
+    return 0;
+}
+
 static int display(VkRenderer *renderer, AVFrame *frame)
 {
     struct pl_swapchain_frame swap_frame = {0};
     struct pl_frame pl_frame = {0};
     struct pl_frame target = {0};
     RendererContext *ctx = (RendererContext *)renderer;
+    int ret;
+
+    ret = transfer_frame(renderer, frame);
+    if (ret < 0)
+        return ret;
 
     if (!pl_swapchain_start_frame(ctx->swapchain, &swap_frame)) {
         av_log(NULL, AV_LOG_ERROR, "start frame failed\n");
@@ -179,6 +295,10 @@ static void destroy(VkRenderer *renderer)
     PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR;
     RendererContext *ctx = (RendererContext *)renderer;
 
+    av_buffer_unref(&ctx->hw_frame);
+    av_buffer_unref(&ctx->hw_device);
+    av_frame_free(&ctx->vk_frame);
+
     for (int i = 0; i < FF_ARRAY_ELEMS(ctx->tex); i++)
         pl_tex_destroy(ctx->pl_vk->gpu, &ctx->tex[i]);
     pl_renderer_destroy(&ctx->renderer);
