st/omx/tizonia/h264d: Add EGLImage support
Example Gstreamer pipeline : MESA_ENABLE_OMX_EGLIMAGE=1 GST_GL_API=gles2 GST_GL_PLATFORM=egl gst-launch-1.0 filesrc location=movie.mp4 ! qtdemux ! h264parse ! omxh264dec ! glimagesink Acked-by: Leo Liu <leo.liu@amd.com> Reviewed-by: Julien Isorce <julien.isorce@gmail.com>
This commit is contained in:
parent
b2f2236dc5
commit
c62cf1f165
|
@ -37,6 +37,7 @@ SUBDIRS = tizonia
|
||||||
|
|
||||||
AM_CFLAGS = \
|
AM_CFLAGS = \
|
||||||
$(GALLIUM_CFLAGS) \
|
$(GALLIUM_CFLAGS) \
|
||||||
|
$(LIBDRM_CFLAGS) \
|
||||||
$(VISIBILITY_CFLAGS) \
|
$(VISIBILITY_CFLAGS) \
|
||||||
$(VL_CFLAGS) \
|
$(VL_CFLAGS) \
|
||||||
$(XCB_DRI3_CFLAGS) \
|
$(XCB_DRI3_CFLAGS) \
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
|
inc_st_omx = [inc_common]
|
||||||
|
dep_st_omx = [dep_omx, dep_x11_xcb, dep_xcb, dep_xcb_dri2, dep_xcb_dri3]
|
||||||
|
|
||||||
files_omx = files(
|
files_omx = files(
|
||||||
'vid_dec_common.c',
|
'vid_dec_common.c',
|
||||||
'vid_dec_h264_common.c',
|
'vid_dec_h264_common.c',
|
||||||
|
@ -45,12 +48,23 @@ files_omx += files(
|
||||||
'tizonia/h264einport.c',
|
'tizonia/h264einport.c',
|
||||||
'tizonia/h264eoutport.c'
|
'tizonia/h264eoutport.c'
|
||||||
)
|
)
|
||||||
|
inc_st_omx = [
|
||||||
|
inc_st_omx,
|
||||||
|
inc_dri_common,
|
||||||
|
include_directories('../../state_trackers/dri'),
|
||||||
|
include_directories('../../../egl/drivers/dri2'),
|
||||||
|
include_directories('../../../egl/main'),
|
||||||
|
include_directories('../../../gbm/backends/dri'),
|
||||||
|
include_directories('../../../gbm/main'),
|
||||||
|
include_directories('../../../loader')
|
||||||
|
]
|
||||||
|
dep_st_omx = [dep_st_omx, dep_omx_other, dep_libdrm]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
libomx_st = static_library(
|
libomx_st = static_library(
|
||||||
'omx_st',
|
'omx_st',
|
||||||
files_omx,
|
files_omx,
|
||||||
c_args : [c_vis_args],
|
c_args : [c_vis_args],
|
||||||
include_directories : [inc_common],
|
include_directories : inc_st_omx,
|
||||||
dependencies : [dep_omx, dep_omx_other, dep_x11_xcb, dep_xcb, dep_xcb_dri2, dep_xcb_dri3],
|
dependencies : dep_st_omx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,8 +22,21 @@ include Makefile.sources
|
||||||
include $(top_srcdir)/src/gallium/Automake.inc
|
include $(top_srcdir)/src/gallium/Automake.inc
|
||||||
|
|
||||||
AM_CFLAGS = \
|
AM_CFLAGS = \
|
||||||
|
-I$(top_srcdir)/include \
|
||||||
|
-I$(top_srcdir)/src/mapi \
|
||||||
|
-I$(top_srcdir)/src/mesa \
|
||||||
|
-I$(top_builddir)/src/mesa/drivers/dri/common \
|
||||||
|
-I$(top_srcdir)/src/mesa/drivers/dri/common \
|
||||||
|
-I$(top_srcdir)/src/egl/drivers/dri2 \
|
||||||
|
-I$(top_srcdir)/src/egl/wayland/wayland-egl \
|
||||||
|
-I$(top_srcdir)/src/egl/main \
|
||||||
|
-I$(top_srcdir)/src/gbm/main \
|
||||||
|
-I$(top_srcdir)/src/loader \
|
||||||
|
-I$(top_srcdir)/src/gbm/backends/dri \
|
||||||
|
-I$(top_srcdir)/src/gallium/state_trackers/dri \
|
||||||
-I$(top_srcdir)/src/gallium/state_trackers/omx \
|
-I$(top_srcdir)/src/gallium/state_trackers/omx \
|
||||||
$(GALLIUM_CFLAGS) \
|
$(GALLIUM_CFLAGS) \
|
||||||
|
$(LIBDRM_CFLAGS) \
|
||||||
$(VISIBILITY_CFLAGS) \
|
$(VISIBILITY_CFLAGS) \
|
||||||
$(VL_CFLAGS) \
|
$(VL_CFLAGS) \
|
||||||
$(XCB_DRI3_CFLAGS) \
|
$(XCB_DRI3_CFLAGS) \
|
||||||
|
|
|
@ -44,6 +44,40 @@
|
||||||
#include "h264eoutport.h"
|
#include "h264eoutport.h"
|
||||||
#include "names.h"
|
#include "names.h"
|
||||||
|
|
||||||
|
#include "util/u_debug.h"
|
||||||
|
|
||||||
|
DEBUG_GET_ONCE_BOOL_OPTION(mesa_enable_omx_eglimage,
|
||||||
|
"MESA_ENABLE_OMX_EGLIMAGE",
|
||||||
|
false)
|
||||||
|
|
||||||
|
static OMX_BOOL egl_image_validation_hook(const OMX_HANDLETYPE ap_hdl,
|
||||||
|
OMX_U32 pid, OMX_PTR ap_eglimage,
|
||||||
|
void *ap_args)
|
||||||
|
{
|
||||||
|
const void * p_krn = NULL;
|
||||||
|
const tiz_port_t * p_port = NULL;
|
||||||
|
|
||||||
|
assert(ap_hdl);
|
||||||
|
assert(ap_eglimage);
|
||||||
|
assert(!ap_args);
|
||||||
|
|
||||||
|
if (!debug_get_option_mesa_enable_omx_eglimage()) {
|
||||||
|
return OMX_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_krn = tiz_get_krn(ap_hdl);
|
||||||
|
p_port = tiz_krn_get_port(p_krn, pid);
|
||||||
|
|
||||||
|
const OMX_VIDEO_PORTDEFINITIONTYPE * p_video_portdef
|
||||||
|
= &(p_port->portdef_.format.video);
|
||||||
|
|
||||||
|
if (!p_video_portdef->pNativeWindow) {
|
||||||
|
return OMX_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OMX_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE ap_hdl)
|
OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE ap_hdl)
|
||||||
{
|
{
|
||||||
tiz_role_factory_t h264d_role;
|
tiz_role_factory_t h264d_role;
|
||||||
|
@ -59,6 +93,11 @@ OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE ap_hdl)
|
||||||
&h264eprc_type,
|
&h264eprc_type,
|
||||||
&h264e_inport_type,
|
&h264e_inport_type,
|
||||||
&h264e_outport_type};
|
&h264e_outport_type};
|
||||||
|
const tiz_eglimage_hook_t egl_validation_hook = {
|
||||||
|
OMX_VID_DEC_AVC_OUTPUT_PORT_INDEX,
|
||||||
|
egl_image_validation_hook,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
/* Settings for roles */
|
/* Settings for roles */
|
||||||
strcpy ((OMX_STRING) h264d_role.role, OMX_VID_DEC_AVC_ROLE);
|
strcpy ((OMX_STRING) h264d_role.role, OMX_VID_DEC_AVC_ROLE);
|
||||||
|
@ -110,5 +149,10 @@ OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE ap_hdl)
|
||||||
/* Register the component roles */
|
/* Register the component roles */
|
||||||
tiz_comp_register_roles (ap_hdl, rf_list, 2);
|
tiz_comp_register_roles (ap_hdl, rf_list, 2);
|
||||||
|
|
||||||
|
/* Register egl image validation hook for the decoder */
|
||||||
|
tiz_check_omx (tiz_comp_register_role_eglimage_hook
|
||||||
|
(ap_hdl, (const OMX_U8 *) OMX_VID_DEC_AVC_ROLE,
|
||||||
|
&egl_validation_hook));
|
||||||
|
|
||||||
return OMX_ErrorNone;
|
return OMX_ErrorNone;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,33 @@
|
||||||
|
|
||||||
#include "vl/vl_video_buffer.h"
|
#include "vl/vl_video_buffer.h"
|
||||||
#include "vl/vl_compositor.h"
|
#include "vl/vl_compositor.h"
|
||||||
|
#include "util/u_hash_table.h"
|
||||||
#include "util/u_surface.h"
|
#include "util/u_surface.h"
|
||||||
|
|
||||||
|
#include "dri_screen.h"
|
||||||
|
#include "egl_dri2.h"
|
||||||
|
|
||||||
unsigned dec_frame_delta;
|
unsigned dec_frame_delta;
|
||||||
|
|
||||||
|
#define PTR_TO_UINT(x) ((unsigned)((intptr_t)(x)))
|
||||||
|
|
||||||
|
static unsigned handle_hash(void *key)
|
||||||
|
{
|
||||||
|
return PTR_TO_UINT(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_compare(void *key1, void *key2)
|
||||||
|
{
|
||||||
|
return PTR_TO_UINT(key1) != PTR_TO_UINT(key2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum pipe_error hash_table_clear_item_callback(void *key, void *value, void *data)
|
||||||
|
{
|
||||||
|
struct pipe_video_buffer *video_buffer = (struct pipe_video_buffer *)value;
|
||||||
|
video_buffer->destroy(video_buffer);
|
||||||
|
return PIPE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
static void release_input_headers(vid_dec_PrivateType* priv) {
|
static void release_input_headers(vid_dec_PrivateType* priv) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < priv->num_in_buffers; i++) {
|
for (i = 0; i < priv->num_in_buffers; i++) {
|
||||||
|
@ -156,6 +179,66 @@ static OMX_BUFFERHEADERTYPE * get_input_buffer(vid_dec_PrivateType* priv) {
|
||||||
return priv->p_inhdr_;
|
return priv->p_inhdr_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct pipe_resource * st_omx_pipe_texture_from_eglimage(EGLDisplay egldisplay,
|
||||||
|
EGLImage eglimage)
|
||||||
|
{
|
||||||
|
_EGLDisplay *disp = egldisplay;
|
||||||
|
struct dri2_egl_display *dri2_egl_dpy = disp->DriverData;
|
||||||
|
__DRIscreen *_dri_screen = dri2_egl_dpy->dri_screen;
|
||||||
|
struct dri_screen *st_dri_screen = dri_screen(_dri_screen);
|
||||||
|
__DRIimage *_dri_image = st_dri_screen->lookup_egl_image(st_dri_screen, eglimage);
|
||||||
|
|
||||||
|
return _dri_image->texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void get_eglimage(vid_dec_PrivateType* priv) {
|
||||||
|
OMX_PTR p_eglimage = NULL;
|
||||||
|
OMX_NATIVE_WINDOWTYPE * p_egldisplay = NULL;
|
||||||
|
const tiz_port_t * p_port = NULL;
|
||||||
|
struct pipe_video_buffer templat = {};
|
||||||
|
struct pipe_video_buffer *video_buffer = NULL;
|
||||||
|
struct pipe_resource * p_res = NULL;
|
||||||
|
struct pipe_resource *resources[VL_NUM_COMPONENTS];
|
||||||
|
|
||||||
|
if (OMX_ErrorNone ==
|
||||||
|
tiz_krn_claim_eglimage(tiz_get_krn (handleOf (priv)),
|
||||||
|
OMX_VID_DEC_AVC_OUTPUT_PORT_INDEX,
|
||||||
|
priv->p_outhdr_, &p_eglimage)) {
|
||||||
|
priv->use_eglimage = true;
|
||||||
|
p_port = tiz_krn_get_port(tiz_get_krn (handleOf (priv)),
|
||||||
|
OMX_VID_DEC_AVC_OUTPUT_PORT_INDEX);
|
||||||
|
p_egldisplay = p_port->portdef_.format.video.pNativeWindow;
|
||||||
|
|
||||||
|
if (!util_hash_table_get(priv->video_buffer_map, priv->p_outhdr_)) {
|
||||||
|
p_res = st_omx_pipe_texture_from_eglimage(p_egldisplay, p_eglimage);
|
||||||
|
|
||||||
|
assert(p_res);
|
||||||
|
|
||||||
|
memset(&templat, 0, sizeof(templat));
|
||||||
|
templat.buffer_format = p_res->format;
|
||||||
|
templat.chroma_format = PIPE_VIDEO_CHROMA_FORMAT_NONE;
|
||||||
|
templat.width = p_res->width0;
|
||||||
|
templat.height = p_res->height0;
|
||||||
|
templat.interlaced = 0;
|
||||||
|
|
||||||
|
memset(resources, 0, sizeof(resources));
|
||||||
|
pipe_resource_reference(&resources[0], p_res);
|
||||||
|
|
||||||
|
video_buffer = vl_video_buffer_create_ex2(priv->pipe, &templat, resources);
|
||||||
|
|
||||||
|
assert(video_buffer);
|
||||||
|
assert(video_buffer->buffer_format == p_res->format);
|
||||||
|
|
||||||
|
util_hash_table_set(priv->video_buffer_map, priv->p_outhdr_, video_buffer);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(void) tiz_krn_release_buffer(tiz_get_krn (handleOf (priv)),
|
||||||
|
OMX_VID_DEC_AVC_OUTPUT_PORT_INDEX,
|
||||||
|
priv->p_outhdr_);
|
||||||
|
priv->p_outhdr_ = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static OMX_BUFFERHEADERTYPE * get_output_buffer(vid_dec_PrivateType* priv) {
|
static OMX_BUFFERHEADERTYPE * get_output_buffer(vid_dec_PrivateType* priv) {
|
||||||
assert (priv);
|
assert (priv);
|
||||||
|
|
||||||
|
@ -164,9 +247,17 @@ static OMX_BUFFERHEADERTYPE * get_output_buffer(vid_dec_PrivateType* priv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!priv->p_outhdr_) {
|
if (!priv->p_outhdr_) {
|
||||||
tiz_krn_claim_buffer(tiz_get_krn (handleOf (priv)),
|
if (OMX_ErrorNone
|
||||||
|
== tiz_krn_claim_buffer(tiz_get_krn (handleOf (priv)),
|
||||||
OMX_VID_DEC_AVC_OUTPUT_PORT_INDEX, 0,
|
OMX_VID_DEC_AVC_OUTPUT_PORT_INDEX, 0,
|
||||||
&priv->p_outhdr_);
|
&priv->p_outhdr_)) {
|
||||||
|
if (priv->p_outhdr_) {
|
||||||
|
/* Check pBuffer nullity to know if an eglimage has been registered. */
|
||||||
|
if (!priv->p_outhdr_->pBuffer) {
|
||||||
|
get_eglimage(priv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return priv->p_outhdr_;
|
return priv->p_outhdr_;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +290,7 @@ static void h264d_manage_buffers(vid_dec_PrivateType* priv) {
|
||||||
|
|
||||||
/* Realase output buffer if filled or eos
|
/* Realase output buffer if filled or eos
|
||||||
Keep if two input buffers are being decoded */
|
Keep if two input buffers are being decoded */
|
||||||
if ((!next_is_eos) && ((priv->p_outhdr_->nFilledLen > 0) || priv->eos_)) {
|
if ((!next_is_eos) && ((priv->p_outhdr_->nFilledLen > 0) || priv->use_eglimage || priv->eos_)) {
|
||||||
h264d_buffer_filled(priv, priv->p_outhdr_);
|
h264d_buffer_filled(priv, priv->p_outhdr_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +398,7 @@ static OMX_ERRORTYPE h264d_prc_allocate_resources(void *ap_obj, OMX_U32 a_pid)
|
||||||
{
|
{
|
||||||
vid_dec_PrivateType*priv = ap_obj;
|
vid_dec_PrivateType*priv = ap_obj;
|
||||||
struct pipe_screen *screen;
|
struct pipe_screen *screen;
|
||||||
|
vl_csc_matrix csc;
|
||||||
|
|
||||||
assert (priv);
|
assert (priv);
|
||||||
|
|
||||||
|
@ -332,8 +424,18 @@ static OMX_ERRORTYPE h264d_prc_allocate_resources(void *ap_obj, OMX_U32 a_pid)
|
||||||
return OMX_ErrorInsufficientResources;
|
return OMX_ErrorInsufficientResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vl_csc_get_matrix(VL_CSC_COLOR_STANDARD_BT_601, NULL, true, &csc);
|
||||||
|
if (!vl_compositor_set_csc_matrix(&priv->cstate, (const vl_csc_matrix *)&csc, 1.0f, 0.0f)) {
|
||||||
|
vl_compositor_cleanup(&priv->compositor);
|
||||||
|
priv->pipe->destroy(priv->pipe);
|
||||||
|
priv->pipe = NULL;
|
||||||
|
return OMX_ErrorInsufficientResources;
|
||||||
|
}
|
||||||
|
|
||||||
LIST_INITHEAD(&priv->codec_data.h264.dpb_list);
|
LIST_INITHEAD(&priv->codec_data.h264.dpb_list);
|
||||||
|
|
||||||
|
priv->video_buffer_map = util_hash_table_create(handle_hash, handle_compare);
|
||||||
|
|
||||||
return OMX_ErrorNone;
|
return OMX_ErrorNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,6 +444,12 @@ static OMX_ERRORTYPE h264d_prc_deallocate_resources(void *ap_obj)
|
||||||
vid_dec_PrivateType*priv = ap_obj;
|
vid_dec_PrivateType*priv = ap_obj;
|
||||||
assert(priv);
|
assert(priv);
|
||||||
|
|
||||||
|
/* Clear hash table */
|
||||||
|
util_hash_table_foreach(priv->video_buffer_map,
|
||||||
|
&hash_table_clear_item_callback,
|
||||||
|
NULL);
|
||||||
|
util_hash_table_destroy(priv->video_buffer_map);
|
||||||
|
|
||||||
if (priv->pipe) {
|
if (priv->pipe) {
|
||||||
vl_compositor_cleanup_state(&priv->cstate);
|
vl_compositor_cleanup_state(&priv->cstate);
|
||||||
vl_compositor_cleanup(&priv->compositor);
|
vl_compositor_cleanup(&priv->compositor);
|
||||||
|
|
|
@ -90,6 +90,44 @@ void vid_dec_FillOutput(vid_dec_PrivateType *priv, struct pipe_video_buffer *buf
|
||||||
|
|
||||||
views = buf->get_sampler_view_planes(buf);
|
views = buf->get_sampler_view_planes(buf);
|
||||||
|
|
||||||
|
#if ENABLE_ST_OMX_TIZONIA
|
||||||
|
if (!output->pBuffer) {
|
||||||
|
struct pipe_video_buffer *dst_buf = NULL;
|
||||||
|
struct pipe_surface **dst_surface = NULL;
|
||||||
|
struct u_rect src_rect;
|
||||||
|
struct u_rect dst_rect;
|
||||||
|
struct vl_compositor *compositor = &priv->compositor;
|
||||||
|
struct vl_compositor_state *s = &priv->cstate;
|
||||||
|
enum vl_compositor_deinterlace deinterlace = VL_COMPOSITOR_WEAVE;
|
||||||
|
|
||||||
|
dst_buf = util_hash_table_get(priv->video_buffer_map, output);
|
||||||
|
assert(dst_buf);
|
||||||
|
|
||||||
|
dst_surface = dst_buf->get_surfaces(dst_buf);
|
||||||
|
assert(views);
|
||||||
|
|
||||||
|
src_rect.x0 = 0;
|
||||||
|
src_rect.y0 = 0;
|
||||||
|
src_rect.x1 = def->nFrameWidth;
|
||||||
|
src_rect.y1 = def->nFrameHeight;
|
||||||
|
|
||||||
|
dst_rect.x0 = 0;
|
||||||
|
dst_rect.y0 = 0;
|
||||||
|
dst_rect.x1 = def->nFrameWidth;
|
||||||
|
dst_rect.y1 = def->nFrameHeight;
|
||||||
|
|
||||||
|
vl_compositor_clear_layers(s);
|
||||||
|
vl_compositor_set_buffer_layer(s, compositor, 0, buf,
|
||||||
|
&src_rect, NULL, deinterlace);
|
||||||
|
vl_compositor_set_layer_dst_area(s, 0, &dst_rect);
|
||||||
|
vl_compositor_render(s, compositor, dst_surface[0], NULL, false);
|
||||||
|
|
||||||
|
priv->pipe->flush(priv->pipe, NULL, 0);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
for (i = 0; i < 2 /* NV12 */; i++) {
|
for (i = 0; i < 2 /* NV12 */; i++) {
|
||||||
if (!views[i]) continue;
|
if (!views[i]) continue;
|
||||||
width = def->nFrameWidth;
|
width = def->nFrameWidth;
|
||||||
|
|
|
@ -115,6 +115,7 @@ ENDCLASS(vid_dec_PrivateType)
|
||||||
#include <tizport_decls.h>
|
#include <tizport_decls.h>
|
||||||
|
|
||||||
#include "util/list.h"
|
#include "util/list.h"
|
||||||
|
#include "util/u_hash_table.h"
|
||||||
|
|
||||||
#include "pipe/p_video_state.h"
|
#include "pipe/p_video_state.h"
|
||||||
|
|
||||||
|
@ -154,6 +155,7 @@ struct h264d_prc
|
||||||
struct pipe_video_codec *codec;
|
struct pipe_video_codec *codec;
|
||||||
struct pipe_video_buffer *target;
|
struct pipe_video_buffer *target;
|
||||||
enum pipe_video_profile profile;
|
enum pipe_video_profile profile;
|
||||||
|
struct util_hash_table *video_buffer_map;
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
unsigned nal_ref_idc;
|
unsigned nal_ref_idc;
|
||||||
|
@ -184,6 +186,7 @@ struct h264d_prc
|
||||||
bool disable_tunnel;
|
bool disable_tunnel;
|
||||||
struct vl_compositor compositor;
|
struct vl_compositor compositor;
|
||||||
struct vl_compositor_state cstate;
|
struct vl_compositor_state cstate;
|
||||||
|
bool use_eglimage;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue