X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=futatabi%2Fvaapi_jpeg_decoder.cpp;h=8fe1ac88f7fc46f4ddb0af10895115bd52a05dd7;hb=8850f9468d6a1727e7be302d2a9b4360f69210cc;hp=2cd2f000aac6f05681f9f2073fef4d0d9d940a5a;hpb=eeda8995329601f9f4e35047358400833eeae68e;p=nageru diff --git a/futatabi/vaapi_jpeg_decoder.cpp b/futatabi/vaapi_jpeg_decoder.cpp index 2cd2f00..8fe1ac8 100644 --- a/futatabi/vaapi_jpeg_decoder.cpp +++ b/futatabi/vaapi_jpeg_decoder.cpp @@ -2,7 +2,11 @@ #include "jpeg_destroyer.h" #include "jpeg_frame.h" +#include "jpeglib_error_wrapper.h" +#include "pbo_pool.h" #include "shared/memcpy_interleaved.h" +#include "shared/va_display.h" +#include "shared/va_resource_pool.h" #include #include @@ -21,33 +25,14 @@ #include #include +#define BUFFER_OFFSET(i) ((char *)nullptr + (i)) + using namespace std; static unique_ptr va_dpy; -static VAConfigID config_id; -static VAImageFormat uyvy_format; -bool vaapi_jpeg_decoding_usable = false; - -struct VAResources { - unsigned width, height; - VASurfaceID surface; - VAContextID context; - VAImage image; -}; -static list va_resources_freelist; -static mutex va_resources_mutex; - -#define CHECK_VASTATUS(va_status, func) \ - if (va_status != VA_STATUS_SUCCESS) { \ - fprintf(stderr, "%s:%d (%s) failed with %d\n", __func__, __LINE__, func, va_status); \ - exit(1); \ - } +static unique_ptr va_pool; -#define CHECK_VASTATUS_RET(va_status, func) \ - if (va_status != VA_STATUS_SUCCESS) { \ - fprintf(stderr, "%s:%d (%s) failed with %d\n", __func__, __LINE__, func, va_status); \ - return nullptr; \ - } +bool vaapi_jpeg_decoding_usable = false; // From libjpeg (although it's of course identical between implementations). static const int jpeg_natural_order[DCTSIZE2] = { @@ -61,163 +46,24 @@ static const int jpeg_natural_order[DCTSIZE2] = { 53, 60, 61, 54, 47, 55, 62, 63, }; -VAResources get_va_resources(unsigned width, unsigned height) -{ - { - lock_guard lock(va_resources_mutex); - for (auto it = va_resources_freelist.begin(); it != va_resources_freelist.end(); ++it) { - if (it->width == width && it->height == height) { - VAResources ret = *it; - va_resources_freelist.erase(it); - return ret; - } - } - } - - VAResources ret; - - ret.width = width; - ret.height = height; - - VAStatus va_status = vaCreateSurfaces(va_dpy->va_dpy, VA_RT_FORMAT_YUV422, - width, height, - &ret.surface, 1, nullptr, 0); - CHECK_VASTATUS(va_status, "vaCreateSurfaces"); - - va_status = vaCreateContext(va_dpy->va_dpy, config_id, width, height, 0, &ret.surface, 1, &ret.context); - CHECK_VASTATUS(va_status, "vaCreateContext"); - - va_status = vaCreateImage(va_dpy->va_dpy, &uyvy_format, width, height, &ret.image); - CHECK_VASTATUS(va_status, "vaCreateImage"); - - return ret; -} - -void release_va_resources(VAResources resources) -{ - lock_guard lock(va_resources_mutex); - if (va_resources_freelist.size() > 10) { - auto it = va_resources_freelist.end(); - --it; - - VAStatus va_status = vaDestroyImage(va_dpy->va_dpy, it->image.image_id); - CHECK_VASTATUS(va_status, "vaDestroyImage"); - - va_status = vaDestroyContext(va_dpy->va_dpy, it->context); - CHECK_VASTATUS(va_status, "vaDestroyContext"); - - va_status = vaDestroySurfaces(va_dpy->va_dpy, &it->surface, 1); - CHECK_VASTATUS(va_status, "vaDestroySurfaces"); - - va_resources_freelist.erase(it); - } - - va_resources_freelist.push_front(resources); -} - -// RAII wrapper to release VAResources on return (even on error). -class ReleaseVAResources { -public: - ReleaseVAResources(const VAResources &resources) - : resources(resources) {} - ~ReleaseVAResources() - { - if (!committed) { - release_va_resources(resources); - } - } - - void commit() { committed = true; } - -private: - const VAResources &resources; - bool committed = false; -}; - -VADisplayWithCleanup::~VADisplayWithCleanup() -{ - if (va_dpy != nullptr) { - vaTerminate(va_dpy); - } - if (x11_display != nullptr) { - XCloseDisplay(x11_display); - } - if (drm_fd != -1) { - close(drm_fd); - } -} - -unique_ptr va_open_display(const string &va_display) -{ - if (va_display.empty() || va_display[0] != '/') { // An X display. - Display *x11_display = XOpenDisplay(va_display.empty() ? nullptr : va_display.c_str()); - if (x11_display == nullptr) { - fprintf(stderr, "error: can't connect to X server!\n"); - return nullptr; - } - - unique_ptr ret(new VADisplayWithCleanup); - ret->x11_display = x11_display; - ret->va_dpy = vaGetDisplay(x11_display); - if (ret->va_dpy == nullptr) { - return nullptr; - } - return ret; - } else { // A DRM node on the filesystem (e.g. /dev/dri/renderD128). - int drm_fd = open(va_display.c_str(), O_RDWR); - if (drm_fd == -1) { - perror(va_display.c_str()); - return nullptr; - } - unique_ptr ret(new VADisplayWithCleanup); - ret->drm_fd = drm_fd; - ret->va_dpy = vaGetDisplayDRM(drm_fd); - if (ret->va_dpy == nullptr) { - return nullptr; - } - return ret; - } -} - -unique_ptr try_open_va(const string &va_display, string *error) +static unique_ptr try_open_va_mjpeg(const string &va_display) { - unique_ptr va_dpy = va_open_display(va_display); + VAConfigID config_id_422, config_id_420; + VAImageFormat uyvy_format, nv12_format; + + // Seemingly VA_FOURCC_422H is no good for vaGetImage(). :-/ + unique_ptr va_dpy = + try_open_va(va_display, { VAProfileJPEGBaseline }, VAEntrypointVLD, + { { "4:2:2", VA_RT_FORMAT_YUV422, VA_FOURCC_UYVY, &config_id_422, &uyvy_format }, + { "4:2:0", VA_RT_FORMAT_YUV420, VA_FOURCC_NV12, &config_id_420, &nv12_format } }, + /*chosen_profile=*/nullptr, /*error=*/nullptr); if (va_dpy == nullptr) { - if (error) - *error = "Opening VA display failed"; - return nullptr; - } - int major_ver, minor_ver; - VAStatus va_status = vaInitialize(va_dpy->va_dpy, &major_ver, &minor_ver); - if (va_status != VA_STATUS_SUCCESS) { - char buf[256]; - snprintf(buf, sizeof(buf), "vaInitialize() failed with status %d\n", va_status); - if (error != nullptr) - *error = buf; - return nullptr; - } - - int num_entrypoints = vaMaxNumEntrypoints(va_dpy->va_dpy); - unique_ptr entrypoints(new VAEntrypoint[num_entrypoints]); - if (entrypoints == nullptr) { - if (error != nullptr) - *error = "Failed to allocate memory for VA entry points"; - return nullptr; - } - - vaQueryConfigEntrypoints(va_dpy->va_dpy, VAProfileJPEGBaseline, entrypoints.get(), &num_entrypoints); - for (int slice_entrypoint = 0; slice_entrypoint < num_entrypoints; slice_entrypoint++) { - if (entrypoints[slice_entrypoint] != VAEntrypointVLD) { - continue; - } - - // We found a usable decode, so return it. return va_dpy; } - if (error != nullptr) - *error = "Can't find VAEntrypointVLD for the JPEG profile"; - return nullptr; + va_pool.reset(new VAResourcePool(va_dpy->va_dpy, uyvy_format, nv12_format, config_id_422, config_id_420, /*with_data_buffer=*/false)); + + return va_dpy; } string get_usable_va_display() @@ -231,7 +77,7 @@ string get_usable_va_display() } // First try the default (ie., whatever $DISPLAY is set to). - unique_ptr va_dpy = try_open_va("", nullptr); + unique_ptr va_dpy = try_open_va_mjpeg(""); if (va_dpy != nullptr) { if (need_env_reset) { unsetenv("LIBVA_MESSAGING_LEVEL"); @@ -245,14 +91,14 @@ string get_usable_va_display() glob_t g; int err = glob("/dev/dri/renderD*", 0, nullptr, &g); if (err != 0) { - fprintf(stderr, "Couldn't list render nodes (%s) when trying to autodetect a replacement.\n", strerror(errno)); + fprintf(stderr, "Couldn't list render nodes (%s) when trying to autodetect a replacement.\n", strerror(errno)); } else { for (size_t i = 0; i < g.gl_pathc; ++i) { string path = g.gl_pathv[i]; - va_dpy = try_open_va(path, nullptr); + va_dpy = try_open_va_mjpeg(path); if (va_dpy != nullptr) { fprintf(stderr, "Autodetected %s as a suitable replacement; using it.\n", - path.c_str()); + path.c_str()); globfree(&g); if (need_env_reset) { unsetenv("LIBVA_MESSAGING_LEVEL"); @@ -276,37 +122,11 @@ void init_jpeg_vaapi() return; } - va_dpy = try_open_va(dpy, nullptr); + va_dpy = try_open_va_mjpeg(dpy); if (va_dpy == nullptr) { return; } - VAConfigAttrib attr = { VAConfigAttribRTFormat, VA_RT_FORMAT_YUV422 }; - - VAStatus va_status = vaCreateConfig(va_dpy->va_dpy, VAProfileJPEGBaseline, VAEntrypointVLD, - &attr, 1, &config_id); - CHECK_VASTATUS(va_status, "vaCreateConfig"); - - int num_formats = vaMaxNumImageFormats(va_dpy->va_dpy); - assert(num_formats > 0); - - unique_ptr formats(new VAImageFormat[num_formats]); - va_status = vaQueryImageFormats(va_dpy->va_dpy, formats.get(), &num_formats); - CHECK_VASTATUS(va_status, "vaQueryImageFormats"); - - bool found = false; - for (int i = 0; i < num_formats; ++i) { - // Seemingly VA_FOURCC_422H is no good for vaGetImage(). :-/ - if (formats[i].fourcc == VA_FOURCC_UYVY) { - memcpy(&uyvy_format, &formats[i], sizeof(VAImageFormat)); - found = true; - break; - } - } - if (!found) { - return; - } - fprintf(stderr, "VA-API JPEG decoding initialized.\n"); vaapi_jpeg_decoding_usable = true; } @@ -316,7 +136,8 @@ public: VABufferDestroyer(VADisplay dpy, VABufferID buf) : dpy(dpy), buf(buf) {} - ~VABufferDestroyer() { + ~VABufferDestroyer() + { VAStatus va_status = vaDestroyBuffer(dpy, buf); CHECK_VASTATUS(va_status, "vaDestroyBuffer"); } @@ -329,20 +150,25 @@ private: shared_ptr decode_jpeg_vaapi(const string &jpeg) { jpeg_decompress_struct dinfo; - jpeg_error_mgr jerr; - dinfo.err = jpeg_std_error(&jerr); - jpeg_create_decompress(&dinfo); + JPEGWrapErrorManager error_mgr(&dinfo); + if (!error_mgr.run([&dinfo] { jpeg_create_decompress(&dinfo); })) { + return nullptr; + } JPEGDestroyer destroy_dinfo(&dinfo); + jpeg_save_markers(&dinfo, JPEG_APP0 + 1, 0xFFFF); + jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size()); - jpeg_read_header(&dinfo, true); + if (!error_mgr.run([&dinfo] { jpeg_read_header(&dinfo, true); })) { + return nullptr; + } if (dinfo.num_components != 3) { fprintf(stderr, "Not a color JPEG. (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n", - dinfo.num_components, - dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor, - dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor, - dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor); + dinfo.num_components, + dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor, + dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor, + dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor); return nullptr; } if (dinfo.comp_info[0].h_samp_factor != 2 || @@ -351,9 +177,9 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) dinfo.comp_info[2].h_samp_factor != 1 || dinfo.comp_info[2].v_samp_factor != dinfo.comp_info[0].v_samp_factor) { fprintf(stderr, "Not 4:2:2. (Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n", - dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor, - dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor, - dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor); + dinfo.comp_info[0].h_samp_factor, dinfo.comp_info[0].v_samp_factor, + dinfo.comp_info[1].h_samp_factor, dinfo.comp_info[1].v_samp_factor, + dinfo.comp_info[2].h_samp_factor, dinfo.comp_info[2].v_samp_factor); return nullptr; } @@ -373,8 +199,11 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) pic_param.color_space = 0; // YUV. pic_param.rotation = VA_ROTATION_NONE; + VAResourcePool::VAResources resources = va_pool->get_va_resources(dinfo.image_width, dinfo.image_height, VA_FOURCC_UYVY); + ReleaseVAResources release(va_pool.get(), resources); + VABufferID pic_param_buffer; - VAStatus va_status = vaCreateBuffer(va_dpy->va_dpy, config_id, VAPictureParameterBufferType, sizeof(pic_param), 1, &pic_param, &pic_param_buffer); + VAStatus va_status = vaCreateBuffer(va_dpy->va_dpy, resources.context, VAPictureParameterBufferType, sizeof(pic_param), 1, &pic_param, &pic_param_buffer); CHECK_VASTATUS_RET(va_status, "vaCreateBuffer"); VABufferDestroyer destroy_pic_param(va_dpy->va_dpy, pic_param_buffer); @@ -399,7 +228,7 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) } VABufferID iq_buffer; - va_status = vaCreateBuffer(va_dpy->va_dpy, config_id, VAIQMatrixBufferType, sizeof(iq), 1, &iq, &iq_buffer); + va_status = vaCreateBuffer(va_dpy->va_dpy, resources.context, VAIQMatrixBufferType, sizeof(iq), 1, &iq, &iq_buffer); CHECK_VASTATUS_RET(va_status, "vaCreateBuffer"); VABufferDestroyer destroy_iq(va_dpy->va_dpy, iq_buffer); @@ -433,7 +262,7 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) } VABufferID huff_buffer; - va_status = vaCreateBuffer(va_dpy->va_dpy, config_id, VAHuffmanTableBufferType, sizeof(huff), 1, &huff, &huff_buffer); + va_status = vaCreateBuffer(va_dpy->va_dpy, resources.context, VAHuffmanTableBufferType, sizeof(huff), 1, &huff, &huff_buffer); CHECK_VASTATUS_RET(va_status, "vaCreateBuffer"); VABufferDestroyer destroy_huff(va_dpy->va_dpy, huff_buffer); @@ -463,19 +292,16 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) parms.num_mcus = horiz_mcus * vert_mcus; VABufferID slice_param_buffer; - va_status = vaCreateBuffer(va_dpy->va_dpy, config_id, VASliceParameterBufferType, sizeof(parms), 1, &parms, &slice_param_buffer); + va_status = vaCreateBuffer(va_dpy->va_dpy, resources.context, VASliceParameterBufferType, sizeof(parms), 1, &parms, &slice_param_buffer); CHECK_VASTATUS_RET(va_status, "vaCreateBuffer"); VABufferDestroyer destroy_slice_param(va_dpy->va_dpy, slice_param_buffer); // The actual data. VA-API will destuff and all for us. VABufferID data_buffer; - va_status = vaCreateBuffer(va_dpy->va_dpy, config_id, VASliceDataBufferType, dinfo.src->bytes_in_buffer, 1, const_cast(dinfo.src->next_input_byte), &data_buffer); + va_status = vaCreateBuffer(va_dpy->va_dpy, resources.context, VASliceDataBufferType, dinfo.src->bytes_in_buffer, 1, const_cast(dinfo.src->next_input_byte), &data_buffer); CHECK_VASTATUS_RET(va_status, "vaCreateBuffer"); VABufferDestroyer destroy_data(va_dpy->va_dpy, data_buffer); - VAResources resources = get_va_resources(dinfo.image_width, dinfo.image_height); - ReleaseVAResources release(resources); - va_status = vaBeginPicture(va_dpy->va_dpy, resources.context, resources.surface); CHECK_VASTATUS_RET(va_status, "vaBeginPicture"); va_status = vaRenderPicture(va_dpy->va_dpy, resources.context, &pic_param_buffer, 1); @@ -542,24 +368,46 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) #else // Convert Y'CbCr to separate Y' and CbCr. frame->is_semiplanar = true; - frame->y.reset(new uint8_t[dinfo.image_width * dinfo.image_height]); - frame->cbcr.reset(new uint8_t[dinfo.image_width * dinfo.image_height]); + + PBO pbo = global_pbo_pool->alloc_pbo(); + size_t cbcr_offset = dinfo.image_width * dinfo.image_height; + uint8_t *y_pix = pbo.ptr; + uint8_t *cbcr_pix = pbo.ptr + cbcr_offset; + const uint8_t *src = (const uint8_t *)mapped + resources.image.offsets[0]; if (resources.image.pitches[0] == dinfo.image_width * 2) { - memcpy_interleaved(frame->cbcr.get(), frame->y.get(), src, dinfo.image_width * dinfo.image_height * 2); + memcpy_interleaved(cbcr_pix, y_pix, src, dinfo.image_width * dinfo.image_height * 2); } else { for (unsigned y = 0; y < dinfo.image_height; ++y) { - memcpy_interleaved(frame->cbcr.get() + y * dinfo.image_width, frame->y.get() + y * dinfo.image_width, + memcpy_interleaved(cbcr_pix + y * dinfo.image_width, y_pix + y * dinfo.image_width, src + y * resources.image.pitches[0], dinfo.image_width * 2); } } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.pbo); + frame->y = create_texture_2d(dinfo.image_width, dinfo.image_height, GL_R8, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); + frame->cbcr = create_texture_2d(dinfo.image_width / 2, dinfo.image_height, GL_RG8, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(cbcr_offset)); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + glFlushMappedNamedBufferRange(pbo.pbo, 0, dinfo.image_width * dinfo.image_height * 2); + glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); + pbo.upload_done = RefCountedGLsync(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0); + frame->uploaded_ui_thread = pbo.upload_done; + frame->uploaded_interpolation = pbo.upload_done; + global_pbo_pool->release_pbo(move(pbo)); #endif frame->width = dinfo.image_width; frame->height = dinfo.image_height; frame->chroma_subsampling_x = 2; frame->chroma_subsampling_y = 1; - frame->pitch_y = dinfo.image_width; - frame->pitch_chroma = dinfo.image_width / 2; + + if (dinfo.marker_list != nullptr && + dinfo.marker_list->marker == JPEG_APP0 + 1 && + dinfo.marker_list->data_length >= 4 && + memcmp(dinfo.marker_list->data, "Exif", 4) == 0) { + frame->exif_data.assign(reinterpret_cast(dinfo.marker_list->data), + dinfo.marker_list->data_length); + } va_status = vaUnmapBuffer(va_dpy->va_dpy, resources.image.buf); CHECK_VASTATUS_RET(va_status, "vaUnmapBuffer");