From dc4790e08ba8bd239e499227881ba8b7c95c127e Mon Sep 17 00:00:00 2001 From: Jean-Philippe Andre Date: Thu, 17 Dec 2015 18:04:06 +0900 Subject: [PATCH] Ector GL: Add skeletton for Evas.Ector.GL.Image.Buffer This is an ector buffer backed by an existing Evas_GL_Image --- src/Makefile_Evas.am | 28 ++++- src/lib/ector/ector_buffer.h | 18 +++- src/lib/ector/gl/ector_gl_buffer.c | 10 -- src/lib/ector/gl/ector_gl_buffer.eo | 2 - src/lib/ector/gl/ector_gl_buffer_base.c | 53 ++++++---- src/lib/ector/gl/ector_gl_buffer_base.eo | 23 +++- src/lib/ector/gl/ector_gl_private.h | 2 + .../evas/engines/gl_common/evas_gl_common.h | 9 ++ .../evas/engines/gl_common/evas_gl_texture.c | 15 +++ .../evas/engines/gl_generic/evas_ector_gl_buffer.c | 14 +++ .../engines/gl_generic/evas_ector_gl_buffer.eo | 5 + .../gl_generic/evas_ector_gl_image_buffer.c | 117 +++++++++++++++++++++ .../gl_generic/evas_ector_gl_image_buffer.eo | 12 +++ .../software_generic/evas_ector_software_buffer.c | 17 ++- 14 files changed, 279 insertions(+), 46 deletions(-) create mode 100644 src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.c create mode 100644 src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.eo create mode 100644 src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.c create mode 100644 src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.eo diff --git a/src/Makefile_Evas.am b/src/Makefile_Evas.am index 2c58f88..e95fe5b 100644 --- a/src/Makefile_Evas.am +++ b/src/Makefile_Evas.am @@ -750,7 +750,32 @@ modules/evas/engines/gl_common/shader_3d/evas_gl_3d_shaders.x: modules/evas/engi GL_GENERIC_SOURCES = \ modules/evas/engines/gl_generic/evas_engine.c \ -modules/evas/engines/gl_generic/Evas_Engine_GL_Generic.h +modules/evas/engines/gl_generic/Evas_Engine_GL_Generic.h \ +modules/evas/engines/gl_generic/evas_ector_gl_buffer.c \ +modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.c +$(NULL) + +evas_gl_generic_eolian_files = \ +modules/evas/engines/gl_generic/evas_ector_gl_buffer.eo \ +modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.eo \ +$(NULL) + +evas_gl_generic_eolian_c = $(evas_gl_generic_eolian_files:%.eo=%.eo.c) +evas_gl_generic_eolian_h = $(evas_gl_generic_eolian_files:%.eo=%.eo.h) \ + $(evas_gl_generic_eolian_files:%.eo=%.eo.legacy.h) \ + $(evas_gl_generic_eolian_type_files:%.eot=%.eot.h) + +BUILT_SOURCES += \ + $(evas_gl_generic_eolian_c) \ + $(evas_gl_generic_eolian_h) + +CLEANFILES += \ + $(evas_gl_generic_eolian_c) \ + $(evas_gl_generic_eolian_h) + +#evaseolianfilesdir = $(datadir)/eolian/include/evas-@VMAJ@ +#evaseolianfiles_DATA += $(evas_gl_generic_eolian_files) + if EVAS_STATIC_BUILD_GL_COMMON lib_evas_libevas_la_SOURCES += $(GL_COMMON_SOURCES) $(GL_GENERIC_SOURCES) @@ -776,6 +801,7 @@ modules_evas_engines_gl_generic_module_la_SOURCES = $(GL_GENERIC_SOURCES) modules_evas_engines_gl_generic_module_la_CFLAGS = \ -I$(top_builddir)/src/lib/efl \ -I$(top_srcdir)/src/lib/evas/include \ +-I$(top_builddir)/src/lib/evas/include \ -I$(top_srcdir)/src/lib/evas/cserve2 \ -I$(top_srcdir)/src/modules/evas/engines/gl_common \ -I$(top_builddir)/src/modules/evas/engines/gl_generic \ diff --git a/src/lib/ector/ector_buffer.h b/src/lib/ector/ector_buffer.h index 9399b17..0db6cc7 100644 --- a/src/lib/ector/ector_buffer.h +++ b/src/lib/ector/ector_buffer.h @@ -14,6 +14,7 @@ typedef Ector_Generic_Buffer Ector_Buffer; typedef struct _Ector_Generic_Buffer_Data Ector_Generic_Buffer_Data; typedef struct _Ector_Software_Buffer_Base_Data Ector_Software_Buffer_Base_Data; +typedef struct _Ector_GL_Buffer_Base_Data Ector_GL_Buffer_Base_Data; struct _Ector_Generic_Buffer_Data { @@ -21,12 +22,12 @@ struct _Ector_Generic_Buffer_Data unsigned int w, h; unsigned char l, r, t, b; Efl_Gfx_Colorspace cspace; - Eina_Bool immutable : 1; // pixels_set is forbidden + Eina_Bool immutable : 1; // pixels_set is forbidden }; struct _Ector_Software_Buffer_Base_Data { - Ector_Generic_Buffer_Data *generic; /* ugly */ + Ector_Generic_Buffer_Data *generic; union { unsigned int *u32; unsigned char *u8; @@ -40,4 +41,17 @@ struct _Ector_Software_Buffer_Base_Data Eina_Bool nofree : 1; // pixel data should not be free()'ed }; +struct _Ector_GL_Buffer_Base_Data +{ + Ector_Generic_Buffer_Data *generic; + int texid; + int fboid; + struct { + // x,y offset within the atlas + // w,h size of the atlas itself + int x, y, w, h; + } atlas; + Eina_Bool whole : 1; +}; + #endif diff --git a/src/lib/ector/gl/ector_gl_buffer.c b/src/lib/ector/gl/ector_gl_buffer.c index 19840f7..3f956b8 100644 --- a/src/lib/ector/gl/ector_gl_buffer.c +++ b/src/lib/ector/gl/ector_gl_buffer.c @@ -10,14 +10,4 @@ #define MY_CLASS ECTOR_GL_BUFFER_CLASS -static Eo_Base * -_ector_gl_buffer_eo_base_constructor(Eo *obj, void *pd) -{ -} - -static void -_ector_gl_buffer_eo_base_destructor(Eo *obj, void *pd) -{ -} - #include "ector_gl_buffer.eo.c" diff --git a/src/lib/ector/gl/ector_gl_buffer.eo b/src/lib/ector/gl/ector_gl_buffer.eo index e83fda4..aabeb09 100644 --- a/src/lib/ector/gl/ector_gl_buffer.eo +++ b/src/lib/ector/gl/ector_gl_buffer.eo @@ -3,7 +3,5 @@ class Ector.GL.Buffer (Eo.Base, Ector.GL.Buffer.Base) legacy_prefix: null; data: null; implements { - Eo.Base.constructor; - Eo.Base.destructor; } } diff --git a/src/lib/ector/gl/ector_gl_buffer_base.c b/src/lib/ector/gl/ector_gl_buffer_base.c index 81cd302..c55963a 100644 --- a/src/lib/ector/gl/ector_gl_buffer_base.c +++ b/src/lib/ector/gl/ector_gl_buffer_base.c @@ -1,5 +1,6 @@ -#define EFL_BETA_API_SUPPORT -#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif #include "Ector_GL.h" #include "ector_gl_private.h" @@ -8,20 +9,6 @@ #define MY_CLASS ECTOR_GL_BUFFER_BASE_MIXIN -typedef struct -{ - Ector_Generic_Buffer_Data *generic; - int texid; - int fboid; - int w, h; // Texture size, not the atlas - struct { - // x,y offset within the atlas - // w,h size of the atlas itself - int x, y, w, h; - } atlas; - Eina_Bool whole : 1; -} Ector_GL_Buffer_Base_Data; - EOLIAN static int _ector_gl_buffer_base_texture_get(Eo *obj EINA_UNUSED, Ector_GL_Buffer_Base_Data *pd) { @@ -43,8 +30,8 @@ _ector_gl_buffer_base_whole_get(Eo *obj EINA_UNUSED, Ector_GL_Buffer_Base_Data * EOLIAN static void _ector_gl_buffer_base_size_get(Eo *obj EINA_UNUSED, Ector_GL_Buffer_Base_Data *pd, int *w, int *h) { - if (w) *w = pd->w; - if (h) *h = pd->h; + if (w) *w = pd->generic->w; + if (h) *h = pd->generic->h; } EOLIAN static void @@ -52,8 +39,34 @@ _ector_gl_buffer_base_vertices_get(Eo *obj EINA_UNUSED, Ector_GL_Buffer_Base_Dat { if (x) *x = (double) pd->atlas.x / pd->atlas.w; if (y) *y = (double) pd->atlas.y / pd->atlas.h; - if (w) *w = (double) pd->w / pd->atlas.w; - if (h) *h = (double) pd->h / pd->atlas.h; + if (w) *w = (double) pd->generic->w / pd->atlas.w; + if (h) *h = (double) pd->generic->h / pd->atlas.h; +} + +EOLIAN static void +_ector_gl_buffer_base_attach(Eo *obj EINA_UNUSED, Ector_GL_Buffer_Base_Data *pd, + int texid, int fboid, Efl_Gfx_Colorspace cspace, + int imw, int imh, int tx, int ty, int tw, int th, + int l, int r, int t, int b) +{ + EINA_SAFETY_ON_NULL_RETURN(pd->generic); + EINA_SAFETY_ON_FALSE_RETURN(!pd->generic->immutable); + + pd->generic->cspace = cspace; + pd->generic->w = imw; + pd->generic->h = imh; + pd->atlas.x = tx; + pd->atlas.y = ty; + pd->atlas.w = tw; + pd->atlas.h = th; + pd->generic->l = l; + pd->generic->r = r; + pd->generic->t = t; + pd->generic->b = b; + if (!(tx - l) && !(ty - t) && ((tw + l + r) == imw) && ((th + t + b) == imh)) + pd->whole = EINA_TRUE; + pd->fboid = fboid; + pd->texid = texid; } #include "ector_gl_buffer_base.eo.c" diff --git a/src/lib/ector/gl/ector_gl_buffer_base.eo b/src/lib/ector/gl/ector_gl_buffer_base.eo index 36562ae..b783880 100644 --- a/src/lib/ector/gl/ector_gl_buffer_base.eo +++ b/src/lib/ector/gl/ector_gl_buffer_base.eo @@ -35,7 +35,10 @@ mixin Ector.GL.Buffer.Base (Ector.Generic.Buffer) } } @property vertices { - [[Returns the texture vertices to use to map this image with no rotation]] + [[Returns the texture vertices to draw this image with no rotation + + The 4 points are then defined as (x,y), (x+w,y), (x,y+h), (x+w,y+h). + ]] get {} values { x: double; [[X position of this image inside the texture atlas, from 0 to 1]] @@ -44,6 +47,24 @@ mixin Ector.GL.Buffer.Base (Ector.Generic.Buffer) h: double; [[Height of this image inside the texture atlas, from 0 to 1]] } } + attach @protected { + [[Attach to an existing texture (or FBO). Used from child classes.]] + params { + texid: int; + fboid: int; + cspace: Efl.Gfx.Colorspace; + imw: int; + imh: int; + tx: int; + ty: int; + tw: int; + th: int; + l: int; + r: int; + t: int; + b: int; + } + } } implements { } diff --git a/src/lib/ector/gl/ector_gl_private.h b/src/lib/ector/gl/ector_gl_private.h index e67b4d1..4fcfb1c 100644 --- a/src/lib/ector/gl/ector_gl_private.h +++ b/src/lib/ector/gl/ector_gl_private.h @@ -1,6 +1,8 @@ #ifndef ECTOR_GL_PRIVATE_H_ # define ECTOR_GL_PRIVATE_H_ +#define EVAS_GL_NO_HEADERS + #include "ector_private.h" #define SHADER_FLAG_SAM_BITSHIFT 3 diff --git a/src/modules/evas/engines/gl_common/evas_gl_common.h b/src/modules/evas/engines/gl_common/evas_gl_common.h index 24a5c3a..09b4cb5 100644 --- a/src/modules/evas/engines/gl_common/evas_gl_common.h +++ b/src/modules/evas/engines/gl_common/evas_gl_common.h @@ -18,6 +18,8 @@ # define MESA_EGL_NO_X11_HEADERS #endif +#ifndef EVAS_GL_NO_HEADERS + #ifdef BUILD_ENGINE_GL_COCOA # include # include @@ -41,6 +43,8 @@ # endif #endif +#endif + #include "evas_gl_define.h" #define EVAS_GL_TILE_SIZE 16 @@ -649,6 +653,7 @@ Evas_GL_Texture *evas_gl_common_texture_nv12tiled_new(Evas_Engine_GL_Context *g void evas_gl_common_texture_nv12tiled_update(Evas_GL_Texture *tex, DATA8 **row, unsigned int w, unsigned int h); Evas_GL_Texture *evas_gl_common_texture_rgb_a_pair_new(Evas_Engine_GL_Context *gc, RGBA_Image *im); void evas_gl_common_texture_rgb_a_pair_update(Evas_GL_Texture *tex, RGBA_Image *im); +Evas_Colorspace evas_gl_common_gl_format_to_colorspace(GLuint f); void evas_gl_common_image_alloc_ensure(Evas_GL_Image *im); Evas_GL_Image *evas_gl_common_image_load(Evas_Engine_GL_Context *gc, const char *file, const char *key, Evas_Image_Load_Opts *lo, int *error); @@ -864,6 +869,8 @@ __evas_gl_errdyn(int err, const char *file, const char *func, int line, const ch Eina_Bool evas_gl_common_module_open(void); void evas_gl_common_module_close(void); +#ifndef EVAS_GL_NO_HEADERS + static inline void _tex_sub_2d(Evas_Engine_GL_Context *gc, int x, int y, int w, int h, int fmt, int type, const void *pix) { @@ -880,6 +887,8 @@ _comp_tex_sub_2d(Evas_Engine_GL_Context *gc, int x, int y, int w, int h, int fmt glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, fmt, imgsize, pix); } +#endif + #include "evas_gl_3d_common.h" #undef EAPI diff --git a/src/modules/evas/engines/gl_common/evas_gl_texture.c b/src/modules/evas/engines/gl_common/evas_gl_texture.c index 9c31dda..f72a3e8 100644 --- a/src/modules/evas/engines/gl_common/evas_gl_texture.c +++ b/src/modules/evas/engines/gl_common/evas_gl_texture.c @@ -161,6 +161,21 @@ _evas_gl_texture_search_format(Eina_Bool alpha, Eina_Bool bgra, Evas_Colorspace return -1; } +Evas_Colorspace +evas_gl_common_gl_format_to_colorspace(GLuint f) +{ + unsigned i; + + for (i = 0; i < sizeof(matching_format) / sizeof(matching_format[0]); i++) + { + if (*matching_format[i].format == f) + return matching_format[i].cspace; + } + + ERR("Unknown texture format!"); + return EVAS_COLORSPACE_ARGB8888; +} + static void _print_tex_count(void) { diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.c b/src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.c new file mode 100644 index 0000000..4736e65 --- /dev/null +++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.c @@ -0,0 +1,14 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include "gl/ector_gl_private.h" +#include "evas_common_private.h" +#include "ector_buffer.h" +#include "evas_ector_buffer.eo.h" +#include "evas_ector_gl_buffer.eo.h" + +#define MY_CLASS EVAS_ECTOR_GL_BUFFER_CLASS + +#include "evas_ector_gl_buffer.eo.c" diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.eo b/src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.eo new file mode 100644 index 0000000..6912657 --- /dev/null +++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_buffer.eo @@ -0,0 +1,5 @@ +class Evas.Ector.GL.Buffer (Eo.Base, Evas.Ector.Buffer, Ector.GL.Buffer.Base) +{ + legacy_prefix: null; + data: null; +} diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.c b/src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.c new file mode 100644 index 0000000..101f0d9 --- /dev/null +++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.c @@ -0,0 +1,117 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define ECTOR_GL_BUFFER_BASE_PROTECTED + +#include +#include "gl/ector_gl_private.h" +#include "evas_common_private.h" +#include "../gl_common/evas_gl_common.h" +#include "evas_private.h" +#include "ector_buffer.h" + +#include "evas_ector_buffer.eo.h" +#include "evas_ector_gl_buffer.eo.h" +#include "evas_ector_gl_image_buffer.eo.h" + +#define MY_CLASS EVAS_ECTOR_GL_IMAGE_BUFFER_CLASS + +typedef struct +{ + Ector_GL_Buffer_Base_Data *base; + Evas *evas; + Evas_GL_Image *image; +} Evas_Ector_GL_Image_Buffer_Data; + +#define ENFN e->engine.func +#define ENDT e->engine.data.output + +EOLIAN static void +_evas_ector_gl_image_buffer_evas_ector_buffer_engine_image_set(Eo *obj, Evas_Ector_GL_Image_Buffer_Data *pd, + Evas *evas, void *image) +{ + Evas_Public_Data *e = eo_data_scope_get(evas, EVAS_CANVAS_CLASS); + Evas_GL_Image *im = image; + int l = 0, r = 0, t = 0, b = 0; + + if (pd->base->generic->immutable) + { + CRI("Can't set image after finalize"); + return; + } + + pd->evas = eo_xref(evas, obj); + pd->image = ENFN->image_ref(ENDT, im); + if (!pd->image) return; + + if (im->tex && im->tex->pt) + { + if (im->im) + { + l = im->im->cache_entry.borders.l; + r = im->im->cache_entry.borders.r; + t = im->im->cache_entry.borders.t; + b = im->im->cache_entry.borders.b; + } + else + { + // always 1 pixel border, except FBO + if (!im->tex->pt->fb) + l = r = t = b = 1; + } + eo_do(obj, ector_gl_buffer_base_attach(im->tex->pt->texture, + im->tex->pt->fb, + evas_gl_common_gl_format_to_colorspace(im->tex->pt->format), + im->tex->w, im->tex->h, + im->tex->x, im->tex->y, + im->tex->pt->w, im->tex->pt->h, + l, r, t, b)); + } + else + { + ERR("What do we do now?"); + } +} + +EOLIAN static void +_evas_ector_gl_image_buffer_evas_ector_buffer_engine_image_get(Eo *obj EINA_UNUSED, + Evas_Ector_GL_Image_Buffer_Data *pd, + Evas **evas, void **image) +{ + if (evas) *evas = pd->evas; + if (image) *image = pd->image; +} + +EOLIAN static Eo_Base * +_evas_ector_gl_image_buffer_eo_base_constructor(Eo *obj, Evas_Ector_GL_Image_Buffer_Data *pd) +{ + eo_do_super(obj, MY_CLASS, obj = eo_constructor()); + pd->base = eo_data_ref(obj, ECTOR_GL_BUFFER_CLASS); + pd->base->generic = eo_data_ref(obj, ECTOR_GENERIC_BUFFER_MIXIN); + pd->base->generic->eo = obj; + return obj; +} + +EOLIAN static Eo_Base * +_evas_ector_gl_image_buffer_eo_base_finalize(Eo *obj, Evas_Ector_GL_Image_Buffer_Data *pd) +{ + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->base, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->image, NULL); + pd->base->generic->immutable = EINA_TRUE; + return eo_do_super_ret(obj, MY_CLASS, obj, eo_finalize()); +} + +EOLIAN static void +_evas_ector_gl_image_buffer_eo_base_destructor(Eo *obj, Evas_Ector_GL_Image_Buffer_Data *pd) +{ + Evas_Public_Data *e = eo_data_scope_get(pd->evas, EVAS_CANVAS_CLASS); + + eo_data_unref(obj, pd->base->generic); + eo_data_unref(obj, pd->base); + ENFN->image_free(ENDT, pd->image); + eo_xunref(pd->evas, obj); + eo_do_super(obj, MY_CLASS, eo_destructor()); +} + +#include "evas_ector_gl_image_buffer.eo.c" diff --git a/src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.eo b/src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.eo new file mode 100644 index 0000000..0b72cda --- /dev/null +++ b/src/modules/evas/engines/gl_generic/evas_ector_gl_image_buffer.eo @@ -0,0 +1,12 @@ +class Evas.Ector.GL.Image.Buffer (Evas.Ector.GL.Buffer) +{ + [[An Ector GL buffer wrapping an existing Evas_GL_Image.]] + legacy_prefix: null; + implements { + Eo.Base.constructor; + Eo.Base.finalize; + Eo.Base.destructor; + Evas.Ector.Buffer.engine_image.set; + Evas.Ector.Buffer.engine_image.get; + } +} diff --git a/src/modules/evas/engines/software_generic/evas_ector_software_buffer.c b/src/modules/evas/engines/software_generic/evas_ector_software_buffer.c index 44ebf8f..22efa05 100644 --- a/src/modules/evas/engines/software_generic/evas_ector_software_buffer.c +++ b/src/modules/evas/engines/software_generic/evas_ector_software_buffer.c @@ -15,7 +15,7 @@ typedef struct { Ector_Software_Buffer_Base_Data *base; Evas *evas; - Image_Entry *ie; + Image_Entry *image; } Evas_Ector_Software_Buffer_Data; #define ENFN e->engine.func @@ -44,8 +44,8 @@ _evas_ector_software_buffer_evas_ector_buffer_engine_image_set(Eo *obj, Evas_Ect } pd->evas = eo_xref(evas, obj); - pd->ie = ENFN->image_ref(ENDT, ie); - if (!pd->ie) return; + pd->image = ENFN->image_ref(ENDT, ie); + if (!pd->image) return; eo_do(obj, ector_buffer_pixels_set(im->image.data, im->cache_entry.w, im->cache_entry.h, 0, @@ -59,7 +59,7 @@ _evas_ector_software_buffer_evas_ector_buffer_engine_image_get(Eo *obj EINA_UNUS Evas **evas, void **image) { if (evas) *evas = pd->evas; - if (image) *image = pd->ie; + if (image) *image = pd->image; } EOLIAN static Eo * @@ -73,11 +73,8 @@ _evas_ector_software_buffer_eo_base_constructor(Eo *obj, Evas_Ector_Software_Buf EOLIAN static Eo * _evas_ector_software_buffer_eo_base_finalize(Eo *obj, Evas_Ector_Software_Buffer_Data *pd) { - if (!pd->ie) - { - CRI("engine_image must be set at construction time only"); - return NULL; - } + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->base, NULL); + EINA_SAFETY_ON_NULL_RETURN_VAL(pd->image, NULL); pd->base->generic->immutable = EINA_TRUE; return eo_do_super_ret(obj, MY_CLASS, obj, eo_finalize()); } @@ -88,7 +85,7 @@ _evas_ector_software_buffer_eo_base_destructor(Eo *obj, Evas_Ector_Software_Buff Evas_Public_Data *e = eo_data_scope_get(pd->evas, EVAS_CANVAS_CLASS); eo_data_xunref(obj, pd->base, obj); - ENFN->image_free(ENDT, pd->ie); + ENFN->image_free(ENDT, pd->image); eo_xunref(pd->evas, obj); eo_do_super(obj, MY_CLASS, eo_destructor()); } -- 2.7.4