[FFmpeg-devel] [PATCH 1/2] libavutil/libavfilter: opencl wrapper based on comments on 20130331

Wei Gao highgod0401 at gmail.com
Mon Apr 1 04:32:32 CEST 2013


Hi

Thanks for your reviewing, some explanations as follows.

Thanks
Best regards


2013/4/1 Stefano Sabatini <stefasab at gmail.com>

> On date Sunday 2013-03-31 15:57:21 +0800, Wei Gao encoded:
> >
> > From 545055bbb065fa9421848e6d7368294943793ec1 Mon Sep 17 00:00:00 2001
> > From: highgod0401 <highgod0401 at gmail.com>
> > Date: Sun, 31 Mar 2013 15:48:02 +0800
> > Subject: [PATCH 1/2] opencl wrapper based on comments on 20130331
> >
>
>

> > +typedef struct UserSpecDevInfo {
> > +    int dev_idx;
> > +    int platform_idx;


> as already noted, "device_id" and "platform_id" should be better names
> assuming these fields are the same are related to the corresponding
> ones in GPUEnv.
>

this is different, it is the index, and hte device_id in GPUEnv is getted
from  clGetDeviceIDs.

> +} UserSpecDevInfo;
> > +
>
>
> > +int av_opencl_init(AVDictionary *options, AVOpenCLExternalEnv
> *ext_opencl_info)
> > +{
> > +    int ret = 0;
> > +    AVDictionaryEntry *opt_build_entry;
> > +    AVDictionaryEntry *opt_platform_entry;
> > +    AVDictionaryEntry *opt_device_entry;
> > +    LOCK_OPENCL
> > +    if (!gpu_env.opencl_is_inited) {
> > +        opt_platform_entry = av_dict_get(options, "platform_idx", NULL,
> 0);
> > +        opt_device_entry   = av_dict_get(options, "device_idx", NULL,
> 0);
> > +        /*initialize devices, context, command_queue*/
> > +        gpu_env.usr_spec_dev_info.platform_idx = -1;
> > +        gpu_env.usr_spec_dev_info.dev_idx = -1;
>
> > +        if (opt_platform_entry) {
> > +            gpu_env.usr_spec_dev_info.platform_idx =
> atoi(opt_platform_entry->value);
> > +        }
> > +        if (opt_device_entry) {
> > +            gpu_env.usr_spec_dev_info.dev_idx =
> atoi(opt_device_entry->value);
> > +        }
>
> atoi() is not robust, you need something more robust like strtol() and
> abort in case of invalid string.


gpu_env.usr_spec_dev_info.platform_idx = atoi(opt_platform_entry->value);

>
> Also you may want to mention the supported options in the docs.
>
> > +        ret = init_opencl_env(&gpu_env, ext_opencl_info);
> > +        if (ret < 0)
> > +            goto end;
> > +        gpu_env.opencl_is_inited = 1;
> > +    }
> > +    /*initialize program, kernel_name, kernel_count*/
>
> > +    opt_build_entry = av_dict_get(options, "build_option", NULL, 0);
>
> "build_options", as stated in the docs and as we seemed to agree.
>
> > +    if (opt_build_entry)
> > +        ret = compile_kernel_file(&gpu_env, opt_build_entry->value);
> > +    else
> > +        ret = compile_kernel_file(&gpu_env, NULL);
> > +    if (ret < 0)
> > +        goto end;
> > +    av_assert1(gpu_env.kernel_code_count > 0);
> > +
> > +end:
> > +    UNLOCK_OPENCL
> > +    return ret;
> > +}
> > +
> > +void av_opencl_uninit(void)
> > +{
> > +    cl_int status;
> > +    int i;
> > +    LOCK_OPENCL
> > +    if (!gpu_env.opencl_is_inited)
> > +        goto end;
> > +    if (gpu_env.is_user_created)
> > +        goto end;
> > +    for (i = 0;i < gpu_env.kernel_count;i++) {
> > +        if (gpu_env.kernel_node[i].kernel)
> > +            goto end;
> > +    }
> > +    for (i = 0; i < gpu_env.program_count; i++) {
> > +        if (gpu_env.programs[i]) {
> > +            status = clReleaseProgram(gpu_env.programs[i]);
> > +            if (status != CL_SUCCESS) {
> > +                av_log(&openclutils, AV_LOG_ERROR, "Could not release
> OpenCL program: %s\n", opencl_errstr(status));
> > +            }
> > +            gpu_env.programs[i] = NULL;
> > +        }
> > +    }
> > +    if (gpu_env.command_queue) {
> > +        status = clReleaseCommandQueue(gpu_env.command_queue);
> > +        if (status != CL_SUCCESS) {
> > +            av_log(&openclutils, AV_LOG_ERROR, "Could not release
> OpenCL command queue: %s\n", opencl_errstr(status));
> > +        }
> > +        gpu_env.command_queue = NULL;
> > +    }
> > +    if (gpu_env.context) {
> > +        status = clReleaseContext(gpu_env.context);
> > +        if (status != CL_SUCCESS) {
> > +            av_log(&openclutils, AV_LOG_ERROR, "Could not release
> OpenCL context: %s\n", opencl_errstr(status));
> > +        }
> > +        gpu_env.context = NULL;
> > +    }
> > +    av_freep(&(gpu_env.device_ids));
> > +    gpu_env.opencl_is_inited = 0;
> > +end:
> > +    UNLOCK_OPENCL
> > +}
> > +
> > +int av_opencl_buffer_create(cl_mem *cl_buf, size_t cl_buf_size, int
> flags, void *host_ptr)
> > +{
> > +    cl_int status;
> > +    *cl_buf = clCreateBuffer(gpu_env.context, flags, cl_buf_size,
> host_ptr, &status);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not create OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    return 0;
> > +}
> > +
> > +void av_opencl_buffer_release(cl_mem *cl_buf)
> > +{
> > +    cl_int status = 0;
>
> > +    if (!(*cl_buf))
> > +        return;
>
> if (!cl_buf)
>    return;
>
> or it will crash if cl_buf = NULL.
>
> > +    status = clReleaseMemObject(*cl_buf);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not release OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +    }
>
> > +    *cl_buf = NULL;
>
> I don't think this makes sense.
>
> Maybe you want to set the *pointed to* structure to 0 instead, with:
> memset(cl_buf, 0, sizeof(*cl_buf));
> or maybe:
> *cl_buf = {0};
>
> > +}
> > +
> > +int av_opencl_buffer_write(cl_mem dst_cl_buf, uint8_t *src_buf, size_t
> buf_size)
> > +{
> > +    cl_int status;
> > +    void *mapped = clEnqueueMapBuffer(gpu_env.command_queue, dst_cl_buf,
> > +                                      CL_TRUE,CL_MAP_WRITE, 0,
> sizeof(uint8_t) * buf_size,
> > +                                      0, NULL, NULL, &status);
> > +
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not map OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    memcpy(mapped, src_buf, buf_size);
> > +
> > +    status = clEnqueueUnmapMemObject(gpu_env.command_queue, dst_cl_buf,
> mapped, 0, NULL, NULL);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not unmap OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    return 0;
> > +}
> > +
> > +int av_opencl_buffer_read(uint8_t *dst_buf, cl_mem src_cl_buf, size_t
> buf_size)
> > +{
> > +    cl_int status;
> > +    void *mapped = clEnqueueMapBuffer(gpu_env.command_queue, src_cl_buf,
> > +                                      CL_TRUE,CL_MAP_READ, 0, buf_size,
> > +                                      0, NULL, NULL, &status);
> > +
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not map OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    memcpy(dst_buf, mapped, buf_size);
> > +
> > +    status = clEnqueueUnmapMemObject(gpu_env.command_queue, src_cl_buf,
> mapped, 0, NULL, NULL);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not unmap OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    return 0;
> > +}
> > +
> > +int av_opencl_buffer_write_image(cl_mem dst_cl_buf, size_t
> cl_buffer_size, int dst_cl_offset,
> > +                                                    uint8_t **src_data,
> int *plane_size, int plane_num)
> > +{
> > +    int i, buffer_size = 0;
> > +    uint8_t *temp;
> > +    cl_int status;
> > +    void *mapped;
> > +    if ((unsigned int)plane_num > 8) {
> > +        return AVERROR(EINVAL);
> > +    }
> > +    for (i = 0;i < plane_num;i++) {
> > +        buffer_size += plane_size[i];
> > +    }
> > +    if (buffer_size > cl_buffer_size) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Cannot write image to
> OpenCL buffer: buffer too small\n");
> > +        return AVERROR(EINVAL);
> > +    }
> > +    mapped = clEnqueueMapBuffer(gpu_env.command_queue, dst_cl_buf,
> > +                                      CL_TRUE,CL_MAP_WRITE, 0,
> buffer_size + dst_cl_offset,
> > +                                      0, NULL, NULL, &status);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not map OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    temp = mapped;
> > +    temp += dst_cl_offset;
> > +    for (i = 0; i < plane_num; i++) {
> > +        memcpy(temp, src_data[i], plane_size[i]);
> > +        temp += plane_size[i];
> > +    }
> > +    status = clEnqueueUnmapMemObject(gpu_env.command_queue, dst_cl_buf,
> mapped, 0, NULL, NULL);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not unmap OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    return 0;
> > +}
> > +
> > +int av_opencl_buffer_read_image(uint8_t **dst_data, int *plane_size,
> int plane_num,
> > +                                       cl_mem src_cl_buf, size_t
> cl_buffer_size)
> > +{
> > +    int i,buffer_size = 0,ret = 0;
> > +    uint8_t *temp;
> > +    void *mapped;
> > +    cl_int status;
> > +    if ((unsigned int)plane_num > 8) {
> > +        return AVERROR(EINVAL);
> > +    }
> > +    for (i = 0;i < plane_num;i++) {
> > +        buffer_size += plane_size[i];
> > +    }
> > +    if (buffer_size > cl_buffer_size) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Cannot write image to CPU
> buffer: OpenCL buffer too small\n");
> > +        return AVERROR(EINVAL);
> > +    }
> > +    mapped = clEnqueueMapBuffer(gpu_env.command_queue, src_cl_buf,
> > +                                      CL_TRUE,CL_MAP_READ, 0,
> buffer_size,
> > +                                      0, NULL, NULL, &status);
> > +
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not map OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    temp = mapped;
> > +    if (ret >= 0) {
> > +        for (i = 0;i < plane_num;i++) {
> > +            memcpy(dst_data[i], temp, plane_size[i]);
> > +            temp += plane_size[i];
> > +        }
> > +    }
> > +    status = clEnqueueUnmapMemObject(gpu_env.command_queue, src_cl_buf,
> mapped, 0, NULL, NULL);
> > +    if (status != CL_SUCCESS) {
> > +        av_log(&openclutils, AV_LOG_ERROR, "Could not unmap OpenCL
> buffer: %s\n", opencl_errstr(status));
> > +        return AVERROR_EXTERNAL;
> > +    }
> > +    return 0;
> > +}
> > +
> > diff --git a/libavutil/opencl.h b/libavutil/opencl.h
> > new file mode 100644
> > index 0000000..d43e509
> > --- /dev/null
> > +++ b/libavutil/opencl.h
> > @@ -0,0 +1,185 @@
> > +/*
> > + * Copyright (C) 2012 Peng Gao <peng at multicorewareinc.com>
> > + * Copyright (C) 2012 Li   Cao <li at multicorewareinc.com>
> > + * Copyright (C) 2012 Wei  Gao <weigao at multicorewareinc.com>
> > + *
> > + * This file is part of FFmpeg.
> > + *
> > + * FFmpeg 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.
> > + *
> > + * FFmpeg 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 FFmpeg; if not, write to the Free Software
> > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> 02110-1301 USA
> > + */
> > +
>
> Add something like this:
>
> /**
>  * @file
>  * OpenCL wrapper
>  *
>  * This interface is considered still experimental and its API and ABI may
>  * change without prior notice.
>  */
>
> > +#include "config.h"
> > +#include "dict.h"
>
> I think you can move these includes after the include guards.
>
> > +
> > +#ifndef LIBAVUTIL_OPENCLWRAPPER_H
> > +#define LIBAVUTIL_OPENCLWRAPPER_H
> > +
> > +#include <CL/cl.h>
> > +
> > +#define AV_OPENCL_KERNEL( ... )# __VA_ARGS__
> > +
> > +#define AV_OPENCL_MAX_KERNEL_NAME_LEN 150
> > +
> > +typedef struct AVOpenCLKernelEnv {
> > +    cl_command_queue command_queue;
> > +    cl_kernel kernel;
>
> > +    char kernel_name[AV_OPENCL_MAX_KERNEL_NAME_LEN];
>
> char kernel_name[AV_OPENCL_MAX_KERNEL_NAME_LEN+1];
>
> > +} AVOpenCLKernelEnv;
> > +
> > +typedef struct AVOpenCLExternalEnv {
> > +    cl_platform_id platform;
> > +    cl_device_type device_type;
> > +    cl_context context;
> > +    cl_device_id *device_ids;
> > +    cl_device_id  device_id;
> > +    cl_command_queue command_queue;
> > +    char *platform_name;
> > +} AVOpenCLExternalEnv;
> > +
>
> > +/**
> > + * Alloc OpenCL external environment space.
> > + *
> > + * @return  pointer of OpenCL external environment space
> > + */
> > +AVOpenCLExternalEnv *av_opencl_alloc_external_environment(void);
>
> /**
>  * Allocate OpenCL external environment.
>  *
>  * It must be freed with av_opencl_free_external_environment().
>  *
>  * @return pointer to allocated OpenCL external environment
>  */
> AVOpenCLExternalEnv *av_opencl_alloc_external_environment(void);
>
> Nit: for consistency with the struct name, this could be:
> av_opencl_alloc_external_env()
>
> or you could adapt the struct name instead:
> AVOpenCLExternalEnvironment
>
> but in that case you should also rename
> AVOpenCLKernelEnvironment
>
> I'm fine with both solutions, as long as they are consistent.
>
> > +
> > +/**
> > + * Free OpenCL external environment space.
> > + *
> > + * @param ext_opencl_env    pointer of OpenCL external environment
> space created by av_opencl_malloc_external_environment
> > + */
> > +void av_opencl_free_external_environment(AVOpenCLExternalEnv
> *ext_opencl_env);
>
> /**
>  * Free OpenCL external environment.
>  *
>  * @param ext_opencl_env pointer to OpenCL external environment created by
> av_opencl_malloc_external_environment()
>  */
> void av_opencl_free_external_environment(AVOpenCLExternalEnv
> *ext_opencl_env);
>
>
> > +
> > +/**
> > + * Register kernel code.
> > + *
> > + *  The registered kernel code is stored in a global context, and
> compiled
> > + *  in the runtime environment when av_opencl_init() is called.
> > + *
> > + * @param kernel_code    kernel code to be compiled in the OpenCL
> runtime environment
> > + * @return  >=0 on success, a negative error code in case of failure
> > + */
> > +int av_opencl_register_kernel_code(const char *kernel_code);
> > +
>
> > +/**
> > + * Initialize the run time OpenCL environment and compile the kernel
> code registered by function
> > + * av_opencl_register_kernel_code
>
> ... registered with av_opencl_register_kernel_code().
>
> > + *
> > + * Currently, the only accepted option is "build_options", used to set
> > + * options to compile registered kernels code. See reference "OpenCL
> > + * Specification Version: 1.2 chapter 5.6.4".
> > + *
> > + * @param options                  dictionary of key/value options
> > + * @param ext_opencl_env      external OpenCL environment, created by an
> > + *                                          application program,
> ignored if set to NULL
>
> > + * @return  >=0 on success, a negative error value on failure
>
> a negative _error code_ on failure
>
> > + */
> > + int av_opencl_init(AVDictionary *options, AVOpenCLExternalEnv
> *ext_opencl_env);
> > +
>
> > +/**
> > + * Create kernel object in the specified kernel environment.
> > + *
> > + * @param env                  kernel environment which is filled with
> the environment,
> > + *                                    used to run the kernel.
>
> @param env                  pointer to kernel environment which is filled
> with the environment,
>                             used to run the kernel
>
> > + * @param kernel_name      kernel function name
> > + * @return >=0 on success, a negative error code on failure
> > + */
> > +int av_opencl_create_kernel(AVOpenCLKernelEnv *env, const char
> *kernel_name);
> > +
>
> > +/**
> > + *  Create OpenCL buffer, the buffer is used to save the data which is
> used or created by OpenCL kernel.
> > + *
> > + * @param cl_buf         the pointer of OpenCL buffer, call
> av_opencl_buffer_release to release the buffer.
> > + * @param cl_buf_size  size in bytes of the OpenCL buffer to create
> > + * @param flags           the flags which used to control buffer
> attributes
> > + * @param host_ptr      the host pointer of OpenCL buffer
> > + * @return  >=0 on success, a negative error code in case of failure
> > + */
> > +int av_opencl_buffer_create(cl_mem *cl_buf, size_t cl_buf_size, int
> flags, void *host_ptr);
>
> /**
>  * Create OpenCL buffer.
>  *
>  * The buffer is used to save the data used or created by an OpenCL
>  * kernel.
>  * The created buffer must be released with av_opencl_buffer_release().
>  *
>  * See clCreateBuffer() function reference for more information about
>  * the parameters.
>  *
>  * @param cl_buf       pointer to OpenCL buffer
>  * @param cl_buf_size  size in bytes of the OpenCL buffer to create
>  * @param flags        flags used to control buffer attributes
>  * @param host_ptr     host pointer of the OpenCL buffer
>  * @return >=0 on success, a negative error code in case of failure
>  */
> int av_opencl_buffer_create(cl_mem *cl_buf, size_t cl_buf_size, int flags,
> void *host_ptr);
>
>
> > +
> > +/**
> > + * Write OpenCL buffer with data from src_buf.
> > + *
> > + * @param dst_cl_buf        pointer to OpenCL destination buffer
> > + * @param src_buf           pointer to source buffer
> > + * @param buf_size          size in bytes of the source and destination
> buffers
> > + * @return >=0 on success, a negative error code in case of failure
> > + */
> > +int av_opencl_buffer_write(cl_mem dst_cl_buf, uint8_t *src_buf, size_t
> buf_size);
> > +
> > +/**
> > + * Read data from OpenCL buffer to memory buffer.
> > + *
> > + * @param dst_buf           pointer to destination buffer (CPU memory)
> > + * @param src_cl_buf        pointer to source OpenCL buffer
> > + * @param buf_size          size in bytes of the source and destination
> buffers
> > + * @return >=0 on success, a negative error code in case of failure
> > + */
> > +int av_opencl_buffer_read(uint8_t *dst_buf, cl_mem src_cl_buf, size_t
> buf_size);
> > +
> > +/**
> > + * Write image data from memory to OpenCL buffer.
> > + *
> > + * The source must be an array of pointers to image plane buffers.
> > + *
> > + * @param dst_cl_buf         pointer to destination OpenCL buffer
> > + * @param dst_cl_buf_size    size in bytes of OpenCL buffer
> > + * @param dst_cl_buf_offset  the offset of the OpenCL buffer start
> position
> > + * @param src_data           array of pointers to source plane buffers
> > + * @param src_plane_sizes    array of sizes in bytes of the source
> plane buffers
> > + * @param src_plane_num      number of source image planes
> > + * @return >=0 on success, a negative error code in case of failure
> > + */
>
> > +int av_opencl_buffer_write_image(cl_mem dst_cl_buf, size_t
> cl_buffer_size, int dst_cl_offset,
> > +                                                       uint8_t
> **src_data, int *plane_size, int plane_num);
>
> Nit: weird indent:
>
> int av_opencl_buffer_write_image(cl_mem dst_cl_buf, size_t cl_buffer_size,
> int dst_cl_offset,
>                                  uint8_t **src_data, int *plane_size, int
> plane_num);
>
> > +/**
> > + * Read image data from OpenCL buffer.
> > + *
> > + * src buffer is OpenCL buffer, dst buffer is frame
> buffer(data[0],data[1]....).
> > + *
> > + * @param dst_data           array of pointers to destination plane
> buffers
> > + * @param dst_plane_sizes    array of pointers to destination plane
> buffers
> > + * @param dst_plane_num      number of destination image planes
> > + * @param src_cl_buf         pointer to source OpenCL buffer
> > + * @param src_cl_buf_size    size in bytes of OpenCL buffer
> > + * @return >=0 on success, a negative error code in case of failure
> > + */
> > +
>
> > +int av_opencl_buffer_read_image(uint8_t **dst_data, int *plane_size,
> int plane_num,
> > +                                       cl_mem src_cl_buf, size_t
> cl_buffer_size);
>
> Nit: weird indent
>
> > +/**
> > + * Release OpenCL buffer.
> > + *
>
> > + * @param cl_buf  pointer of OpenCL buffer to release, which was
> previously filled with av_opencl_buffer_create()
>
> pointer *to* OpenCL buffer to release, ...
>
> > + */
> > +void av_opencl_buffer_release(cl_mem *cl_buf);
> > +
> > +/**
> > + *  Release kernel object.
> > + *
> > + * @param env  kernel environment where the kernel object was created
> with av_opencl_create_kernel
>
> nit: av_opencl_create_kernel() so doxygen will auto-create a link to
> the function.
>
> > + */
> > +void av_opencl_release_kernel(AVOpenCLKernelEnv *env);
> > +
> > +/**
> > + *  Release OpenCL environment.
> > + *
> > + * The OpenCL environment is effectively released only if all the
> created
> > + * kernels had been released with av_opencl_release_kernel().
> > + */
> > +void av_opencl_uninit(void);
> > +
> > +#endif/*LIBAVUTIL_OPENCL_H*/
> > +
> > --
> > 1.7.11.msysgit.1
> >
> --
> FFmpeg = Fiendish and Frenzy Magical Puritan Ecumenical Gargoyle
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>


More information about the ffmpeg-devel mailing list