| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 1 | <html> | 
 | 2 |  | 
 | 3 | <title>Mesa EGL</title> | 
 | 4 |  | 
 | 5 | <head><link rel="stylesheet" type="text/css" href="mesa.css"></head> | 
 | 6 |  | 
 | 7 | <body> | 
 | 8 |  | 
 | 9 | <h1>Mesa EGL</h1> | 
 | 10 |  | 
 | 11 | <p>The current version of EGL in Mesa implements EGL 1.4.  More information | 
 | 12 | about EGL can be found at | 
| Brian Paul | aeff9f9 | 2010-01-21 08:13:32 -0700 | [diff] [blame] | 13 | <a href="http://www.khronos.org/egl/" target="_parent"> | 
 | 14 | http://www.khronos.org/egl/</a>.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 15 |  | 
 | 16 | <p>The Mesa's implementation of EGL uses a driver architecture.  The main | 
 | 17 | library (<code>libEGL</code>) is window system neutral.  It provides the EGL | 
 | 18 | API entry points and helper functions for use by the drivers.  Drivers are | 
 | 19 | dynamically loaded by the main library and most of the EGL API calls are | 
 | 20 | directly dispatched to the drivers.</p> | 
 | 21 |  | 
 | 22 | <p>The driver in use decides the window system to support.  For drivers that | 
 | 23 | support hardware rendering, there are usually multiple drivers supporting the | 
 | 24 | same window system.  Each one of of them supports a certain range of graphics | 
 | 25 | cards.</p> | 
 | 26 |  | 
 | 27 | <h2>Build EGL</h2> | 
 | 28 |  | 
 | 29 | <ol> | 
 | 30 | <li> | 
| Jeff Smith | fab1f07 | 2008-06-13 09:50:43 -0500 | [diff] [blame] | 31 | <p>Run <code>configure</code> with the desired state trackers and enable | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 32 | the Gallium driver for your hardware.  For example</p> | 
 | 33 |  | 
 | 34 | <pre> | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 35 |   $ ./configure --enable-gles-overlay --with-state-trackers=egl,vega --enable-gallium-intel | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 36 | </pre> | 
 | 37 |  | 
| Chia-I Wu | 2e3c4e4 | 2010-05-07 14:13:08 +0800 | [diff] [blame] | 38 | <p>The main library and OpenGL is enabled by default.  The first option enables | 
 | 39 | <a href="opengles.html">OpenGL ES 1.x and 2.x</a>.  The <code>egl</code> state | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 40 | tracker is needed by a number of EGL drivers.  EGL drivers will be covered | 
| Chia-I Wu | 2e3c4e4 | 2010-05-07 14:13:08 +0800 | [diff] [blame] | 41 | later.  The <a href="openvg.html">vega state tracker</a> provides OpenVG | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 42 | 1.x.</p> | 
 | 43 | </li> | 
 | 44 |  | 
 | 45 | <li>Build and install Mesa as usual.</li> | 
 | 46 | </ol> | 
 | 47 |  | 
 | 48 | <p>In the given example, it will build and install <code>libEGL</code>, | 
| Chia-I Wu | 2e3c4e4 | 2010-05-07 14:13:08 +0800 | [diff] [blame] | 49 | <code>libGL</code>, <code>libGLESv1_CM</code>, <code>libGLESv2</code>, | 
 | 50 | <code>libOpenVG</code>, and one or more EGL drivers.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 51 |  | 
 | 52 | <h3>Configure Options</h3> | 
 | 53 |  | 
 | 54 | <p>There are several options that control the build of EGL at configuration | 
 | 55 | time</p> | 
 | 56 |  | 
 | 57 | <ul> | 
 | 58 | <li><code>--enable-egl</code> | 
 | 59 |  | 
 | 60 | <p>By default, EGL is enabled.  When disabled, the main library and the drivers | 
 | 61 | will not be built.</p> | 
 | 62 |  | 
 | 63 | </li> | 
 | 64 |  | 
| Chia-I Wu | a6342af | 2010-01-26 10:54:45 +0800 | [diff] [blame] | 65 | <li><code>--with-egl-driver-dir</code> | 
 | 66 |  | 
 | 67 | <p>The directory EGL drivers should be installed to.  If not specified, EGL | 
 | 68 | drivers will be installed to <code>${libdir}/egl</code>.</p> | 
 | 69 |  | 
 | 70 | </li> | 
 | 71 |  | 
| Chia-I Wu | da39d5d | 2010-06-17 16:07:46 +0800 | [diff] [blame] | 72 | <li><code>--with-egl-platforms</code> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 73 |  | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 74 | <p>List the platforms (window systems) to support.  Its argument is a comma | 
 | 75 | seprated string such as <code>--with-egl-platforms=x11,kms</code>.  It decides | 
 | 76 | the platforms a driver may support.  The first listed platform is also used by | 
 | 77 | the main library to decide the native platform: the platform the EGL native | 
 | 78 | types such as <code>EGLNativeDisplayType</code> or | 
 | 79 | <code>EGLNativeWindowType</code> defined for.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 80 |  | 
| Chia-I Wu | da39d5d | 2010-06-17 16:07:46 +0800 | [diff] [blame] | 81 | <p>The available platforms are <code>x11</code>, <code>kms</code>, | 
 | 82 | <code>fbdev</code>, and <code>gdi</code>.  The <code>gdi</code> platform can | 
| Chia-I Wu | 411bba3 | 2010-06-11 12:47:14 +0800 | [diff] [blame] | 83 | only be built with SCons.</p> | 
 | 84 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 85 | </li> | 
 | 86 |  | 
 | 87 | <li><code>--with-state-trackers</code> | 
 | 88 |  | 
 | 89 | <p>The argument is a comma separated string.  It is usually used to specify the | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 90 | rendering APIs, such as OpenVG, to build.  But it is also used to specify | 
 | 91 | <code>egl</code> state tracker that <code>egl_gallium</code> depends on.</p> | 
| Chia-I Wu | 2e3c4e4 | 2010-05-07 14:13:08 +0800 | [diff] [blame] | 92 |  | 
 | 93 | </li> | 
 | 94 |  | 
 | 95 | <li><code>--enable-gles-overlay</code> | 
 | 96 |  | 
 | 97 | <p>OpenGL and OpenGL ES are not controlled by | 
 | 98 | <code>--with-state-trackers</code>.  OpenGL is always built.  To build OpenGL | 
 | 99 | ES, this option must be explicitly given.</p> | 
 | 100 |  | 
 | 101 | </li> | 
 | 102 |  | 
 | 103 | <li><code>--enable-gles1</code> and <code>--enable-gles2</code> | 
 | 104 |  | 
 | 105 | <p>Unlike <code>--enable-gles-overlay</code>, which builds one library for each | 
 | 106 | rendering API, these options enable OpenGL ES support in OpenGL.  The result is | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 107 | one big library that supports multiple APIs.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 108 |  | 
 | 109 | </li> | 
| Chia-I Wu | a1306f4 | 2010-01-22 15:51:51 +0800 | [diff] [blame] | 110 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 111 | </ul> | 
 | 112 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 113 | <h2>Use EGL</h2> | 
 | 114 |  | 
| Chia-I Wu | 411bba3 | 2010-06-11 12:47:14 +0800 | [diff] [blame] | 115 | <h3>Demos</h3> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 116 |  | 
| Chia-I Wu | 411bba3 | 2010-06-11 12:47:14 +0800 | [diff] [blame] | 117 | <p>There are demos for the client APIs supported by EGL.  They can be found in | 
 | 118 | mesa/demos repository.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 119 |  | 
 | 120 | <h3>Environment Variables</h3> | 
 | 121 |  | 
 | 122 | <p>There are several environment variables that control the behavior of EGL at | 
 | 123 | runtime</p> | 
 | 124 |  | 
 | 125 | <ul> | 
| Chia-I Wu | 5d8646c | 2010-02-02 15:34:55 +0800 | [diff] [blame] | 126 | <li><code>EGL_DRIVERS_PATH</code> | 
 | 127 |  | 
 | 128 | <p>By default, the main library will look for drivers in the directory where | 
 | 129 | the drivers are installed to.  This variable specifies a list of | 
 | 130 | colon-separated directories where the main library will look for drivers, in | 
| Chia-I Wu | 6fd8b6a | 2010-02-02 16:47:53 +0800 | [diff] [blame] | 131 | addition to the default directory.  This variable is ignored for setuid/setgid | 
 | 132 | binaries.</p> | 
| Chia-I Wu | 5d8646c | 2010-02-02 15:34:55 +0800 | [diff] [blame] | 133 |  | 
 | 134 | </li> | 
 | 135 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 136 | <li><code>EGL_DRIVER</code> | 
 | 137 |  | 
| Chia-I Wu | 7fc3581 | 2010-02-02 11:05:19 +0800 | [diff] [blame] | 138 | <p>This variable specifies a full path to an EGL driver and it forces the | 
 | 139 | specified EGL driver to be loaded.  It comes in handy when one wants to test a | 
| Chia-I Wu | 6fd8b6a | 2010-02-02 16:47:53 +0800 | [diff] [blame] | 140 | specific driver.  This variable is ignored for setuid/setgid binaries.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 141 |  | 
| Chia-I Wu | 6807182 | 2010-09-09 13:51:59 +0800 | [diff] [blame^] | 142 | <p><code>egl_gallium</code> dynamically loads hardware drivers and client API | 
 | 143 | modules found in <code>EGL_DRIVERS_PATH</code>.  Thus, specifying this variable | 
 | 144 | alone is not sufficient for <code>egl_gallium</code> for uninstalled build.</p> | 
 | 145 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 146 | </li> | 
 | 147 |  | 
| Chia-I Wu | da39d5d | 2010-06-17 16:07:46 +0800 | [diff] [blame] | 148 | <li><code>EGL_PLATFORM</code> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 149 |  | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 150 | <p>This variable specifies the native platform.  The valid values are the same | 
 | 151 | as those for <code>--with-egl-platforms</code>.  When the variable is not set, | 
 | 152 | the main library uses the first platform listed in | 
 | 153 | <code>--with-egl-platforms</code> as the native platform</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 154 |  | 
 | 155 | </li> | 
 | 156 |  | 
 | 157 | <li><code>EGL_LOG_LEVEL</code> | 
 | 158 |  | 
 | 159 | <p>This changes the log level of the main library and the drivers.  The valid | 
 | 160 | values are: <code>debug</code>, <code>info</code>, <code>warning</code>, and | 
 | 161 | <code>fatal</code>.</p> | 
 | 162 |  | 
 | 163 | </li> | 
 | 164 |  | 
 | 165 | <li><code>EGL_SOFTWARE</code> | 
 | 166 |  | 
 | 167 | <p>For drivers that support both hardware and software rendering, setting this | 
 | 168 | variable to true forces the use of software rendering.</p> | 
 | 169 |  | 
 | 170 | </li> | 
 | 171 | </ul> | 
 | 172 |  | 
 | 173 | <h2>EGL Drivers</h2> | 
 | 174 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 175 | <ul> | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 176 | <li><code>egl_gallium</code> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 177 |  | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 178 | <p>This driver is based on Gallium3D.  It supports all rendering APIs and | 
 | 179 | hardwares supported by Gallium3D.  It is the only driver that supports OpenVG. | 
 | 180 | The supported platforms are X11, KMS, FBDEV, and GDI.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 181 |  | 
| Chia-I Wu | b365b15 | 2010-07-03 17:18:40 +0800 | [diff] [blame] | 182 | </li> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 183 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 184 | <li><code>egl_glx</code> | 
 | 185 |  | 
 | 186 | <p>This driver provides a wrapper to GLX.  It uses exclusively GLX to implement | 
 | 187 | the EGL API.  It supports both direct and indirect rendering when the GLX does. | 
 | 188 | It is accelerated when the GLX is.  As such, it cannot provide functions that | 
 | 189 | is not available in GLX or GLX extensions.</p> | 
 | 190 | </li> | 
 | 191 |  | 
| Chia-I Wu | 5f08eff | 2010-02-05 11:46:28 +0800 | [diff] [blame] | 192 | <li><code>egl_dri2</code> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 193 |  | 
 | 194 | <p>This driver supports the X Window System as its window system.  It functions | 
| Chia-I Wu | 5f08eff | 2010-02-05 11:46:28 +0800 | [diff] [blame] | 195 | as a DRI2 driver loader.  Unlike <code>egl_glx</code>, it has no dependency on | 
 | 196 | <code>libGL</code>.  It talks to the X server directly using DRI2 protocol.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 197 |  | 
 | 198 | </li> | 
 | 199 | <li><code>egl_dri</code> | 
 | 200 |  | 
 | 201 | <p>This driver lacks maintenance and does <em>not</em> build.  It is similiar | 
| Chia-I Wu | 5f08eff | 2010-02-05 11:46:28 +0800 | [diff] [blame] | 202 | to <code>egl_dri2</code> in that it functions as a DRI(1) driver loader.  But | 
 | 203 | unlike <code>egl_dri2</code>, it supports Linux framebuffer devices as its | 
 | 204 | window system and supports EGL_MESA_screen_surface extension.  As DRI1 drivers | 
 | 205 | are phasing out, it might eventually be replaced by <code>egl_dri2</code>.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 206 |  | 
 | 207 | </li> | 
 | 208 | </ul> | 
 | 209 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 210 | <h2>Developers</h2> | 
 | 211 |  | 
| Chia-I Wu | 14cbf32 | 2010-01-27 23:18:22 +0800 | [diff] [blame] | 212 | <p>The sources of the main library and the classic drivers can be found at | 
| Chia-I Wu | 3c967a9 | 2010-01-22 16:31:43 +0800 | [diff] [blame] | 213 | <code>src/egl/</code>.  The sources of the <code>egl</code> state tracker can | 
| Chia-I Wu | 14cbf32 | 2010-01-27 23:18:22 +0800 | [diff] [blame] | 214 | be found at <code>src/gallium/state_trackers/egl/</code>.</p> | 
 | 215 |  | 
 | 216 | <p>The suggested way to learn to write a EGL driver is to see how other drivers | 
 | 217 | are written.  <code>egl_glx</code> should be a good reference.  It works in any | 
 | 218 | environment that has GLX support, and it is simpler than most drivers.</p> | 
 | 219 |  | 
 | 220 | <h3>Lifetime of Display Resources</h3> | 
 | 221 |  | 
 | 222 | <p>Contexts and surfaces are examples of display resources.  They might live | 
 | 223 | longer than the display that creates them.</p> | 
 | 224 |  | 
 | 225 | <p>In EGL, when a display is terminated through <code>eglTerminate</code>, all | 
 | 226 | display resources should be destroyed.  Similarly, when a thread is released | 
 | 227 | throught <code>eglReleaseThread</code>, all current display resources should be | 
 | 228 | released.  Another way to destory or release resources is through functions | 
 | 229 | such as <code>eglDestroySurface</code> or <code>eglMakeCurrent</code>.</p> | 
 | 230 |  | 
 | 231 | <p>When a resource that is current to some thread is destroyed, the resource | 
 | 232 | should not be destroyed immediately.  EGL requires the resource to live until | 
 | 233 | it is no longer current.  A driver usually calls | 
 | 234 | <code>eglIs<Resource>Bound</code> to check if a resource is bound | 
 | 235 | (current) to any thread in the destroy callbacks.  If it is still bound, the | 
 | 236 | resource is not destroyed.</p> | 
 | 237 |  | 
 | 238 | <p>The main library will mark destroyed current resources as unlinked.  In a | 
 | 239 | driver's <code>MakeCurrent</code> callback, | 
 | 240 | <code>eglIs<Resource>Linked</code> can then be called to check if a newly | 
 | 241 | released resource is linked to a display.  If it is not, the last reference to | 
 | 242 | the resource is removed and the driver should destroy the resource.  But it | 
 | 243 | should be careful here because <code>MakeCurrent</code> might be called with an | 
 | 244 | uninitialized display.</p> | 
 | 245 |  | 
 | 246 | <p>This is the only mechanism provided by the main library to help manage the | 
 | 247 | resources.  The drivers are responsible to the correct behavior as defined by | 
 | 248 | EGL.</p> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 249 |  | 
| Chia-I Wu | 1b2b08c | 2010-02-05 12:44:45 +0800 | [diff] [blame] | 250 | <h3><code>EGL_RENDER_BUFFER</code></h3> | 
 | 251 |  | 
 | 252 | <p>In EGL, the color buffer a context should try to render to is decided by the | 
 | 253 | binding surface.  It should try to render to the front buffer if the binding | 
 | 254 | surface has <code>EGL_RENDER_BUFFER</code> set to | 
 | 255 | <code>EGL_SINGLE_BUFFER</code>;  If the same context is later bound to a | 
 | 256 | surface with <code>EGL_RENDER_BUFFER</code> set to | 
 | 257 | <code>EGL_BACK_BUFFER</code>, the context should try to render to the back | 
 | 258 | buffer.  However, the context is allowed to make the final decision as to which | 
 | 259 | color buffer it wants to or is able to render to.</p> | 
 | 260 |  | 
 | 261 | <p>For pbuffer surfaces, the render buffer is always | 
 | 262 | <code>EGL_BACK_BUFFER</code>.  And for pixmap surfaces, the render buffer is | 
 | 263 | always <code>EGL_SINGLE_BUFFER</code>.  Unlike window surfaces, EGL spec | 
 | 264 | requires their <code>EGL_RENDER_BUFFER</code> values to be honored.  As a | 
 | 265 | result, a driver should never set <code>EGL_PIXMAP_BIT</code> or | 
 | 266 | <code>EGL_PBUFFER_BIT</code> bits of a config if the contexts created with the | 
 | 267 | config won't be able to honor the <code>EGL_RENDER_BUFFER</code> of pixmap or | 
 | 268 | pbuffer surfaces.</p> | 
 | 269 |  | 
 | 270 | <p>It should also be noted that pixmap and pbuffer surfaces are assumed to be | 
 | 271 | single-buffered, in that <code>eglSwapBuffers</code> has no effect on them.  It | 
 | 272 | is desirable that a driver allocates a private color buffer for each pbuffer | 
 | 273 | surface created.  If the window system the driver supports has native pbuffers, | 
 | 274 | or if the native pixmaps have more than one color buffers, the driver should | 
 | 275 | carefully attach the native color buffers to the EGL surfaces, re-route them if | 
 | 276 | required.</p> | 
 | 277 |  | 
 | 278 | <p>There is no defined behavior as to, for example, how | 
 | 279 | <code>glDrawBuffer</code> interacts with <code>EGL_RENDER_BUFFER</code>.  Right | 
 | 280 | now, it is desired that the draw buffer in a client API be fixed for pixmap and | 
 | 281 | pbuffer surfaces.  Therefore, the driver is responsible to guarantee that the | 
 | 282 | client API renders to the specified render buffer for pixmap and pbuffer | 
 | 283 | surfaces.</p> | 
 | 284 |  | 
| Chia-I Wu | e16f577 | 2010-02-17 19:52:29 +0800 | [diff] [blame] | 285 | <h3><code>EGLDisplay</code> Mutex</h3> | 
 | 286 |  | 
 | 287 | The <code>EGLDisplay</code> will be locked before calling any of the dispatch | 
 | 288 | functions (well, except for GetProcAddress which does not take an | 
 | 289 | <code>EGLDisplay</code>).  This guarantees that the same dispatch function will | 
 | 290 | not be called with the sample display at the same time.  If a driver has access | 
 | 291 | to an <code>EGLDisplay</code> without going through the EGL APIs, the driver | 
 | 292 | should as well lock the display before using it. | 
 | 293 |  | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 294 | <h3>TODOs</h3> | 
 | 295 |  | 
 | 296 | <ul> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 297 | <li>Pass the conformance tests</li> | 
| Chia-I Wu | 6807182 | 2010-09-09 13:51:59 +0800 | [diff] [blame^] | 298 | <li>Reference counting in main library?</li> | 
 | 299 | <li>Mixed use of OpenGL, OpenGL ES 1.1, and OpenGL ES 2.0 is supported.  But | 
 | 300 | which one of <code>libGL.so</code>, <code>libGLESv1_CM.so</code>, and | 
 | 301 | <code>libGLESv2.so</code> should an application link to?  Bad things may happen | 
 | 302 | when, say, an application is linked to <code>libGLESv2.so</code> and | 
 | 303 | <code>libcairo</code>, which is linked to <code>libGL.so</code> instead.</li> | 
| Chia-I Wu | ada4605 | 2010-01-21 15:29:14 +0800 | [diff] [blame] | 304 |  | 
 | 305 | </ul> | 
 | 306 |  | 
 | 307 | </body> | 
 | 308 | </html> |