diff mbox

[Branch,~glmark2-dev/glmark2/trunk] Rev 138: ShaderSource: Add support for setting shader precision at runtime using per-scene options.

Message ID 20110919162913.14543.63512.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

alexandros.frantzis@linaro.org Sept. 19, 2011, 4:29 p.m. UTC
Merge authors:
  Alexandros Frantzis (afrantzis)
Related merge proposals:
  https://code.launchpad.net/~linaro-graphics-wg/glmark2/shader-precision/+merge/75515
  proposed by: Alexandros Frantzis (afrantzis)
  review: Approve - Jesse Barker (jesse-barker)
------------------------------------------------------------
revno: 138 [merge]
committer: Alexandros Frantzis <alexandros.frantzis@linaro.org>
branch nick: trunk
timestamp: Mon 2011-09-19 19:18:59 +0300
message:
  ShaderSource: Add support for setting shader precision at runtime using per-scene options.
modified:
  data/shaders/bump-normals.frag
  data/shaders/bump-poly.frag
  data/shaders/conditionals.frag
  data/shaders/desktop-blur.frag
  data/shaders/desktop.frag
  data/shaders/effect-2d-convolution.frag
  data/shaders/function.frag
  data/shaders/light-advanced.frag
  data/shaders/light-basic-tex.frag
  data/shaders/light-basic.frag
  data/shaders/light-phong.frag
  data/shaders/loop.frag
  src/scene-build.cpp
  src/scene-desktop.cpp
  src/scene-texture.cpp
  src/scene.cpp
  src/shader-source.cpp
  src/shader-source.h


--
lp:glmark2
https://code.launchpad.net/~glmark2-dev/glmark2/trunk

You are subscribed to branch lp:glmark2.
To unsubscribe from this branch go to https://code.launchpad.net/~glmark2-dev/glmark2/trunk/+edit-subscription
diff mbox

Patch

=== modified file 'data/shaders/bump-normals.frag'
--- data/shaders/bump-normals.frag	2011-07-27 15:02:27 +0000
+++ data/shaders/bump-normals.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 uniform sampler2D NormalMap;
 uniform mat4 NormalMatrix;
 

=== modified file 'data/shaders/bump-poly.frag'
--- data/shaders/bump-poly.frag	2011-07-27 15:02:27 +0000
+++ data/shaders/bump-poly.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec3 Normal;
 
 void main(void)

=== modified file 'data/shaders/conditionals.frag'
--- data/shaders/conditionals.frag	2011-07-07 13:59:05 +0000
+++ data/shaders/conditionals.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec4 dummy;
 
 void main(void)

=== modified file 'data/shaders/desktop-blur.frag'
--- data/shaders/desktop-blur.frag	2011-09-12 15:39:23 +0000
+++ data/shaders/desktop-blur.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 uniform sampler2D Texture0;
 
 varying vec2 TextureCoord;

=== modified file 'data/shaders/desktop.frag'
--- data/shaders/desktop.frag	2011-09-09 13:57:53 +0000
+++ data/shaders/desktop.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 uniform sampler2D MaterialTexture0;
 
 varying vec2 TextureCoord;

=== modified file 'data/shaders/effect-2d-convolution.frag'
--- data/shaders/effect-2d-convolution.frag	2011-07-28 14:06:56 +0000
+++ data/shaders/effect-2d-convolution.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 uniform sampler2D Texture0;
 varying vec2 TextureCoord;
 

=== modified file 'data/shaders/function.frag'
--- data/shaders/function.frag	2011-07-07 13:16:12 +0000
+++ data/shaders/function.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec4 dummy;
 
 float process(float d)

=== modified file 'data/shaders/light-advanced.frag'
--- data/shaders/light-advanced.frag	2011-07-27 15:02:27 +0000
+++ data/shaders/light-advanced.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec3 Normal;
 
 void main(void)

=== modified file 'data/shaders/light-basic-tex.frag'
--- data/shaders/light-basic-tex.frag	2010-07-28 14:52:54 +0000
+++ data/shaders/light-basic-tex.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 uniform sampler2D MaterialTexture0;
 
 varying vec4 Color;

=== modified file 'data/shaders/light-basic.frag'
--- data/shaders/light-basic.frag	2010-07-28 14:52:54 +0000
+++ data/shaders/light-basic.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec4 Color;
 varying vec2 TextureCoord;
 

=== modified file 'data/shaders/light-phong.frag'
--- data/shaders/light-phong.frag	2011-09-13 16:08:19 +0000
+++ data/shaders/light-phong.frag	2011-09-19 16:18:59 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec3 vertex_normal;
 varying vec4 vertex_position;
 

=== modified file 'data/shaders/loop.frag'
--- data/shaders/loop.frag	2011-07-08 12:48:36 +0000
+++ data/shaders/loop.frag	2011-09-14 16:53:37 +0000
@@ -1,7 +1,3 @@ 
-#ifdef GL_ES
-precision mediump float;
-#endif
-
 varying vec4 dummy;
 uniform int FragmentLoops;
 

=== modified file 'src/scene-build.cpp'
--- src/scene-build.cpp	2011-09-16 12:04:44 +0000
+++ src/scene-build.cpp	2011-09-19 16:18:59 +0000
@@ -45,6 +45,26 @@ 
 
 int SceneBuild::load()
 {
+    mRotationSpeed = 36.0f;
+
+    mRunning = false;
+
+    return 1;
+}
+
+void SceneBuild::unload()
+{
+    mMesh.reset();
+
+}
+
+void SceneBuild::setup()
+{
+    using LibMatrix::vec3;
+
+    Scene::setup();
+
+    /* Set up shaders */
     static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic.vert");
     static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic.frag");
     static const LibMatrix::vec4 lightPosition(20.0f, 20.0f, 10.0f, 1.0f);
@@ -59,28 +79,9 @@ 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))
     {
-        return 0;
+        return;
     }
 
-    mRotationSpeed = 36.0f;
-
-    mRunning = false;
-
-    return 1;
-}
-
-void SceneBuild::unload()
-{
-    mProgram.stop();
-    mProgram.release();
-}
-
-void SceneBuild::setup()
-{
-    using LibMatrix::vec3;
-
-    Scene::setup();
-
     Model model;
     bool modelLoaded(false);
     const std::string& whichModel(mOptions["model"].value);
@@ -150,6 +151,7 @@ 
 SceneBuild::teardown()
 {
     mProgram.stop();
+    mProgram.release();
 
     mMesh.reset();
 

=== modified file 'src/scene-desktop.cpp'
--- src/scene-desktop.cpp	2011-09-19 15:34:58 +0000
+++ src/scene-desktop.cpp	2011-09-19 16:18:59 +0000
@@ -600,16 +600,12 @@ 
 int
 SceneDesktop::load()
 {
-    priv_->screen.init();
-    priv_->desktop.init();
     return 1;
 }
 
 void
 SceneDesktop::unload()
 {
-    priv_->desktop.release();
-    priv_->screen.release();
 }
 
 void
@@ -642,6 +638,8 @@ 
     glDepthMask(GL_FALSE);
 
     /* Set up the screen and desktop RenderObjects */
+    priv_->screen.init();
+    priv_->desktop.init();
     priv_->screen.size(LibMatrix::vec2(mCanvas.width(), mCanvas.height()));
     priv_->desktop.size(LibMatrix::vec2(mCanvas.width(), mCanvas.height()));
 
@@ -696,6 +694,9 @@ 
     glEnable(GL_DEPTH_TEST);
     glDepthMask(GL_TRUE);
 
+    priv_->desktop.release();
+    priv_->screen.release();
+
     Scene::teardown();
 }
 

=== modified file 'src/scene-texture.cpp'
--- src/scene-texture.cpp	2011-08-31 21:22:27 +0000
+++ src/scene-texture.cpp	2011-09-14 16:44:46 +0000
@@ -44,11 +44,6 @@ 
 
 int SceneTexture::load()
 {
-    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic.vert");
-    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic-tex.frag");
-    static const LibMatrix::vec4 lightPosition(20.0f, 20.0f, 10.0f, 1.0f);
-    static const LibMatrix::vec4 materialDiffuse(1.0f, 1.0f, 1.0f, 1.0f);
-
     Model model;
 
     if(!model.load_3ds(GLMARK_DATA_PATH"/models/cube.3ds"))
@@ -58,7 +53,27 @@ 
     model.convert_to_mesh(mCubeMesh);
     mCubeMesh.build_vbo();
 
+    mRotationSpeed = LibMatrix::vec3(36.0f, 36.0f, 36.0f);
+
+    mRunning = false;
+
+    return 1;
+}
+
+void SceneTexture::unload()
+{
+    mCubeMesh.reset();
+}
+
+void SceneTexture::setup()
+{
+    Scene::setup();
+
     // Load shaders
+    static const std::string vtx_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic.vert");
+    static const std::string frg_shader_filename(GLMARK_DATA_PATH"/shaders/light-basic-tex.frag");
+    static const LibMatrix::vec4 lightPosition(20.0f, 20.0f, 10.0f, 1.0f);
+    static const LibMatrix::vec4 materialDiffuse(1.0f, 1.0f, 1.0f, 1.0f);
     ShaderSource vtx_source(vtx_shader_filename);
     ShaderSource frg_source(frg_shader_filename);
 
@@ -69,7 +84,7 @@ 
     if (!Scene::load_shaders_from_strings(mProgram, vtx_source.str(),
                                           frg_source.str()))
     {
-        return 0;
+        return;
     }
 
     std::vector<GLint> attrib_locations;
@@ -78,25 +93,6 @@ 
     attrib_locations.push_back(mProgram["texcoord"].location());
     mCubeMesh.set_attrib_locations(attrib_locations);
 
-    mRotationSpeed = LibMatrix::vec3(36.0f, 36.0f, 36.0f);
-
-    mRunning = false;
-
-    return 1;
-}
-
-void SceneTexture::unload()
-{
-    mCubeMesh.reset();
-
-    mProgram.stop();
-    mProgram.release();
-}
-
-void SceneTexture::setup()
-{
-    Scene::setup();
-
     // Create texture according to selected filtering
     GLint min_filter = GL_NONE;
     GLint mag_filter = GL_NONE;
@@ -130,6 +126,8 @@ 
 void SceneTexture::teardown()
 {
     mProgram.stop();
+    mProgram.release();
+
     glDeleteTextures(1, &mTexture);
 
     Scene::teardown();

=== modified file 'src/scene.cpp'
--- src/scene.cpp	2011-07-05 09:14:41 +0000
+++ src/scene.cpp	2011-09-15 09:04:17 +0000
@@ -23,6 +23,7 @@ 
  */
 #include "scene.h"
 #include "log.h"
+#include "shader-source.h"
 #include <sstream>
 #include <cmath>
 #include <sys/time.h>
@@ -38,6 +39,12 @@ 
 {
     mOptions["duration"] = Scene::Option("duration", "10.0",
                                          "The duration of each benchmark in seconds");
+    mOptions["vertex-precision"] = Scene::Option("vertex-precision",
+                                                 "default,default,default,default",
+                                                 "The precision values for the vertex shader (\"int,float,sampler2d,samplercube\")");
+    mOptions["fragment-precision"] = Scene::Option("fragment-precision",
+                                                   "default,default,default,default",
+                                                   "The precision values for the fragment shader (\"int,float,sampler2d,samplercube\")");
 }
 
 Scene::~Scene()
@@ -57,6 +64,17 @@ 
 {
     stringstream ss(mOptions["duration"].value);
     ss >> mDuration;
+
+    ShaderSource::default_precision(
+            ShaderSource::Precision(mOptions["vertex-precision"].value),
+            ShaderSource::ShaderTypeVertex
+            );
+
+    ShaderSource::default_precision(
+            ShaderSource::Precision(mOptions["fragment-precision"].value),
+            ShaderSource::ShaderTypeFragment
+            );
+
 }
 
 void Scene::teardown()

=== modified file 'src/shader-source.cpp'
--- src/shader-source.cpp	2011-08-10 13:53:02 +0000
+++ src/shader-source.cpp	2011-09-15 09:04:17 +0000
@@ -29,6 +29,13 @@ 
 #include "util.h"
 
 /** 
+ * Holds default precision values for all shader types
+ * (even the unknown type, which is hardwired to default precision values)
+ */
+std::vector<ShaderSource::Precision>
+ShaderSource::default_precision_(ShaderSource::ShaderTypeUnknown + 1);
+
+/** 
  * Loads the contents of a file into a string.
  * 
  * @param filename the name of the file
@@ -351,3 +358,246 @@ 
 
     add(decl, decl_function);
 }
+
+/** 
+ * Gets the ShaderType for this ShaderSource.
+ *
+ * If the ShaderType is unknown, an attempt is made to infer
+ * the type from the shader source contents.
+ * 
+ * @return the ShaderType
+ */
+ShaderSource::ShaderType
+ShaderSource::type()
+{
+    /* Try to infer the type from the source contents */
+    if (type_ == ShaderSource::ShaderTypeUnknown) {
+        std::string source(source_.str());
+
+        if (source.find("gl_FragColor") != std::string::npos)
+            type_ = ShaderSource::ShaderTypeFragment;
+        else if (source.find("gl_Position") != std::string::npos)
+            type_ = ShaderSource::ShaderTypeVertex;
+        else
+            Log::debug("Cannot infer shader type from contents. Leaving it Unknown.\n");
+    }
+
+    return type_;
+}
+
+/** 
+ * Helper function that emits a precision statement.
+ * 
+ * @param ss the stringstream to add the statement to
+ * @param val the precision value
+ * @param type_str the variable type to apply the precision value to
+ */
+void
+ShaderSource::emit_precision(std::stringstream& ss, ShaderSource::PrecisionValue val,
+                             const std::string& type_str)
+{
+    static const char *precision_map[] = {
+        "lowp", "mediump", "highp", NULL
+    };
+
+    if (val == ShaderSource::PrecisionValueHigh) {
+        if (type_ == ShaderSource::ShaderTypeFragment)
+            ss << "#ifdef GL_FRAGMENT_PRECISION_HIGH" << std::endl;
+
+        ss << "precision highp " << type_str << ";" << std::endl;
+
+        if (type_ == ShaderSource::ShaderTypeFragment) {
+            ss << "#else" << std::endl;
+            ss << "precision mediump " << type_str << ";" << std::endl;
+            ss << "#endif" << std::endl;
+        }
+    }
+    else if (val >= 0 && val < ShaderSource::PrecisionValueDefault) {
+        ss << "precision " << precision_map[val] << " ";
+        ss << type_str << ";" << std::endl;
+    }
+
+    /* There is no default precision in the fragment shader, so set it to mediump */
+    if (val == ShaderSource::PrecisionValueDefault
+        && type_str == "float" && type_ == ShaderSource::ShaderTypeFragment)
+    {
+        ss << "precision mediump float;" << std::endl;
+    }
+}
+
+/** 
+ * Gets a string containing the complete shader source.
+ *
+ * Precision statements are applied at this point.
+ * 
+ * @return the shader source
+ */
+std::string
+ShaderSource::str()
+{
+    /* Decide which precision values to use */
+    ShaderSource::Precision precision;
+
+    if (precision_has_been_set_)
+        precision = precision_;
+    else
+        precision = default_precision(type());
+
+    /* Create the precision statements */
+    std::stringstream ss;
+    
+    emit_precision(ss, precision.int_precision, "int");
+    emit_precision(ss, precision.float_precision, "float");
+    emit_precision(ss, precision.sampler2d_precision, "sampler2D");
+    emit_precision(ss, precision.samplercube_precision, "samplerCube");
+
+    std::string precision_str(ss.str());
+    if (!precision_str.empty()) {
+        precision_str.insert(0, "#ifdef GL_ES\n");
+        precision_str.insert(precision_str.size(), "#endif\n");
+    }
+
+    return precision_str + source_.str();
+}
+
+/** 
+ * Sets the precision that will be used for this shader.
+ *
+ * This overrides any default values set with ShaderSource::default_*_precision().
+ * 
+ * @param precision the precision to set
+ */
+void
+ShaderSource::precision(const ShaderSource::Precision& precision)
+{
+    precision_ = precision;
+    precision_has_been_set_ = true;
+}
+
+/** 
+ * Gets the precision that will be used for this shader.
+ * 
+ * @return the precision
+ */
+const ShaderSource::Precision&
+ShaderSource::precision()
+{
+    return precision_;
+}
+
+/** 
+ * Sets the default precision that will be used for a shaders type.
+ *
+ * If type is ShaderTypeUnknown the supplied precision is used for all
+ * shader types.
+ *
+ * This can be overriden per ShaderSource object by using ::precision().
+ * 
+ * @param precision the default precision to set
+ * @param type the ShaderType to use the precision for
+ */
+void
+ShaderSource::default_precision(const ShaderSource::Precision& precision,
+                                ShaderSource::ShaderType type)
+{
+    if (type < 0 || type > ShaderSource::ShaderTypeUnknown)
+        type = ShaderSource::ShaderTypeUnknown;
+
+    if (type == ShaderSource::ShaderTypeUnknown) {
+        for (size_t i = 0; i < ShaderSource::ShaderTypeUnknown; i++)
+            default_precision_[i] = precision;
+    }
+    else {
+        default_precision_[type] = precision;
+    }
+}
+
+/** 
+ * Gets the default precision that will be used for a shader type.
+ *
+ * It is valid to use a type of ShaderTypeUnknown. This will always
+ * return a Precision with default values.
+ *
+ * @param type the ShaderType to get the precision of
+ * 
+ * @return the precision
+ */
+const ShaderSource::Precision&
+ShaderSource::default_precision(ShaderSource::ShaderType type)
+{
+    if (type < 0 || type > ShaderSource::ShaderTypeUnknown)
+        type = ShaderSource::ShaderTypeUnknown;
+
+    return default_precision_[type];
+}
+
+/****************************************
+ * ShaderSource::Precision constructors *
+ ****************************************/
+
+/** 
+ * Creates a ShaderSource::Precision with default precision values.
+ */
+ShaderSource::Precision::Precision() :
+    int_precision(ShaderSource::PrecisionValueDefault),
+    float_precision(ShaderSource::PrecisionValueDefault),
+    sampler2d_precision(ShaderSource::PrecisionValueDefault),
+    samplercube_precision(ShaderSource::PrecisionValueDefault)
+{
+}
+
+/** 
+ * Creates a ShaderSource::Precision using the supplied precision values.
+ */
+ShaderSource::Precision::Precision(ShaderSource::PrecisionValue int_p,
+                                   ShaderSource::PrecisionValue float_p,
+                                   ShaderSource::PrecisionValue sampler2d_p,
+                                   ShaderSource::PrecisionValue samplercube_p) :
+    int_precision(int_p), float_precision(float_p),
+    sampler2d_precision(sampler2d_p), samplercube_precision(samplercube_p)
+{
+}
+
+/** 
+ * Creates a ShaderSource::Precision from a string representation of
+ * precision values.
+ *
+ * The string format is:
+ * "<int>,<float>,<sampler2d>,<samplercube>"
+ *
+ * Each precision value is one of "high", "medium", "low" or "default".
+ * 
+ * @param precision_values the string representation of the precision values
+ */
+ShaderSource::Precision::Precision(const std::string& precision_values) :
+    int_precision(ShaderSource::PrecisionValueDefault),
+    float_precision(ShaderSource::PrecisionValueDefault),
+    sampler2d_precision(ShaderSource::PrecisionValueDefault),
+    samplercube_precision(ShaderSource::PrecisionValueDefault)
+{
+    std::vector<std::string> elems;
+
+    Util::split(precision_values, ',', elems);
+
+    for (size_t i = 0; i < elems.size() && i < 4; i++) {
+        const std::string& pstr(elems[i]);
+        ShaderSource::PrecisionValue pval;
+
+        if (pstr == "high")
+            pval = ShaderSource::PrecisionValueHigh;
+        else if (pstr == "medium")
+            pval = ShaderSource::PrecisionValueMedium;
+        else if (pstr == "low")
+            pval = ShaderSource::PrecisionValueLow;
+        else
+            pval = ShaderSource::PrecisionValueDefault;
+
+        switch(i) {
+            case 0: int_precision = pval; break;
+            case 1: float_precision = pval; break;
+            case 2: sampler2d_precision = pval; break;
+            case 3: samplercube_precision = pval; break;
+            default: break;
+        }
+    }
+}

=== modified file 'src/shader-source.h'
--- src/shader-source.h	2011-07-28 08:44:31 +0000
+++ src/shader-source.h	2011-09-15 09:04:17 +0000
@@ -32,8 +32,16 @@ 
 class ShaderSource
 {
 public:
-    ShaderSource() {}
-    ShaderSource(const std::string &filename) { append_file(filename); }
+    enum ShaderType {
+        ShaderTypeVertex,
+        ShaderTypeFragment,
+        ShaderTypeUnknown
+    };
+
+    ShaderSource(ShaderType type = ShaderTypeUnknown) :
+        precision_has_been_set_(false), type_(type) {}
+    ShaderSource(const std::string &filename, ShaderType type = ShaderTypeUnknown) :
+        precision_has_been_set_(false), type_(type) { append_file(filename); }
 
     void append(const std::string &str);
     void append_file(const std::string &filename);
@@ -58,12 +66,46 @@ 
                    const std::string &init_function,
                    const std::string &decl_function = "");
 
-    std::string str() { return source_.str(); }
+    ShaderType type();
+    std::string str();
+
+    enum PrecisionValue {
+        PrecisionValueLow,
+        PrecisionValueMedium,
+        PrecisionValueHigh,
+        PrecisionValueDefault,
+    };
+
+    struct Precision {
+        Precision();
+        Precision(PrecisionValue int_p, PrecisionValue float_p,
+                  PrecisionValue sampler2d_p, PrecisionValue samplercube_p);
+        Precision(const std::string& list);
+
+        PrecisionValue int_precision;
+        PrecisionValue float_precision;
+        PrecisionValue sampler2d_precision;
+        PrecisionValue samplercube_precision;
+    };
+
+    void precision(const Precision& precision);
+    const Precision& precision();
+
+    static void default_precision(const Precision& precision,
+                                  ShaderType type = ShaderTypeUnknown);
+    static const Precision& default_precision(ShaderType type);
 
 private:
     void add_global(const std::string &str);
     void add_local(const std::string &str, const std::string &function);
     bool load_file(const std::string& filename, std::string& str);
+    void emit_precision(std::stringstream& ss, ShaderSource::PrecisionValue val,
+                        const std::string& type_str);
 
     std::stringstream source_;
+    Precision precision_;
+    bool precision_has_been_set_;
+    ShaderType type_;
+
+    static std::vector<Precision> default_precision_;
 };