/************************************************************************** * * Copyright 2010 Thomas Balling Sørensen & Orasanu Lucian. * Copyright 2014 Advanced Micro Devices, Inc. * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * **************************************************************************/ #include "pipe/p_screen.h" #include "frontend/drm_driver.h" #include "util/u_memory.h" #include "util/u_handle_table.h" #include "util/u_transfer.h" #include "vl/vl_winsys.h" #include "va_private.h" #ifdef _WIN32 #include #endif #ifndef VA_MAPBUFFER_FLAG_DEFAULT #define VA_MAPBUFFER_FLAG_DEFAULT 0 #define VA_MAPBUFFER_FLAG_READ 1 #define VA_MAPBUFFER_FLAG_WRITE 2 #endif VAStatus vlVaCreateBuffer(VADriverContextP ctx, VAContextID context, VABufferType type, unsigned int size, unsigned int num_elements, void *data, VABufferID *buf_id) { vlVaDriver *drv; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; buf = CALLOC(1, sizeof(vlVaBuffer)); if (!buf) return VA_STATUS_ERROR_ALLOCATION_FAILED; buf->type = type; buf->size = size; buf->num_elements = num_elements; if (buf->type == VAEncCodedBufferType) buf->data = CALLOC(1, sizeof(VACodedBufferSegment)); else buf->data = MALLOC(size * num_elements); if (!buf->data) { FREE(buf); return VA_STATUS_ERROR_ALLOCATION_FAILED; } if (data) memcpy(buf->data, data, size * num_elements); drv = VL_VA_DRIVER(ctx); mtx_lock(&drv->mutex); *buf_id = handle_table_add(drv->htab, buf); mtx_unlock(&drv->mutex); return VA_STATUS_SUCCESS; } VAStatus vlVaBufferSetNumElements(VADriverContextP ctx, VABufferID buf_id, unsigned int num_elements) { vlVaDriver *drv; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); mtx_unlock(&drv->mutex); if (!buf) return VA_STATUS_ERROR_INVALID_BUFFER; if (buf->derived_surface.resource) return VA_STATUS_ERROR_INVALID_BUFFER; buf->data = REALLOC(buf->data, buf->size * buf->num_elements, buf->size * num_elements); buf->num_elements = num_elements; if (!buf->data) return VA_STATUS_ERROR_ALLOCATION_FAILED; return VA_STATUS_SUCCESS; } VAStatus vlVaMapBuffer(VADriverContextP ctx, VABufferID buf_id, void **pbuff) { return vlVaMapBuffer2(ctx, buf_id, pbuff, VA_MAPBUFFER_FLAG_DEFAULT); } VAStatus vlVaMapBuffer2(VADriverContextP ctx, VABufferID buf_id, void **pbuff, uint32_t flags) { vlVaDriver *drv; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); if (!drv) return VA_STATUS_ERROR_INVALID_CONTEXT; if (!pbuff) return VA_STATUS_ERROR_INVALID_PARAMETER; mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); if (!buf || buf->export_refcount > 0) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_BUFFER; } if (buf->derived_surface.resource) { struct pipe_resource *resource; struct pipe_box box; unsigned usage = 0; void *(*map_func)(struct pipe_context *, struct pipe_resource *resource, unsigned level, unsigned usage, /* a combination of PIPE_MAP_x */ const struct pipe_box *, struct pipe_transfer **out_transfer); memset(&box, 0, sizeof(box)); resource = buf->derived_surface.resource; box.width = resource->width0; box.height = resource->height0; box.depth = resource->depth0; if (resource->target == PIPE_BUFFER) map_func = drv->pipe->buffer_map; else map_func = drv->pipe->texture_map; if (flags == VA_MAPBUFFER_FLAG_DEFAULT) { /* For VAImageBufferType, use PIPE_MAP_WRITE for now, * PIPE_MAP_READ_WRITE degradate perf with two copies when map/unmap. */ if (buf->type == VAEncCodedBufferType) usage = PIPE_MAP_READ; else usage = PIPE_MAP_WRITE; /* Map decoder and postproc surfaces also for reading. */ if (buf->derived_surface.entrypoint == PIPE_VIDEO_ENTRYPOINT_BITSTREAM || buf->derived_surface.entrypoint == PIPE_VIDEO_ENTRYPOINT_PROCESSING) usage |= PIPE_MAP_READ; } if (flags & VA_MAPBUFFER_FLAG_READ) usage |= PIPE_MAP_READ; if (flags & VA_MAPBUFFER_FLAG_WRITE) usage |= PIPE_MAP_WRITE; assert(usage); *pbuff = map_func(drv->pipe, resource, 0, usage, &box, &buf->derived_surface.transfer); mtx_unlock(&drv->mutex); if (!buf->derived_surface.transfer || !*pbuff) return VA_STATUS_ERROR_INVALID_BUFFER; if (buf->type == VAEncCodedBufferType) { VACodedBufferSegment* curr_buf_ptr = (VACodedBufferSegment*) buf->data; if ((buf->extended_metadata.present_metadata & PIPE_VIDEO_FEEDBACK_METADATA_TYPE_ENCODE_RESULT) && (buf->extended_metadata.encode_result & PIPE_VIDEO_FEEDBACK_METADATA_ENCODE_FLAG_FAILED)) { curr_buf_ptr->status = VA_CODED_BUF_STATUS_BAD_BITSTREAM; return VA_STATUS_ERROR_OPERATION_FAILED; } curr_buf_ptr->status = (buf->extended_metadata.average_frame_qp & VA_CODED_BUF_STATUS_PICTURE_AVE_QP_MASK); if (buf->extended_metadata.encode_result & PIPE_VIDEO_FEEDBACK_METADATA_ENCODE_FLAG_MAX_FRAME_SIZE_OVERFLOW) curr_buf_ptr->status |= VA_CODED_BUF_STATUS_FRAME_SIZE_OVERFLOW; if ((buf->extended_metadata.present_metadata & PIPE_VIDEO_FEEDBACK_METADATA_TYPE_CODEC_UNIT_LOCATION) == 0) { curr_buf_ptr->buf = *pbuff; curr_buf_ptr->size = buf->coded_size; *pbuff = buf->data; } else { uint8_t* compressed_bitstream_data = *pbuff; *pbuff = buf->data; for (size_t i = 0; i < buf->extended_metadata.codec_unit_metadata_count - 1; i++) { if (!curr_buf_ptr->next) curr_buf_ptr->next = CALLOC(1, sizeof(VACodedBufferSegment)); if (!curr_buf_ptr->next) return VA_STATUS_ERROR_ALLOCATION_FAILED; curr_buf_ptr = curr_buf_ptr->next; } if (curr_buf_ptr->next) { VACodedBufferSegment *node = curr_buf_ptr->next; while (node) { VACodedBufferSegment *next = node->next; FREE(node); node = next; } } curr_buf_ptr->next = NULL; curr_buf_ptr = buf->data; for (size_t i = 0; i < buf->extended_metadata.codec_unit_metadata_count; i++) { curr_buf_ptr->size = buf->extended_metadata.codec_unit_metadata[i].size; curr_buf_ptr->buf = compressed_bitstream_data + buf->extended_metadata.codec_unit_metadata[i].offset; if (buf->extended_metadata.codec_unit_metadata[i].flags & PIPE_VIDEO_CODEC_UNIT_LOCATION_FLAG_MAX_SLICE_SIZE_OVERFLOW) curr_buf_ptr->status |= VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK; if (buf->extended_metadata.codec_unit_metadata[i].flags & PIPE_VIDEO_CODEC_UNIT_LOCATION_FLAG_SINGLE_NALU) curr_buf_ptr->status |= VA_CODED_BUF_STATUS_SINGLE_NALU; curr_buf_ptr = curr_buf_ptr->next; } } } } else { mtx_unlock(&drv->mutex); *pbuff = buf->data; } return VA_STATUS_SUCCESS; } VAStatus vlVaUnmapBuffer(VADriverContextP ctx, VABufferID buf_id) { vlVaDriver *drv; vlVaBuffer *buf; struct pipe_resource *resource; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); if (!drv) return VA_STATUS_ERROR_INVALID_CONTEXT; mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); if (!buf || buf->export_refcount > 0) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_BUFFER; } resource = buf->derived_surface.resource; if (resource) { void (*unmap_func)(struct pipe_context *pipe, struct pipe_transfer *transfer); if (!buf->derived_surface.transfer) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_BUFFER; } if (resource->target == PIPE_BUFFER) unmap_func = pipe_buffer_unmap; else unmap_func = pipe_texture_unmap; unmap_func(drv->pipe, buf->derived_surface.transfer); buf->derived_surface.transfer = NULL; if (buf->type == VAImageBufferType) drv->pipe->flush(drv->pipe, NULL, 0); } mtx_unlock(&drv->mutex); return VA_STATUS_SUCCESS; } VAStatus vlVaDestroyBuffer(VADriverContextP ctx, VABufferID buf_id) { vlVaDriver *drv; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); if (!buf) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_BUFFER; } if (buf->derived_surface.resource) { pipe_resource_reference(&buf->derived_surface.resource, NULL); if (buf->derived_image_buffer) buf->derived_image_buffer->destroy(buf->derived_image_buffer); } if (buf->type == VAEncCodedBufferType) { VACodedBufferSegment* node = buf->data; while (node) { VACodedBufferSegment* next = (VACodedBufferSegment*) node->next; FREE(node); node = next; } } else { FREE(buf->data); } FREE(buf); handle_table_remove(VL_VA_DRIVER(ctx)->htab, buf_id); mtx_unlock(&drv->mutex); return VA_STATUS_SUCCESS; } VAStatus vlVaBufferInfo(VADriverContextP ctx, VABufferID buf_id, VABufferType *type, unsigned int *size, unsigned int *num_elements) { vlVaDriver *drv; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); mtx_unlock(&drv->mutex); if (!buf) return VA_STATUS_ERROR_INVALID_BUFFER; *type = buf->type; *size = buf->size; *num_elements = buf->num_elements; return VA_STATUS_SUCCESS; } VAStatus vlVaAcquireBufferHandle(VADriverContextP ctx, VABufferID buf_id, VABufferInfo *out_buf_info) { vlVaDriver *drv; uint32_t i; uint32_t mem_type; vlVaBuffer *buf ; struct pipe_screen *screen; /* List of supported memory types, in preferred order. */ static const uint32_t mem_types[] = { #ifdef _WIN32 VA_SURFACE_ATTRIB_MEM_TYPE_NTHANDLE, VA_SURFACE_ATTRIB_MEM_TYPE_D3D12_RESOURCE, #else VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME, #endif 0 }; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); screen = VL_VA_PSCREEN(ctx); mtx_lock(&drv->mutex); buf = handle_table_get(VL_VA_DRIVER(ctx)->htab, buf_id); mtx_unlock(&drv->mutex); if (!buf) return VA_STATUS_ERROR_INVALID_BUFFER; /* Only VA surface|image like buffers are supported for now .*/ if (buf->type != VAImageBufferType) return VA_STATUS_ERROR_UNSUPPORTED_BUFFERTYPE; if (!out_buf_info) return VA_STATUS_ERROR_INVALID_PARAMETER; if (!out_buf_info->mem_type) mem_type = mem_types[0]; else { mem_type = 0; for (i = 0; mem_types[i] != 0; i++) { if (out_buf_info->mem_type & mem_types[i]) { mem_type = out_buf_info->mem_type; break; } } if (!mem_type) return VA_STATUS_ERROR_UNSUPPORTED_MEMORY_TYPE; } if (!buf->derived_surface.resource) return VA_STATUS_ERROR_INVALID_BUFFER; if (buf->export_refcount > 0) { if (buf->export_state.mem_type != mem_type) return VA_STATUS_ERROR_INVALID_PARAMETER; } else { VABufferInfo * const buf_info = &buf->export_state; switch (mem_type) { #ifdef _WIN32 case VA_SURFACE_ATTRIB_MEM_TYPE_D3D12_RESOURCE: case VA_SURFACE_ATTRIB_MEM_TYPE_NTHANDLE: #else case VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME: #endif { struct winsys_handle whandle; mtx_lock(&drv->mutex); drv->pipe->flush(drv->pipe, NULL, 0); memset(&whandle, 0, sizeof(whandle)); whandle.type = WINSYS_HANDLE_TYPE_FD; #ifdef _WIN32 if (mem_type == VA_SURFACE_ATTRIB_MEM_TYPE_D3D12_RESOURCE) whandle.type = WINSYS_HANDLE_TYPE_D3D12_RES; #endif if (!screen->resource_get_handle(screen, drv->pipe, buf->derived_surface.resource, &whandle, PIPE_HANDLE_USAGE_FRAMEBUFFER_WRITE)) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_BUFFER; } mtx_unlock(&drv->mutex); buf_info->handle = (intptr_t)whandle.handle; #ifdef _WIN32 if (mem_type == VA_SURFACE_ATTRIB_MEM_TYPE_D3D12_RESOURCE) buf_info->handle = (intptr_t)whandle.com_obj; #endif break; } default: return VA_STATUS_ERROR_UNSUPPORTED_MEMORY_TYPE; } buf_info->type = buf->type; buf_info->mem_type = mem_type; buf_info->mem_size = buf->num_elements * buf->size; } buf->export_refcount++; *out_buf_info = buf->export_state; return VA_STATUS_SUCCESS; } VAStatus vlVaReleaseBufferHandle(VADriverContextP ctx, VABufferID buf_id) { vlVaDriver *drv; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); mtx_unlock(&drv->mutex); if (!buf) return VA_STATUS_ERROR_INVALID_BUFFER; if (buf->export_refcount == 0) return VA_STATUS_ERROR_INVALID_BUFFER; if (--buf->export_refcount == 0) { VABufferInfo * const buf_info = &buf->export_state; switch (buf_info->mem_type) { #ifdef _WIN32 case VA_SURFACE_ATTRIB_MEM_TYPE_D3D12_RESOURCE: // Do nothing for this case. break; case VA_SURFACE_ATTRIB_MEM_TYPE_NTHANDLE: CloseHandle((HANDLE) buf_info->handle); break; #else case VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME: close((intptr_t)buf_info->handle); break; #endif default: return VA_STATUS_ERROR_INVALID_BUFFER; } buf_info->mem_type = 0; } return VA_STATUS_SUCCESS; } #if VA_CHECK_VERSION(1, 15, 0) VAStatus vlVaSyncBuffer(VADriverContextP ctx, VABufferID buf_id, uint64_t timeout_ns) { vlVaDriver *drv; vlVaContext *context; vlVaBuffer *buf; if (!ctx) return VA_STATUS_ERROR_INVALID_CONTEXT; drv = VL_VA_DRIVER(ctx); if (!drv) return VA_STATUS_ERROR_INVALID_CONTEXT; /* Some apps like ffmpeg check for vaSyncBuffer to be present to do async enqueuing of multiple vaEndPicture encode calls before calling vaSyncBuffer with a pre-defined latency If vaSyncBuffer is not implemented, they fallback to the usual synchronous pairs of { vaEndPicture + vaSyncSurface } As this might require the driver to support multiple operations and/or store multiple feedback values before sync fallback to backward compatible behaviour unless driver explicitly supports PIPE_VIDEO_CAP_ENC_SUPPORTS_ASYNC_OPERATION */ if (!drv->pipe->screen->get_video_param(drv->pipe->screen, PIPE_VIDEO_PROFILE_UNKNOWN, PIPE_VIDEO_ENTRYPOINT_ENCODE, PIPE_VIDEO_CAP_ENC_SUPPORTS_ASYNC_OPERATION)) return VA_STATUS_ERROR_UNIMPLEMENTED; mtx_lock(&drv->mutex); buf = handle_table_get(drv->htab, buf_id); if (!buf) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_BUFFER; } if (!buf->feedback) { /* No outstanding operation: nothing to do. */ mtx_unlock(&drv->mutex); return VA_STATUS_SUCCESS; } context = handle_table_get(drv->htab, buf->ctx); if (!context) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_INVALID_CONTEXT; } vlVaSurface* surf = handle_table_get(drv->htab, buf->associated_encode_input_surf); if ((buf->feedback) && (context->decoder->entrypoint == PIPE_VIDEO_ENTRYPOINT_ENCODE)) { if (surf && context->decoder->get_feedback_fence && !context->decoder->get_feedback_fence(context->decoder, surf->fence, timeout_ns)) { mtx_unlock(&drv->mutex); return VA_STATUS_ERROR_TIMEDOUT; } context->decoder->get_feedback(context->decoder, buf->feedback, &(buf->coded_size), &(buf->extended_metadata)); buf->feedback = NULL; /* Also mark the associated render target (encode source texture) surface as done in case they call vaSyncSurface on it to avoid getting the feedback twice*/ if(surf) { surf->feedback = NULL; buf->associated_encode_input_surf = VA_INVALID_ID; } } mtx_unlock(&drv->mutex); return VA_STATUS_SUCCESS; } #endif