Просмотр исходного кода

Changed UI/forms to no longer render to an offscreen framebuffer.
By default all draw calls for UI (forms, controls, text) is now batched (it was never batched at all in the past).
Added a new property to form files called "batchEnabled" to allow disabling batching in rare cases where forms contain multiple transparent layers of controls and text in such a way that batching causes visual artifacts. For most forms, this will never be an issue.

Steve Grenier 12 лет назад
Родитель
Сommit
3068010d8d
41 измененных файлов с 915 добавлено и 402 удалено
  1. 0 17
      gameplay/res/shaders/form.frag
  2. 0 19
      gameplay/res/shaders/form.vert
  3. 10 5
      gameplay/src/CheckBox.cpp
  4. 2 5
      gameplay/src/CheckBox.h
  5. 22 23
      gameplay/src/Container.cpp
  6. 2 8
      gameplay/src/Container.h
  7. 72 123
      gameplay/src/Control.cpp
  8. 61 17
      gameplay/src/Control.h
  9. 1 1
      gameplay/src/ControlFactory.cpp
  10. 199 60
      gameplay/src/Form.cpp
  11. 44 3
      gameplay/src/Form.h
  12. 7 7
      gameplay/src/ImageControl.cpp
  13. 5 2
      gameplay/src/ImageControl.h
  14. 18 9
      gameplay/src/Joystick.cpp
  15. 2 5
      gameplay/src/Joystick.h
  16. 10 7
      gameplay/src/Label.cpp
  17. 2 4
      gameplay/src/Label.h
  18. 9 2
      gameplay/src/MeshBatch.cpp
  19. 6 0
      gameplay/src/MeshBatch.h
  20. 9 4
      gameplay/src/RadioButton.cpp
  21. 2 5
      gameplay/src/RadioButton.h
  22. 24 27
      gameplay/src/Slider.cpp
  23. 4 14
      gameplay/src/Slider.h
  24. 14 1
      gameplay/src/SpriteBatch.cpp
  25. 24 0
      gameplay/src/SpriteBatch.h
  26. 31 20
      gameplay/src/TextBox.cpp
  27. 4 10
      gameplay/src/TextBox.h
  28. 7 2
      gameplay/src/Theme.h
  29. 73 0
      gameplay/src/lua/lua_Form.cpp
  30. 2 0
      gameplay/src/lua/lua_Form.h
  31. 36 0
      gameplay/src/lua/lua_MeshBatch.cpp
  32. 1 0
      gameplay/src/lua/lua_MeshBatch.h
  33. 64 0
      gameplay/src/lua/lua_Rectangle.cpp
  34. 1 0
      gameplay/src/lua/lua_Rectangle.h
  35. 97 0
      gameplay/src/lua/lua_SpriteBatch.cpp
  36. 1 0
      gameplay/src/lua/lua_SpriteBatch.h
  37. 45 0
      gameplay/src/lua/lua_Theme.cpp
  38. 1 0
      gameplay/src/lua/lua_Theme.h
  39. 1 0
      samples/browser/res/common/forms/formBasicControls.form
  40. 1 0
      samples/browser/res/common/forms/formZOrder.form
  41. 1 2
      samples/browser/src/FormsSample.cpp

+ 0 - 17
gameplay/res/shaders/form.frag

@@ -1,17 +0,0 @@
-#ifdef OPENGL_ES
-precision highp float;
-#endif
-
-///////////////////////////////////////////////////////////
-// Uniforms
-uniform sampler2D u_texture;
-
-///////////////////////////////////////////////////////////
-// Varyings
-varying vec2 v_texCoord;
-
-
-void main()
-{
-    gl_FragColor = texture2D(u_texture, v_texCoord);
-}

+ 0 - 19
gameplay/res/shaders/form.vert

@@ -1,19 +0,0 @@
-///////////////////////////////////////////////////////////
-// Attributes
-attribute vec3 a_position;
-attribute vec2 a_texCoord;
-
-///////////////////////////////////////////////////////////
-// Uniforms
-uniform mat4 u_worldViewProjectionMatrix;
-
-///////////////////////////////////////////////////////////
-// Varyings
-varying vec2 v_texCoord;
-
-
-void main()
-{
-    gl_Position = u_worldViewProjectionMatrix * vec4(a_position, 1);
-    v_texCoord = a_texCoord;
-}

+ 10 - 5
gameplay/src/CheckBox.cpp

@@ -148,14 +148,14 @@ void CheckBox::update(const Control* container, const Vector2& offset)
     }
 }
 
-void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+unsigned int CheckBox::drawImages(Form* form, const Rectangle& clip)
 {
-    GP_ASSERT(spriteBatch);
-    GP_ASSERT(_image);
+    if (!_image)
+        return 0;
 
     // Left, v-center.
     // TODO: Set an alignment for icons.
-    
+
     const Rectangle& region = _image->getRegion();
     const Theme::UVs& uvs = _image->getUVs();
     Vector4 color = _image->getColor();
@@ -173,7 +173,12 @@ void CheckBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 
     Vector2 pos(_viewportBounds.x, _viewportBounds.y + _viewportBounds.height * 0.5f - size.y * 0.5f);
 
-    spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+    SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+    startBatch(form, batch);
+    batch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+    finishBatch(form, batch);
+
+    return 1;
 }
 
 const char* CheckBox::getType() const

+ 2 - 5
gameplay/src/CheckBox.h

@@ -146,12 +146,9 @@ protected:
     void update(const Control* container, const Vector2& offset);
 
     /**
-     * Draw the checkbox icon associated with this control.
-     *
-     * @param spriteBatch The sprite batch containing this control's icons.
-     * @param clip The container position this control is relative to.
+     * @see Control::drawImages
      */
-    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    unsigned int drawImages(Form* form, const Rectangle& clip);
 
     /**
      * Whether this checkbox is currently checked.

+ 22 - 23
gameplay/src/Container.cpp

@@ -607,30 +607,32 @@ void Container::update(const Control* container, const Vector2& offset)
     }
 }
 
-void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targetHeight)
+unsigned int Container::draw(Form* form, const Rectangle& clip)
 {
     if (!_visible)
-        return;
+        return 0;
 
-    spriteBatch->start();
-    Control::drawBorder(spriteBatch, clip);
-    spriteBatch->finish();
+    // Draw container skin
+    unsigned int drawCalls = Control::draw(form, clip);
 
+    // Draw child controls
     for (size_t i = 0, count = _controls.size(); i < count; ++i)
     {
         Control* control = _controls[i];
         if (control && control->_absoluteClipBounds.intersects(_absoluteClipBounds))
         {
-            control->draw(spriteBatch, _viewportClipBounds, targetHeight);
+            drawCalls += control->draw(form, _viewportClipBounds);
         }
     }
 
+    // Draw scrollbars
     if (_scroll != SCROLL_NONE && (_scrollBarOpacity > 0.0f))
     {
         // Draw scroll bars.
         Rectangle clipRegion(_viewportClipBounds);
 
-        spriteBatch->start();
+        SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+        startBatch(form, batch);
 
         if (_scrollBarBounds.height > 0 && ((_scroll & SCROLL_VERTICAL) == SCROLL_VERTICAL))
         {
@@ -652,15 +654,17 @@ void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targ
             clipRegion.width += verticalRegion.width;
 
             Rectangle bounds(_viewportBounds.x + _viewportBounds.width, _viewportBounds.y + _scrollBarBounds.y, topRegion.width, topRegion.height);
-            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, topUVs.u1, topUVs.v1, topUVs.u2, topUVs.v2, topColor, clipRegion);
+            batch->draw(bounds.x, bounds.y, bounds.width, bounds.height, topUVs.u1, topUVs.v1, topUVs.u2, topUVs.v2, topColor, clipRegion);
 
             bounds.y += topRegion.height;
             bounds.height = _scrollBarBounds.height - topRegion.height - bottomRegion.height;
-            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, verticalUVs.u1, verticalUVs.v1, verticalUVs.u2, verticalUVs.v2, verticalColor, clipRegion);
+            batch->draw(bounds.x, bounds.y, bounds.width, bounds.height, verticalUVs.u1, verticalUVs.v1, verticalUVs.u2, verticalUVs.v2, verticalColor, clipRegion);
 
             bounds.y += bounds.height;
             bounds.height = bottomRegion.height;
-            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, bottomUVs.u1, bottomUVs.v1, bottomUVs.u2, bottomUVs.v2, bottomColor, clipRegion);
+            batch->draw(bounds.x, bounds.y, bounds.width, bounds.height, bottomUVs.u1, bottomUVs.v1, bottomUVs.u2, bottomUVs.v2, bottomColor, clipRegion);
+
+            drawCalls += 3;
         }
 
         if (_scrollBarBounds.width > 0 && ((_scroll & SCROLL_HORIZONTAL) == SCROLL_HORIZONTAL))
@@ -683,28 +687,23 @@ void Container::draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targ
             clipRegion.height += horizontalRegion.height;
         
             Rectangle bounds(_viewportBounds.x + _scrollBarBounds.x, _viewportBounds.y + _viewportBounds.height, leftRegion.width, leftRegion.height);
-            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, leftUVs.u1, leftUVs.v1, leftUVs.u2, leftUVs.v2, leftColor, clipRegion);
+            batch->draw(bounds.x, bounds.y, bounds.width, bounds.height, leftUVs.u1, leftUVs.v1, leftUVs.u2, leftUVs.v2, leftColor, clipRegion);
 
             bounds.x += leftRegion.width;
             bounds.width = _scrollBarBounds.width - leftRegion.width - rightRegion.width;
-            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, horizontalUVs.u1, horizontalUVs.v1, horizontalUVs.u2, horizontalUVs.v2, horizontalColor, clipRegion);
+            batch->draw(bounds.x, bounds.y, bounds.width, bounds.height, horizontalUVs.u1, horizontalUVs.v1, horizontalUVs.u2, horizontalUVs.v2, horizontalColor, clipRegion);
 
             bounds.x += bounds.width;
             bounds.width = rightRegion.width;
-            spriteBatch->draw(bounds.x, bounds.y, bounds.width, bounds.height, rightUVs.u1, rightUVs.v1, rightUVs.u2, rightUVs.v2, rightColor, clipRegion);
-        }
+            batch->draw(bounds.x, bounds.y, bounds.width, bounds.height, rightUVs.u1, rightUVs.v1, rightUVs.u2, rightUVs.v2, rightColor, clipRegion);
 
-        spriteBatch->finish();
-
-        if (_scrollingVelocity.isZero())
-        {
-            _dirty = false;
+            drawCalls += 3;
         }
+
+        finishBatch(form, batch);
     }
-    else
-    {
-        _dirty = false;
-    }
+
+    return drawCalls;
 }
 
 bool Container::isDirty()

+ 2 - 8
gameplay/src/Container.h

@@ -374,15 +374,9 @@ protected:
     void addControls(Properties* properties);
 
     /**
-     * Draws a sprite batch for the specified clipping rect.
-     *
-     * @param spriteBatch The sprite batch to use.
-     * @param clip The clipping rectangle.
-     * @param needsClear Whether it needs to be cleared.
-     * @param cleared Whether it was previously cleared
-     * @param targetHeight The targets height
+     * @see Control::draw
      */
-    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targetHeight);
+    virtual unsigned int draw(Form* form, const Rectangle& clip);
 
     /**
      * Update scroll position and velocity.

+ 72 - 123
gameplay/src/Control.cpp

@@ -1137,10 +1137,6 @@ void Control::update(const Control* container, const Vector2& offset)
         _bounds.height);
 
     // Calculate absolute clipped bounds
-    /*_absoluteClipBounds.x = min(max(_absoluteBounds.x, parentAbsoluteClip.x), _absoluteBounds.right());
-    _absoluteClipBounds.y = min(max(_absoluteBounds.y, parentAbsoluteClip.y), _absoluteBounds.bottom());
-    _absoluteClipBounds.width = max(min(_absoluteBounds.right(), parentAbsoluteClip.right()) - _absoluteClipBounds.x, 0.0f);
-    _absoluteClipBounds.height = max(min(_absoluteBounds.bottom(), parentAbsoluteClip.bottom()) - _absoluteClipBounds.y, 0.0f);*/
     Rectangle::intersect(_absoluteBounds, parentAbsoluteClip, &_absoluteClipBounds);
 
     // Calculate the local clipped bounds
@@ -1161,98 +1157,6 @@ void Control::update(const Control* container, const Vector2& offset)
     // Calculate the absolute clipped viewport bounds
     Rectangle::intersect(_viewportBounds, parentAbsoluteClip, &_viewportClipBounds);
 
-    /*float x, y, width, height, clipX2, x2, clipY2, y2;
-
-    // Calculate the local clipped bounds
-    const Theme::Border& border = getBorder(getState());
-    const Theme::Padding& padding = getPadding();
-    width = _bounds.width;
-    height = _bounds.height;
-    if (container)
-    {
-        x = _bounds.x + offset.x;
-        y = _bounds.y + offset.y;
-        x2 = parentAbsoluteClip.x + x + width;
-        y2 = parentAbsoluteClip.y + y + height;
-    }
-    else
-    {
-        x = border.left + padding.left;
-        y = border.top + padding.top;
-        x2 = width;
-        y2 = height;
-    }
-    clipX2 = parentAbsoluteClip.x + parentAbsoluteClip.width;
-    clipY2 = parentAbsoluteClip.y + parentAbsoluteClip.height;
-    if (x2 > clipX2)
-        width -= x2 - clipX2;
-    if (y2 > clipY2)
-        height -= y2 - clipY2;
-
-    if (x < 0)
-    {
-        width += x;
-        x = -x;
-    }
-    else
-    {
-        x = 0;
-    }
-
-    if (y < 0)
-    {
-        height += y;
-        y = -y;
-    }
-    else
-    {
-        y = 0;
-    }
-    _clipBounds.set(x, y, width, height);
-    */
-
-    // Calculate the absolute viewport bounds (content area, which does not include border and padding)
-    /*x += border.left + padding.left;
-    y += border.top + padding.top;
-    width = _bounds.width - border.left - padding.left - border.right - padding.right;
-    height = _bounds.height - border.top - padding.top - border.bottom - padding.bottom;
-    _viewportBounds.set(x, y, width, height);*/
-
-    // Calculate the clipped viewport bounds (clipped content area).
-    //if (container)
-    /*{
-        clipX2 = parentAbsoluteClip.x + parentAbsoluteClip.width;
-        clipY2 = parentAbsoluteClip.y + parentAbsoluteClip.height;
-    }
-    //else
-    //{
-        //clipX2 = parentAbsoluteClip.width;
-        //clipY2 = parentAbsoluteClip.height;
-    //}
-    x2 = x + width;
-    if (x2 > clipX2)
-        width = clipX2 - x;
-    y2 = y + height;
-    if (y2 > clipY2)
-        height = clipY2 - y;
-
-    if (x < parentAbsoluteClip.x)
-    {
-        float dx = parentAbsoluteClip.x - x;
-        width -= dx;
-        x = parentAbsoluteClip.x;
-    }
-
-    if (y < parentAbsoluteClip.y)
-    {
-        float dy = parentAbsoluteClip.y - y;
-        height -= dy;
-        y = parentAbsoluteClip.y;
-    }
-
-    _viewportClipBounds.set(x, y, width, height);
-    */
-
     // Cache themed attributes for performance.
     _skin = getSkin(getState());
 
@@ -1262,10 +1166,36 @@ void Control::update(const Control* container, const Vector2& offset)
         _opacity *= container->_opacity;
 }
 
-void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
+void Control::startBatch(Form* form, SpriteBatch* batch)
 {
-    if (!spriteBatch || !_skin || _absoluteBounds.width <= 0 || _absoluteBounds.height <= 0)
-        return;
+    form->startBatch(batch);
+}
+
+void Control::finishBatch(Form* form, SpriteBatch* batch)
+{
+    form->finishBatch(batch);
+}
+
+unsigned int Control::draw(Form* form, const Rectangle& clip)
+{
+    if (!_visible)
+        return 0;
+
+    unsigned int drawCalls = drawBorder(form, clip);
+    drawCalls += drawImages(form, clip);
+    drawCalls += drawText(form, clip);
+    return drawCalls;
+}
+
+unsigned int Control::drawBorder(Form* form, const Rectangle& clip)
+{
+    if (!form || !_skin || _absoluteBounds.width <= 0 || _absoluteBounds.height <= 0)
+        return 0;
+
+    unsigned int drawCalls = 0;
+
+    SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+    startBatch(form, batch);
 
     // Get the border and background images for this control's current state.
     const Theme::UVs& topLeft = _skin->getUVs(Theme::Skin::TOP_LEFT);
@@ -1295,53 +1225,72 @@ void Control::drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip)
     if (!border.left && !border.right && !border.top && !border.bottom)
     {
         // No border, just draw the image.
-        spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, _absoluteBounds.width, _absoluteBounds.height, center.u1, center.v1, center.u2, center.v2, skinColor, clip);
+        batch->draw(_absoluteBounds.x, _absoluteBounds.y, _absoluteBounds.width, _absoluteBounds.height, center.u1, center.v1, center.u2, center.v2, skinColor, clip);
+        ++drawCalls;
     }
     else
     {
         if (border.left && border.top)
-            spriteBatch->draw(_absoluteBounds.x, _absoluteBounds.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, skinColor, clip);
+        {
+            batch->draw(_absoluteBounds.x, _absoluteBounds.y, border.left, border.top, topLeft.u1, topLeft.v1, topLeft.u2, topLeft.v2, skinColor, clip);
+            ++drawCalls;
+        }
         if (border.top)
-            spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, skinColor, clip);
+        {
+            batch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y, midWidth, border.top, top.u1, top.v1, top.u2, top.v2, skinColor, clip);
+            ++drawCalls;
+        }
         if (border.right && border.top)
-            spriteBatch->draw(rightX, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
+        {
+            batch->draw(rightX, _absoluteBounds.y, border.right, border.top, topRight.u1, topRight.v1, topRight.u2, topRight.v2, skinColor, clip);
+            ++drawCalls;
+        }
         if (border.left)
-            spriteBatch->draw(_absoluteBounds.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
+        {
+            batch->draw(_absoluteBounds.x, midY, border.left, midHeight, left.u1, left.v1, left.u2, left.v2, skinColor, clip);
+            ++drawCalls;
+        }
 
         // Always draw the background.
-        spriteBatch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _absoluteBounds.width - border.left - border.right, _absoluteBounds.height - border.top - border.bottom,
+        batch->draw(_absoluteBounds.x + border.left, _absoluteBounds.y + border.top, _absoluteBounds.width - border.left - border.right, _absoluteBounds.height - border.top - border.bottom,
             center.u1, center.v1, center.u2, center.v2, skinColor, clip);
+        ++drawCalls;
 
         if (border.right)
-            spriteBatch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, skinColor, clip);
+        {
+            batch->draw(rightX, midY, border.right, midHeight, right.u1, right.v1, right.u2, right.v2, skinColor, clip);
+            ++drawCalls;
+        }
         if (border.bottom && border.left)
-            spriteBatch->draw(_absoluteBounds.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, skinColor, clip);
+        {
+            batch->draw(_absoluteBounds.x, bottomY, border.left, border.bottom, bottomLeft.u1, bottomLeft.v1, bottomLeft.u2, bottomLeft.v2, skinColor, clip);
+            ++drawCalls;
+        }
         if (border.bottom)
-            spriteBatch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, skinColor, clip);
+        {
+            batch->draw(midX, bottomY, midWidth, border.bottom, bottom.u1, bottom.v1, bottom.u2, bottom.v2, skinColor, clip);
+            ++drawCalls;
+        }
         if (border.bottom && border.right)
-            spriteBatch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, skinColor, clip);
+        {
+            batch->draw(rightX, bottomY, border.right, border.bottom, bottomRight.u1, bottomRight.v1, bottomRight.u2, bottomRight.v2, skinColor, clip);
+            ++drawCalls;
+        }
     }
-}
 
-void Control::drawImages(SpriteBatch* spriteBatch, const Rectangle& position)
-{
+    finishBatch(form, batch);
+
+    return drawCalls;
 }
 
-void Control::drawText(const Rectangle& position)
+unsigned int Control::drawImages(Form* form, const Rectangle& position)
 {
+    return 0;
 }
 
-void Control::draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targetHeight)
+unsigned int Control::drawText(Form* form, const Rectangle& position)
 {
-    if (!_visible)
-        return;
-
-    spriteBatch->start();
-    drawBorder(spriteBatch, clip);
-    drawImages(spriteBatch, clip);
-    spriteBatch->finish();
-
-    drawText(clip);
+    return 0;
 }
 
 bool Control::isDirty()

+ 61 - 17
gameplay/src/Control.h

@@ -4,7 +4,6 @@
 #include "Ref.h"
 #include "Rectangle.h"
 #include "Vector2.h"
-#include "SpriteBatch.h"
 #include "Theme.h"
 #include "ThemeStyle.h"
 #include "Touch.h"
@@ -1093,39 +1092,84 @@ protected:
      */
     virtual void update(const Control* container, const Vector2& offset);
 
+    /**
+     * Indicates that a control will begin drawing into the specified batch.
+     *
+     * When drawing is finshed (before any other batch can be drawn into), the
+     * finishBatch method should be called.
+     *
+     * @param form The form beign drawn.
+     * @param batch The sprite batch to be drawn into.
+     */
+    void startBatch(Form* form, SpriteBatch* batch);
+
+    /**
+     * Called after a batch has been drawn into and before any other batch is used.
+     *
+     * @param form The form being drawn.
+     * @param batch The batch that was previously started (via Control::startBatch).
+     */
+    void finishBatch(Form* form, SpriteBatch* batch);
+
+    /**
+     * Draws the control.
+     *
+     * Implementations of Control are expected to perform all drawing into a SpriteBatch.
+     * Batches should not be explicitly started or finished, but instead should be passed
+     * to Control::prepare(Form*, SpriteBatch*). This will handle automatically starting
+     * and finishing the batch when neccessary.
+     *
+     * @param form The top level form being drawn.
+     * @param clip The clipping rectangle.
+     *
+     * @return The number of draw calls issued.
+     */
+    virtual unsigned int draw(Form* form, const Rectangle& clip);
+
     /**
      * Draws the themed border and background of a control.
      *
-     * @param spriteBatch The sprite batch containing this control's border images.
+     * Implementations of Control are expected to perform all drawing into a SpriteBatch.
+     * Batches should not be explicitly started or finished, but instead should be passed
+     * to Control::prepare(Form*, SpriteBatch*). This will handle automatically starting
+     * and finishing the batch when neccessary.
+     *
+     * @param form The top level form being drawn.
      * @param clip The clipping rectangle of this control's parent container.
+     *
+     * @return The number of draw calls issued.
      */
-    virtual void drawBorder(SpriteBatch* spriteBatch, const Rectangle& clip);
+    virtual unsigned int drawBorder(Form* form, const Rectangle& clip);
 
     /**
      * Draw the images associated with this control.
      *
-     * @param spriteBatch The sprite batch containing this control's icons.
+     * Implementations of Control are expected to perform all drawing into a SpriteBatch.
+     * Batches should not be explicitly started or finished, but instead should be passed
+     * to Control::prepare(Form*, SpriteBatch*). This will handle automatically starting
+     * and finishing the batch when neccessary.
+     *
+     * @param form The top level form being drawn.
      * @param clip The clipping rectangle of this control's parent container.
+     *
+     * @return The number of draw calls issued.
      */
-    virtual void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    virtual unsigned int drawImages(Form* form, const Rectangle& clip);
 
     /**
      * Draw this control's text.
      *
+     * Implementations of Control are expected to perform all drawing into a SpriteBatch.
+     * Batches should not be explicitly started or finished, but instead should be passed
+     * to Control::prepare(Form*, SpriteBatch*). This will handle automatically starting
+     * and finishing the batch when neccessary.
+     *
+     * @param form The top level form being drawn.
      * @param clip The clipping rectangle of this control's parent container.
-     */
-    virtual void drawText(const Rectangle& clip);
-
-    /**
-     * Draws a sprite batch for the specified clipping rect.
      *
-     * @param spriteBatch The sprite batch to use.
-     * @param clip The clipping rectangle.
-     * @param needsClear Whether it needs to be cleared.
-     * @param cleared Whether it was previously cleared
-     * @param targetHeight The targets height
+     * @return The number of draw calls issued.
      */
-    virtual void draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targetHeight);
+    virtual unsigned int drawText(Form* form, const Rectangle& clip);
 
     /**
      * Initializes the control.
@@ -1335,7 +1379,7 @@ private:
     Theme::Skin* getSkin(State state);
 
     void addSpecificListener(Control::Listener* listener, Control::Listener::EventType eventType);
-    
+
     bool _styleOverridden;
     Theme::Skin* _skin;
 

+ 1 - 1
gameplay/src/ControlFactory.cpp

@@ -10,7 +10,7 @@
 #include "Joystick.h"
 #include "ImageControl.h"
 
-namespace gameplay 
+namespace gameplay
 {
 
 static ControlFactory* __controlFactory = NULL;

+ 199 - 60
gameplay/src/Form.cpp

@@ -10,10 +10,6 @@
 #include "CheckBox.h"
 #include "Scene.h"
 
-// Default form shaders
-#define FORM_VSH "res/shaders/form.vert"
-#define FORM_FSH "res/shaders/form.frag"
-
 // Scroll speed when using a DPad -- max scroll speed when using a joystick.
 static const float GAMEPAD_SCROLL_SPEED = 500.0f;
 // Distance a joystick must be pushed in order to trigger focus-change and/or scrolling.
@@ -21,30 +17,29 @@ static const float JOYSTICK_THRESHOLD = 0.75f;
 // If the DPad or joystick is held down, this is the initial delay in milliseconds between focus changes.
 static const float GAMEPAD_FOCUS_REPEAT_DELAY = 300.0f;
 
+// Shaders used for drawing offscreen quad when form is attached to a node
+#define FORM_VSH "res/shaders/sprite.vert"
+#define FORM_FSH "res/shaders/sprite.frag"
+
+//#define GAMEPLAY_FORMS_USE_FRAMEBUFFER
+
 namespace gameplay
 {
 
-static Effect* __formEffect = NULL;
 static std::vector<Form*> __forms;
 Control* Form::_focusControl = NULL;
 Control* Form::_activeControl = NULL;
 Control::State Form::_activeControlState = Control::NORMAL;
 static bool _shiftKeyDown = false;
 
-Form::Form() : _node(NULL), _u2(0), _v1(0)
+Form::Form() : _node(NULL), _frameBuffer(NULL), _model(NULL), _batched(true)
 {
 }
 
 Form::~Form()
 {
-    if (__formEffect)
-    {
-        if (__formEffect->getRefCount() == 1)
-        {
-            __formEffect->release();
-            __formEffect = NULL;
-        }
-    }
+    SAFE_RELEASE(_model);
+    SAFE_RELEASE(_frameBuffer);
 
     // Remove this Form from the global list.
     std::vector<Form*>::iterator it = std::find(__forms.begin(), __forms.end(), this);
@@ -96,6 +91,8 @@ Form* Form::create(const char* url)
         }
     }
 
+    form->_batched = formProperties->getBool("batchingEnabled", true);
+
     // Initialize the form and all of its child controls
     form->initialize("Form", style, formProperties);
 
@@ -151,52 +148,108 @@ bool Form::isForm() const
     return true;
 }
 
-static Effect* createEffect()
+void Form::setNode(Node* node)
 {
-    Effect* effect = NULL;
-    if (__formEffect == NULL)
+    if (_node != node)
     {
-        __formEffect = Effect::createFromFile(FORM_VSH, FORM_FSH);
-        if (__formEffect == NULL)
-        {
-            GP_ERROR("Unable to load form effect.");
-            return NULL;
-        }
-        effect = __formEffect;
+        _node = node;
+
+        updateFrameBuffer();
     }
-    else
+}
+
+static unsigned int nextPowerOfTwo(unsigned int v)
+{
+    if (!((v & (v - 1)) == 0))
     {
-        effect = __formEffect;
+        v--;
+        v |= v >> 1;
+        v |= v >> 2;
+        v |= v >> 4;
+        v |= v >> 8;
+        v |= v >> 16;
+        return v + 1;
     }
-    return effect;
+
+    return v;
 }
 
-void Form::setNode(Node* node)
+void Form::updateFrameBuffer()
 {
-    _node = node;
+    SAFE_RELEASE(_model);
+    SAFE_RELEASE(_frameBuffer);
+
+#ifdef GAMEPLAY_FORMS_USE_FRAMEBUFFER
+
+    if (_node && _absoluteClipBounds.width > 0 && _absoluteClipBounds.height > 0)
+    {
+        // Create an offscreen buffer to draw our form into
+        unsigned int width = nextPowerOfTwo(_absoluteClipBounds.width);
+        unsigned int height = nextPowerOfTwo(_absoluteClipBounds.height);
+
+        _frameBuffer = FrameBuffer::create(_id.c_str(), width, height);
+
+        // Create a model (quad) to draw our offscreen buffer onto
+        float x2 = _absoluteClipBounds.width;
+        float y2 = _absoluteClipBounds.height;
+        float u2 = x2 / width;
+        float v1 = y2 / height;
+        float vertices[] =
+        {
+            0, y2, 0, 0, v1, 1, 1, 1, 1,
+            0, 0, 0, 0, 0, 1, 1, 1, 1,
+            x2, y2, 0, u2, v1, 1, 1, 1, 1,
+            x2, 0, 0, u2, 0, 1, 1, 1, 1
+        };
+        VertexFormat::Element elements[] =
+        {
+            VertexFormat::Element(VertexFormat::POSITION, 3),
+            VertexFormat::Element(VertexFormat::TEXCOORD0, 2),
+            VertexFormat::Element(VertexFormat::COLOR, 4)
+        };
+        Mesh* mesh = Mesh::createMesh(VertexFormat(elements, 3), 4, false);
+        GP_ASSERT(mesh);
+        mesh->setPrimitiveType(Mesh::TRIANGLE_STRIP);
+        mesh->setVertexData(vertices, 0, 4);
+
+        _model = Model::create(mesh);
+        SAFE_RELEASE(mesh);
+
+        Material* material = _model->setMaterial(FORM_VSH, FORM_FSH);
+        material->getParameter("u_projectionMatrix")->bindValue(this, &Form::getProjectionMatrix);
+
+        Texture::Sampler* sampler = Texture::Sampler::create(_frameBuffer->getRenderTarget()->getTexture());
+        sampler->setWrapMode(Texture::CLAMP, Texture::CLAMP);
+        material->getParameter("u_texture")->setSampler(sampler);
+        sampler->release();
+
+        material->getStateBlock()->setDepthWrite(true);
+        material->getStateBlock()->setBlend(true);
+        material->getStateBlock()->setBlendSrc(RenderState::BLEND_SRC_ALPHA);
+        material->getStateBlock()->setBlendDst(RenderState::BLEND_ONE_MINUS_SRC_ALPHA);
+    }
+
+#endif
 }
 
 void Form::update(float elapsedTime)
 {
-    if (isDirty())
-    {
-        update(NULL, Vector2::zero());
+    update(NULL, Vector2::zero());
 
-        Control::State state = getState();
+    Control::State state = getState();
 
-        // Cache themed attributes for performance.
-        _skin = getSkin(state);
-        _opacity = getOpacity(state);
+    // Cache themed attributes for performance.
+    _skin = getSkin(state);
+    _opacity = getOpacity(state);
 
-        GP_ASSERT(_layout);
-        if (_scroll != SCROLL_NONE)
-        {
-            updateScroll();
-        }
-        else
-        {
-            _layout->update(this, Vector2::zero());
-        }
+    GP_ASSERT(_layout);
+    if (_scroll != SCROLL_NONE)
+    {
+        updateScroll();
+    }
+    else
+    {
+        _layout->update(this, Vector2::zero());
     }
 }
 
@@ -208,6 +261,38 @@ void Form::update(const Control* container, const Vector2& offset)
     Container::update(container, offset);
 
     _layout->align(this, NULL);
+
+    if (_absoluteClipBounds != oldAbsoluteClipBounds)
+    {
+        if (_node)
+            updateFrameBuffer();
+    }
+}
+
+void Form::startBatch(SpriteBatch* batch)
+{
+    // TODO (note: might want to pass a level number/depth here so that batch draw calls can be sorted correctly, such as all text on top)
+    if (!batch->isStarted())
+    {
+        batch->setProjectionMatrix(_projectionMatrix);
+        batch->start();
+
+        if (_batched)
+            _batches.push_back(batch);
+    }
+}
+
+void Form::finishBatch(SpriteBatch* batch)
+{
+    if (!_batched)
+    {
+        batch->finish();
+    }
+}
+
+const Matrix& Form::getProjectionMatrix() const
+{
+    return  _projectionMatrix;
 }
 
 unsigned int Form::draw()
@@ -215,31 +300,74 @@ unsigned int Form::draw()
     if (!_visible || _absoluteClipBounds.width == 0 || _absoluteClipBounds.height == 0)
         return 0;
 
-    if (_node)
+    Game* game = Game::getInstance();
+    Rectangle viewport = game->getViewport();
+
+    FrameBuffer* oldFrameBuffer = NULL;
+    if (_frameBuffer)
     {
-        // TODO: Create a perspective projection matrix based off the node's camera
-        GP_WARN("TODO: Support forms attached to nodes.");
+        // Update the viewport
+        game->setViewport(Rectangle(0, 0, _absoluteClipBounds.width, _absoluteClipBounds.height));
+
+        // Bind the offscreen buffer and clear its color and depth.
+        oldFrameBuffer = _frameBuffer->bind();
+        Game::getInstance()->clear(Game::CLEAR_COLOR_DEPTH, Vector4::zero(), 1, 0);
+
+        // Setup an ortho matrix the maps to the size of the form
+        Matrix::createOrthographicOffCenter(0, _absoluteClipBounds.width, _absoluteClipBounds.height, 0, 0, 1, &_projectionMatrix);
     }
     else
     {
-        // Create an orthographic projection mapped to the current viewport
-        const Rectangle& viewport = Game::getInstance()->getViewport();
-        Matrix::createOrthographicOffCenter(0, viewport.width, viewport.height, 0, 0, 1, &_projectionMatrix);
+        // If we're drawing in 2D (i.e. not attached to a node), we need to clear the depth buffer
+        if (_node)
+        {
+            // Drawing in 3D.
+            // Setup a projection matrix for drawing the form via the node's world transform.
+            Matrix world(_node->getWorldMatrix());
+            world.scale(1, -1, 1);
+            world.translate(0, -_absoluteClipBounds.height, 0);
+            Matrix::multiply(_node->getViewProjectionMatrix(), world, &_projectionMatrix);
+        }
+        else
+        {
+            // Drawing in 2D, so we need to clear the depth buffer
+            Game::getInstance()->clear(Game::CLEAR_DEPTH, Vector4::zero(), 1, 0);
+
+            // Setup an ortho matrix that maps to the current viewport
+            const Rectangle& viewport = Game::getInstance()->getViewport();
+            Matrix::createOrthographicOffCenter(0, viewport.width, viewport.height, 0, 0, 1, &_projectionMatrix);
+        }
     }
 
-    _style->getTheme()->setProjectionMatrix(_projectionMatrix);
+    // Draw the form
+    unsigned int drawCalls = Container::draw(this, _absoluteClipBounds);
 
-    // TODO: Maintain multiple sprite batches (for different styles/themes and fonts) and start/top them all as neccessary
-    SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
-    //batch->start();
+    // Flush all batches that were queued during drawing and then empty the batch list
+    if (_batched)
+    {
+        unsigned int batchCount = _batches.size();
+        for (unsigned int i = 0; i < batchCount; ++i)
+            _batches[i]->finish();
+        _batches.clear();
+        drawCalls = batchCount;
+    }
 
-    // Draw the form
-    Container::draw(batch, _absoluteClipBounds, _absoluteClipBounds.height);
+    // If the form was drawn into an offscreen buffer, we need to now draw that buffer
+    // using the WVP matrix of the node we're attached to.
+    if (oldFrameBuffer)
+    {
+        game->setViewport(viewport);
+        oldFrameBuffer->bind();
 
-    //batch->finish();
+        if (_model)
+        {
+            _projectionMatrix = _node->getWorldViewProjectionMatrix();
+            _model->draw();
+            ++drawCalls;
+        }
+    }
 
-    // TODO: We should return the actual number of draw calls here (number fo batches flushed)
-    return 1;
+    return drawCalls;
 }
 
 const char* Form::getType() const
@@ -247,6 +375,17 @@ const char* Form::getType() const
     return "form";
 }
 
+
+bool Form::isBatchingEnabled() const
+{
+    return _batched;
+}
+
+void Form::setBatchingEnabled(bool enabled)
+{
+    _batched = enabled;
+}
+
 Control* Form::getActiveControl()
 {
     return _activeControl;

+ 44 - 3
gameplay/src/Form.h

@@ -9,6 +9,7 @@
 #include "Keyboard.h"
 #include "Mouse.h"
 #include "Gamepad.h"
+#include "FrameBuffer.h"
 
 namespace gameplay
 {
@@ -128,6 +129,8 @@ public:
 
     /**
      * Draws this form.
+     *
+     * @return The nubmer of draw calls issued to draw the form.
      */
     unsigned int draw();
 
@@ -136,6 +139,25 @@ public:
      */
     const char* getType() const;
 
+    /**
+     * Determines whether batching is enabled for this form.
+     *
+     * @return True if batching is enabled for this form, false otherwise.
+     */
+    bool isBatchingEnabled() const;
+
+    /**
+     * Turns batching on or off for this form.
+     *
+     * By default, forms enable batching as a way to optimize performance. However, on certain
+     * complex forms that contain multiple layers of overlapping text and transparent controls,
+     * batching may cause some visual artifacts due alpha blending issues. In these cases,
+     * turning batching off usually fixes the issue at a slight performance cost.
+     *
+     * @param enabled True to enable batching (default), false otherwise.
+     */
+    void setBatchingEnabled(bool enabled);
+
     /**
      * Returns the single currently active control within the UI system.
      *
@@ -222,6 +244,21 @@ private:
      */
     static void resizeEventInternal(unsigned int width, unsigned int height);
 
+    /**
+     * Called to update internal framebuffer when forms are attached to a node.
+     */
+    void updateFrameBuffer();
+
+    /**
+     * Called during drawing to prepare a sprite batch for being drawn into for this form.
+     */
+    void startBatch(SpriteBatch* batch);
+
+    /**
+     * Called during drawing to signal completion of drawing into a batch.
+     */
+    void finishBatch(SpriteBatch* batch);
+
     /**
      * Unproject a point (from a mouse or touch event) into the scene and then project it onto the form.
      *
@@ -233,6 +270,8 @@ private:
      */
     bool projectPoint(int x, int y, Vector3* point);
 
+    const Matrix& getProjectionMatrix() const;
+
     static bool pointerEventInternal(bool mouse, int evt, int x, int y, int param);
 
     static Control* findInputControl(int* x, int* y, bool focus);
@@ -256,9 +295,11 @@ private:
     static bool pollGamepad(Gamepad* gamepad);
 
     Node* _node;                        // Node for transforming this Form in world-space.
-    float _u2;
-    float _v1;
-    Matrix _projectionMatrix;           // Orthographic projection matrix to be set on SpriteBatch objects when rendering into the FBO.
+    FrameBuffer* _frameBuffer;          // FrameBuffer for offscreen drawing of forms that are attached to a Node
+    Model* _model;                      // Model used to render form in 3D when attached to a Node
+    Matrix _projectionMatrix;           // Projection matrix to be set on SpriteBatch objects when rendering the form
+    std::vector<SpriteBatch*> _batches;
+    bool _batched;
     static Control* _focusControl;
     static Control* _activeControl;
     static Control::State _activeControlState;

+ 7 - 7
gameplay/src/ImageControl.cpp

@@ -113,17 +113,16 @@ const char* ImageControl::getType() const
     return "image";
 }
 
-void ImageControl::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+unsigned int ImageControl::drawImages(Form* form, const Rectangle& clip)
 {
-    spriteBatch->finish();
+    if (!_batch)
+        return 0;
 
-    // An ImageControl is not part of the texture atlas but should use the same projection matrix.
-    _batch->setProjectionMatrix(spriteBatch->getProjectionMatrix());
+    startBatch(form, _batch);
 
     Vector4 color = Vector4::one();
     color.w *= _opacity;
 
-    _batch->start();
     if (_dstRegion.isEmpty())
     {
         _batch->draw(_viewportBounds.x, _viewportBounds.y, _viewportBounds.width, _viewportBounds.height,
@@ -135,9 +134,10 @@ void ImageControl::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
             _dstRegion.width, _dstRegion.height,
             _uvs.u1, _uvs.v1, _uvs.u2, _uvs.v2, color, _viewportClipBounds);
     }
-    _batch->finish();
 
-    spriteBatch->start();
+    finishBatch(form, _batch);
+
+    return 1;
 }
 
 void ImageControl::update(const Control* container, const Vector2& offset)

+ 5 - 2
gameplay/src/ImageControl.h

@@ -122,7 +122,10 @@ protected:
 
     void initialize(const char* typeName, Theme::Style* style, Properties* properties);
 
-    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    /**
+     * @see Control::drawImages
+     */
+    unsigned int drawImages(Form* form, const Rectangle& clip);
 
     /**
      * @see Control#update(const Control*, const Vector2&)
@@ -134,7 +137,7 @@ protected:
     // Destination region.
     Rectangle _dstRegion;
     SpriteBatch* _batch;
-    
+
     // One over texture width and height, for use when calculating UVs from a new source region.
     float _tw;
     float _th;

+ 18 - 9
gameplay/src/Joystick.cpp

@@ -241,12 +241,12 @@ bool Joystick::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int cont
     return Control::touchEvent(evt, x, y, contactIndex);
 }
 
-void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+unsigned int Joystick::drawImages(Form* form, const Rectangle& clip)
 {
-    GP_ASSERT(spriteBatch);
-
     Control::State state = getState();
 
+    unsigned int drawCalls = 0;
+
     // If the joystick is not absolute, then only draw if it is active.
     if (!_relative || (_relative && state == ACTIVE))
     {
@@ -256,6 +256,9 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
             _screenRegion.y = _viewportClipBounds.y + (_viewportClipBounds.height - _screenRegion.height) / 2.0f;
         }
 
+        SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+        startBatch(form, batch);
+
         // Draw the outer image.
         Theme::ThemeImage* outer = getImage("outer", state);
         if (outer)
@@ -263,9 +266,10 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
             const Theme::UVs& uvs = outer->getUVs();
             const Vector4& color = outer->getColor();
             if (_relative)
-                spriteBatch->draw(_screenRegion.x, _screenRegion.y, _outerSize->x, _outerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+                batch->draw(_screenRegion.x, _screenRegion.y, _outerSize->x, _outerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
             else
-                spriteBatch->draw(_screenRegion.x, _screenRegion.y, _outerSize->x, _outerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+                batch->draw(_screenRegion.x, _screenRegion.y, _outerSize->x, _outerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+            ++drawCalls;
         }
 
         // Draw the inner image.
@@ -273,20 +277,25 @@ void Joystick::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         if (inner)
         {
             Vector2 position(_screenRegion.x, _screenRegion.y);
-            
+
             // Adjust position to reflect displacement.
             position.x += _displacement.x;
             position.y += -_displacement.y;
-            
+
             // Get the uvs and color and draw.
             const Theme::UVs& uvs = inner->getUVs();
             const Vector4& color = inner->getColor();
             if (_relative)
-                spriteBatch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
+                batch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color);
             else
-                spriteBatch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+                batch->draw(position.x, position.y, _innerSize->x, _innerSize->y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+            ++drawCalls;
         }
+
+        finishBatch(form, batch);
     }
+
+    return drawCalls;
 }
 
 const char* Joystick::getType() const

+ 2 - 5
gameplay/src/Joystick.h

@@ -163,12 +163,9 @@ protected:
     bool touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int contactIndex);
 
     /**
-     * Draw the images associated with this control.
-     *
-     * @param spriteBatch The sprite batch containing this control's icons.
-     * @param clip The clipping rectangle of this control's parent container.
+     * @see Control::drawImages
      */
-    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    unsigned int drawImages(Form* form, const Rectangle& clip);
 
 private:
 

+ 10 - 7
gameplay/src/Label.cpp

@@ -94,19 +94,22 @@ void Label::update(const Control* container, const Vector2& offset)
     }
 }
 
-void Label::drawText(const Rectangle& clip)
+unsigned int Label::drawText(Form* form, const Rectangle& clip)
 {
-    if (_text.size() <= 0)
-        return;
-
     // Draw the text.
-    if (_font)
+    if (_text.size() > 0 && _font)
     {
         Control::State state = getState();
-        _font->start();
+
+        SpriteBatch* batch = _font->getSpriteBatch();
+        startBatch(form, batch);
         _font->drawText(_text.c_str(), _textBounds, _textColor, getFontSize(state), getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
-        _font->finish();
+        finishBatch(form, batch);
+
+        return 1;
     }
+
+    return 0;
 }
 
 const char* Label::getType() const

+ 2 - 4
gameplay/src/Label.h

@@ -115,11 +115,9 @@ protected:
     void update(const Control* container, const Vector2& offset);
 
     /**
-     * Draw this label's text.
-     *
-     * @param clip The clipping rectangle of this label's parent container.
+     * @see Control::drawText
      */
-    virtual void drawText(const Rectangle& clip);
+    virtual unsigned int drawText(Form* form, const Rectangle& clip);
 
     /**
      * The text displayed by this label.

+ 9 - 2
gameplay/src/MeshBatch.cpp

@@ -7,7 +7,7 @@ namespace gameplay
 
 MeshBatch::MeshBatch(const VertexFormat& vertexFormat, Mesh::PrimitiveType primitiveType, Material* material, bool indexed, unsigned int initialCapacity, unsigned int growSize)
     : _vertexFormat(vertexFormat), _primitiveType(primitiveType), _material(material), _indexed(indexed), _capacity(0), _growSize(growSize),
-      _vertexCapacity(0), _indexCapacity(0), _vertexCount(0), _indexCount(0), _vertices(NULL), _verticesPtr(NULL), _indices(NULL), _indicesPtr(NULL)
+    _vertexCapacity(0), _indexCapacity(0), _vertexCount(0), _indexCount(0), _vertices(NULL), _verticesPtr(NULL), _indices(NULL), _indicesPtr(NULL), _started(false)
 {
     resize(initialCapacity);
 }
@@ -221,17 +221,24 @@ void MeshBatch::add(const float* vertices, unsigned int vertexCount, const unsig
 {
     add(vertices, sizeof(float), vertexCount, indices, indexCount);
 }
-    
+
 void MeshBatch::start()
 {
     _vertexCount = 0;
     _indexCount = 0;
     _verticesPtr = _vertices;
     _indicesPtr = _indices;
+    _started = true;
+}
+
+bool MeshBatch::isStarted() const
+{
+    return _started;
 }
 
 void MeshBatch::finish()
 {
+    _started = false;
 }
 
 void MeshBatch::draw()

+ 6 - 0
gameplay/src/MeshBatch.h

@@ -126,6 +126,11 @@ public:
      */
     void start();
 
+    /**
+    * Determines if the batch has been started and not yet finished.
+    */
+    bool isStarted() const;
+
     /**
      * Indicates that batching is complete and prepares the batch for drawing.
      */
@@ -173,6 +178,7 @@ private:
     unsigned char* _verticesPtr;
     unsigned short* _indices;
     unsigned short* _indicesPtr;
+    bool _started;
 
 };
 

+ 9 - 4
gameplay/src/RadioButton.cpp

@@ -193,10 +193,10 @@ void RadioButton::update(const Control* container, const Vector2& offset)
     }
 }
 
-void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+unsigned int RadioButton::drawImages(Form* form, const Rectangle& clip)
 {
-    GP_ASSERT(spriteBatch);
-    GP_ASSERT(_image);
+    if (!_image)
+        return 0;
 
     // Left, v-center.
     // TODO: Set an alignment for radio button images.   
@@ -217,7 +217,12 @@ void RadioButton::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
 
     Vector2 pos(_viewportBounds.x, _viewportBounds.y + _viewportBounds.height * 0.5f - size.y * 0.5f);
 
-    spriteBatch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+    SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+    startBatch(form, batch);
+    batch->draw(pos.x, pos.y, size.x, size.y, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+    finishBatch(form, batch);
+
+    return 1;
 }
 
 const char* RadioButton::getType() const

+ 2 - 5
gameplay/src/RadioButton.h

@@ -159,12 +159,9 @@ protected:
     void update(const Control* container, const Vector2& offset);
 
     /**
-     * Draw the images associated with this control.
-     *
-     * @param spriteBatch The sprite batch containing this control's icons.
-     * @param clip The clipping rectangle of this control's parent container.
+     * @see Control::drawImages
      */
-    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    unsigned int drawImages(Form* form, const Rectangle& clip);
 
     /**
      * Clear the _selected flag of all radio buttons in the given group.

+ 24 - 27
gameplay/src/Slider.cpp

@@ -388,26 +388,10 @@ void Slider::update(const Control* container, const Vector2& offset)
     }
 }
 
-void Slider::draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targetHeight)
+unsigned int Slider::drawImages(Form* form, const Rectangle& clip)
 {
-    if (!_visible)
-        return;
-
-    spriteBatch->start();
-    drawBorder(spriteBatch, clip);
-    drawImages(spriteBatch, clip);
-    spriteBatch->finish();
-
-    drawText(clip);
-}
-
-void Slider::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
-{
-    GP_ASSERT(spriteBatch);
-    GP_ASSERT(_minImage);
-    GP_ASSERT(_maxImage);
-    GP_ASSERT(_markerImage);
-    GP_ASSERT(_trackImage);
+    if (!(_minImage && _maxImage && _markerImage && _trackImage))
+        return 0;
 
     // TODO: Vertical slider.
 
@@ -434,37 +418,50 @@ void Slider::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
     markerColor.w *= _opacity;
     trackColor.w *= _opacity;
 
+    SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+    startBatch(form, batch);
+
     // Draw order: track, caps, marker.
     float midY = _viewportBounds.y + (_viewportBounds.height) * 0.5f;
     Vector2 pos(_viewportBounds.x, midY - trackRegion.height * 0.5f);
-    spriteBatch->draw(pos.x, pos.y, _viewportBounds.width, trackRegion.height, track.u1, track.v1, track.u2, track.v2, trackColor, _viewportClipBounds);
+    batch->draw(pos.x, pos.y, _viewportBounds.width, trackRegion.height, track.u1, track.v1, track.u2, track.v2, trackColor, _viewportClipBounds);
 
     pos.y = midY - minCapRegion.height * 0.5f;
     pos.x -= minCapRegion.width * 0.5f;
-    spriteBatch->draw(pos.x, pos.y, minCapRegion.width, minCapRegion.height, minCap.u1, minCap.v1, minCap.u2, minCap.v2, minCapColor, _viewportClipBounds);
+    batch->draw(pos.x, pos.y, minCapRegion.width, minCapRegion.height, minCap.u1, minCap.v1, minCap.u2, minCap.v2, minCapColor, _viewportClipBounds);
 
     pos.x = _viewportBounds.x + _viewportBounds.width - maxCapRegion.width * 0.5f;
-    spriteBatch->draw(pos.x, pos.y, maxCapRegion.width, maxCapRegion.height, maxCap.u1, maxCap.v1, maxCap.u2, maxCap.v2, maxCapColor, _viewportClipBounds);
+    batch->draw(pos.x, pos.y, maxCapRegion.width, maxCapRegion.height, maxCap.u1, maxCap.v1, maxCap.u2, maxCap.v2, maxCapColor, _viewportClipBounds);
 
     // Percent across.
     float markerPosition = (_value - _min) / (_max - _min);
     markerPosition *= _viewportBounds.width - minCapRegion.width * 0.5f - maxCapRegion.width * 0.5f - markerRegion.width;
     pos.x = _viewportBounds.x + minCapRegion.width * 0.5f + markerPosition;
     pos.y = midY - markerRegion.height / 2.0f;
-    spriteBatch->draw(pos.x, pos.y, markerRegion.width, markerRegion.height, marker.u1, marker.v1, marker.u2, marker.v2, markerColor, _viewportClipBounds);
+    batch->draw(pos.x, pos.y, markerRegion.width, markerRegion.height, marker.u1, marker.v1, marker.u2, marker.v2, markerColor, _viewportClipBounds);
+
+    finishBatch(form, batch);
+
+    return 4;
 }
 
-void Slider::drawText(const Rectangle& clip)
+unsigned int Slider::drawText(Form* form, const Rectangle& clip)
 {
-    Label::drawText(clip);
+    unsigned int drawCalls = Label::drawText(form, clip);
 
     if (_valueTextVisible && _font)
     {
         Control::State state = getState();
-        _font->start();
+
+        SpriteBatch* batch = _font->getSpriteBatch();
+        startBatch(form, batch);
         _font->drawText(_valueText.c_str(), _textBounds, _textColor, getFontSize(state), _valueTextAlignment, true, getTextRightToLeft(state), &_viewportClipBounds);
-        _font->finish();
+        finishBatch(form, batch);
+
+        ++drawCalls;
     }
+
+    return drawCalls;
 }
 
 const char* Slider::getType() const

+ 4 - 14
gameplay/src/Slider.h

@@ -236,24 +236,14 @@ protected:
     bool keyEvent(Keyboard::KeyEvent evt, int key);
 
     /**
-     * @see Control::draw
+     * @see Control::drawImages
      */
-    void draw(SpriteBatch* spriteBatch, const Rectangle& clip, float targetHeight);
+    unsigned int drawImages(Form* form, const Rectangle& clip);
 
     /**
-     * Draw the images associated with this control.
-     *
-     * @param spriteBatch The sprite batch containing this control's icons.
-     * @param clip The clipping rectangle of this control's parent container.
-     */
-    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
-
-    /**
-     * Draw this slider's text.
-     *
-     * @param clip The clipping rectangle of this slider's parent container.
+     * @see Control::drawText
      */
-    void drawText(const Rectangle& clip);
+    unsigned int drawText(Form* form, const Rectangle& clip);
 
     /**
      * Called when a slider's properties change. Updates this slider's internal rendering

+ 14 - 1
gameplay/src/SpriteBatch.cpp

@@ -105,6 +105,7 @@ SpriteBatch* SpriteBatch::create(Texture* texture,  Effect* effect, unsigned int
     material->getStateBlock()->setBlend(true);
     material->getStateBlock()->setBlendSrc(RenderState::BLEND_SRC_ALPHA);
     material->getStateBlock()->setBlendDst(RenderState::BLEND_ONE_MINUS_SRC_ALPHA);
+    //material->getStateBlock()->setDepthFunction(RenderState::DEPTH_LEQUAL);
 
     // Bind the texture to the material as a sampler
     Texture::Sampler* sampler = Texture::Sampler::create(texture); // +ref texture
@@ -144,6 +145,11 @@ void SpriteBatch::start()
     _batch->start();
 }
 
+bool SpriteBatch::isStarted() const
+{
+    return _batch->isStarted();
+}
+
 void SpriteBatch::draw(const Rectangle& dst, const Rectangle& src, const Vector4& color)
 {
     // Calculate uvs.
@@ -298,9 +304,16 @@ void SpriteBatch::draw(float x, float y, float width, float height, float u1, fl
 
 void SpriteBatch::draw(float x, float y, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, const Rectangle& clip)
 {
+    draw(x, y, 0, width, height, u1, v1, u2, v2, color, clip);
+}
+
+void SpriteBatch::draw(float x, float y, float z, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, const Rectangle& clip)
+{
+    // TODO: Perform software clipping instead of culling the entire sprite.
+
     // Only draw if at least part of the sprite is within the clip region.
     if (clipSprite(clip, x, y, width, height, u1, v1, u2, v2))
-        draw(x, y, 0, width, height, u1, v1, u2, v2, color);
+        draw(x, y, z, width, height, u1, v1, u2, v2, color);
 }
 
 void SpriteBatch::addSprite(float x, float y, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, SpriteBatch::SpriteVertex* vertices)

+ 24 - 0
gameplay/src/SpriteBatch.h

@@ -96,6 +96,13 @@ public:
      */
     void start();
 
+    /**
+     * Determines if the sprite batch has been started but not yet finished.
+     *
+     * @return True if the batch has been started and not finished.
+     */
+    bool isStarted() const;
+
     /**
      * Draws a single sprite.
      * 
@@ -220,6 +227,23 @@ public:
      */
     void draw(float x, float y, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, const Rectangle& clip);
 
+    /**
+     * Draws a single sprite, clipped within a rectangle.
+     * 
+     * @param x The x coordinate.
+     * @param y The y coordinate.
+     * @param z The z coordinate.
+     * @param width The sprite width.
+     * @param height The sprite height
+     * @param u1 Texture coordinate.
+     * @param v1 Texture coordinate.
+     * @param u2 Texture coordinate.
+     * @param v2 Texture coordinate.
+     * @param color The color to tint the sprite. Use white for no tint.
+     * @param clip The clip rectangle.
+     */
+    void draw(float x, float y, float z, float width, float height, float u1, float v1, float u2, float v2, const Vector4& color, const Rectangle& clip);
+
     /**
      * Draws a single sprite.
      * 

+ 31 - 20
gameplay/src/TextBox.cpp

@@ -324,7 +324,7 @@ void TextBox::update(const Control* container, const Vector2& offset)
     _caretImage = getImage("textCaret", state);
 }
 
-void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
+unsigned int TextBox::drawImages(Form* form, const Rectangle& clip)
 {
     Control::State state = getState();
 
@@ -334,7 +334,6 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
         const Rectangle& region = _caretImage->getRegion();
         if (!region.isEmpty())
         {
-            GP_ASSERT(spriteBatch);
             const Theme::UVs uvs = _caretImage->getUVs();
             Vector4 color = _caretImage->getColor();
             color.w *= _opacity;
@@ -346,11 +345,39 @@ void TextBox::drawImages(SpriteBatch* spriteBatch, const Rectangle& clip)
             Vector2 point;
             font->getLocationAtIndex(getDisplayedText().c_str(), _textBounds, fontSize, &point, _caretLocation, 
                  getTextAlignment(state), true, getTextRightToLeft(state));
-            spriteBatch->draw(point.x - caretWidth * 0.5f, point.y, caretWidth, fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+
+            SpriteBatch* batch = _style->getTheme()->getSpriteBatch();
+            startBatch(form, batch);
+            batch->draw(point.x - caretWidth * 0.5f, point.y, caretWidth, fontSize, uvs.u1, uvs.v1, uvs.u2, uvs.v2, color, _viewportClipBounds);
+            finishBatch(form, batch);
+
+            return 1;
         }
     }
 
-    _dirty = false;
+    return 0;
+}
+
+unsigned int TextBox::drawText(Form* form, const Rectangle& clip)
+{
+    if (_text.size() <= 0)
+        return 0;
+
+    // Draw the text.
+    if (_font)
+    {
+        Control::State state = getState();
+        const std::string displayedText = getDisplayedText();
+
+        SpriteBatch* batch = _font->getSpriteBatch();
+        startBatch(form, batch);
+        _font->drawText(displayedText.c_str(), _textBounds, _textColor, getFontSize(state), getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
+        finishBatch(form, batch);
+
+        return 1;
+    }
+
+    return 0;
 }
 
 void TextBox::setCaretLocation(int x, int y)
@@ -445,22 +472,6 @@ TextBox::InputMode TextBox::getInputMode() const
     return _inputMode;
 }
 
-void TextBox::drawText(const Rectangle& clip)
-{
-    if (_text.size() <= 0)
-        return;
-
-    // Draw the text.
-    if (_font)
-    {
-        Control::State state = getState();
-        const std::string displayedText = getDisplayedText();
-        _font->start();
-        _font->drawText(displayedText.c_str(), _textBounds, _textColor, getFontSize(state), getTextAlignment(state), true, getTextRightToLeft(state), &_viewportClipBounds);
-        _font->finish();
-    }
-}
-
 TextBox::InputMode TextBox::getInputMode(const char* inputMode)
 {
     if (!inputMode)

+ 4 - 10
gameplay/src/TextBox.h

@@ -190,20 +190,14 @@ protected:
     void update(const Control* container, const Vector2& offset);
 
     /**
-     * Draw the images associated with this control.
-     *
-     * @param spriteBatch The sprite batch containing this control's icons.
-     * @param clip The clipping rectangle of this control's parent container.
+     * @see Control::drawImages
      */
-    void drawImages(SpriteBatch* spriteBatch, const Rectangle& clip);
+    unsigned int drawImages(Form* form, const Rectangle& clip);
 
     /**
-     * Draw this textbox's text.
-     *
-     * @param clip The clipping rectangle of this textbox's
-     * parent container.
+     * @see Control::drawText
      */
-    virtual void drawText(const Rectangle& clip);
+    unsigned int drawText(Form* form, const Rectangle& clip);
 
     /**
      * Gets an InputMode by string.

+ 7 - 2
gameplay/src/Theme.h

@@ -336,6 +336,13 @@ public:
      */
     Theme::Style* getEmptyStyle();
 
+    /**
+     * Returns the sprite batch for this theme.
+     *
+     * @return The theme's sprite batch.
+     */
+    SpriteBatch* getSpriteBatch() const;
+
 private:
 
     /**
@@ -470,8 +477,6 @@ private:
 
     void setProjectionMatrix(const Matrix& matrix);
 
-    SpriteBatch* getSpriteBatch() const;
-
     static void generateUVs(float tw, float th, float x, float y, float width, float height, UVs* uvs);
 
     void lookUpSprites(const Properties* overlaySpace, ImageList** imageList, ThemeImage** mouseCursor, Skin** skin);

+ 73 - 0
gameplay/src/lua/lua_Form.cpp

@@ -106,6 +106,7 @@ void luaRegister_Form()
         {"getZIndex", lua_Form_getZIndex},
         {"hasFocus", lua_Form_hasFocus},
         {"insertControl", lua_Form_insertControl},
+        {"isBatchingEnabled", lua_Form_isBatchingEnabled},
         {"isChild", lua_Form_isChild},
         {"isContainer", lua_Form_isContainer},
         {"isEnabled", lua_Form_isEnabled},
@@ -128,6 +129,7 @@ void luaRegister_Form()
         {"setAnimationPropertyValue", lua_Form_setAnimationPropertyValue},
         {"setAutoHeight", lua_Form_setAutoHeight},
         {"setAutoWidth", lua_Form_setAutoWidth},
+        {"setBatchingEnabled", lua_Form_setBatchingEnabled},
         {"setBorder", lua_Form_setBorder},
         {"setBounds", lua_Form_setBounds},
         {"setCanFocus", lua_Form_setCanFocus},
@@ -3308,6 +3310,41 @@ int lua_Form_insertControl(lua_State* state)
     return 0;
 }
 
+int lua_Form_isBatchingEnabled(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Form* instance = getInstance(state);
+                bool result = instance->isBatchingEnabled();
+
+                // Push the return value onto the stack.
+                lua_pushboolean(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Form_isBatchingEnabled - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Form_isChild(lua_State* state)
 {
     // Get the number of parameters.
@@ -4223,6 +4260,42 @@ int lua_Form_setAutoWidth(lua_State* state)
     return 0;
 }
 
+int lua_Form_setBatchingEnabled(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 2:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                lua_type(state, 2) == LUA_TBOOLEAN)
+            {
+                // Get parameter 1 off the stack.
+                bool param1 = gameplay::ScriptUtil::luaCheckBool(state, 2);
+
+                Form* instance = getInstance(state);
+                instance->setBatchingEnabled(param1);
+                
+                return 0;
+            }
+
+            lua_pushstring(state, "lua_Form_setBatchingEnabled - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 2).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Form_setBorder(lua_State* state)
 {
     // Get the number of parameters.

+ 2 - 0
gameplay/src/lua/lua_Form.h

@@ -69,6 +69,7 @@ int lua_Form_getY(lua_State* state);
 int lua_Form_getZIndex(lua_State* state);
 int lua_Form_hasFocus(lua_State* state);
 int lua_Form_insertControl(lua_State* state);
+int lua_Form_isBatchingEnabled(lua_State* state);
 int lua_Form_isChild(lua_State* state);
 int lua_Form_isContainer(lua_State* state);
 int lua_Form_isEnabled(lua_State* state);
@@ -91,6 +92,7 @@ int lua_Form_setAlignment(lua_State* state);
 int lua_Form_setAnimationPropertyValue(lua_State* state);
 int lua_Form_setAutoHeight(lua_State* state);
 int lua_Form_setAutoWidth(lua_State* state);
+int lua_Form_setBatchingEnabled(lua_State* state);
 int lua_Form_setBorder(lua_State* state);
 int lua_Form_setBounds(lua_State* state);
 int lua_Form_setCanFocus(lua_State* state);

+ 36 - 0
gameplay/src/lua/lua_MeshBatch.cpp

@@ -18,6 +18,7 @@ void luaRegister_MeshBatch()
         {"finish", lua_MeshBatch_finish},
         {"getCapacity", lua_MeshBatch_getCapacity},
         {"getMaterial", lua_MeshBatch_getMaterial},
+        {"isStarted", lua_MeshBatch_isStarted},
         {"setCapacity", lua_MeshBatch_setCapacity},
         {"start", lua_MeshBatch_start},
         {NULL, NULL}
@@ -316,6 +317,41 @@ int lua_MeshBatch_getMaterial(lua_State* state)
     return 0;
 }
 
+int lua_MeshBatch_isStarted(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                MeshBatch* instance = getInstance(state);
+                bool result = instance->isStarted();
+
+                // Push the return value onto the stack.
+                lua_pushboolean(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_MeshBatch_isStarted - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_MeshBatch_setCapacity(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_MeshBatch.h

@@ -11,6 +11,7 @@ int lua_MeshBatch_draw(lua_State* state);
 int lua_MeshBatch_finish(lua_State* state);
 int lua_MeshBatch_getCapacity(lua_State* state);
 int lua_MeshBatch_getMaterial(lua_State* state);
+int lua_MeshBatch_isStarted(lua_State* state);
 int lua_MeshBatch_setCapacity(lua_State* state);
 int lua_MeshBatch_start(lua_State* state);
 int lua_MeshBatch_static_create(lua_State* state);

+ 64 - 0
gameplay/src/lua/lua_Rectangle.cpp

@@ -31,6 +31,7 @@ void luaRegister_Rectangle()
     {
         {"combine", lua_Rectangle_static_combine},
         {"empty", lua_Rectangle_static_empty},
+        {"intersect", lua_Rectangle_static_intersect},
         {NULL, NULL}
     };
     std::vector<std::string> scopePath;
@@ -843,6 +844,69 @@ int lua_Rectangle_static_empty(lua_State* state)
     return 0;
 }
 
+int lua_Rectangle_static_intersect(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 3:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA || lua_type(state, 1) == LUA_TNIL) &&
+                (lua_type(state, 2) == LUA_TUSERDATA || lua_type(state, 2) == LUA_TNIL) &&
+                (lua_type(state, 3) == LUA_TUSERDATA || lua_type(state, 3) == LUA_TTABLE || lua_type(state, 3) == LUA_TNIL))
+            {
+                // Get parameter 1 off the stack.
+                bool param1Valid;
+                gameplay::ScriptUtil::LuaArray<Rectangle> param1 = gameplay::ScriptUtil::getObjectPointer<Rectangle>(1, "Rectangle", true, &param1Valid);
+                if (!param1Valid)
+                {
+                    lua_pushstring(state, "Failed to convert parameter 1 to type 'Rectangle'.");
+                    lua_error(state);
+                }
+
+                // Get parameter 2 off the stack.
+                bool param2Valid;
+                gameplay::ScriptUtil::LuaArray<Rectangle> param2 = gameplay::ScriptUtil::getObjectPointer<Rectangle>(2, "Rectangle", true, &param2Valid);
+                if (!param2Valid)
+                {
+                    lua_pushstring(state, "Failed to convert parameter 2 to type 'Rectangle'.");
+                    lua_error(state);
+                }
+
+                // Get parameter 3 off the stack.
+                bool param3Valid;
+                gameplay::ScriptUtil::LuaArray<Rectangle> param3 = gameplay::ScriptUtil::getObjectPointer<Rectangle>(3, "Rectangle", false, &param3Valid);
+                if (!param3Valid)
+                {
+                    lua_pushstring(state, "Failed to convert parameter 3 to type 'Rectangle'.");
+                    lua_error(state);
+                }
+
+                bool result = Rectangle::intersect(*param1, *param2, param3);
+
+                // Push the return value onto the stack.
+                lua_pushboolean(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Rectangle_static_intersect - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 3).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Rectangle_top(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Rectangle.h

@@ -19,6 +19,7 @@ int lua_Rectangle_set(lua_State* state);
 int lua_Rectangle_setPosition(lua_State* state);
 int lua_Rectangle_static_combine(lua_State* state);
 int lua_Rectangle_static_empty(lua_State* state);
+int lua_Rectangle_static_intersect(lua_State* state);
 int lua_Rectangle_top(lua_State* state);
 int lua_Rectangle_width(lua_State* state);
 int lua_Rectangle_x(lua_State* state);

+ 97 - 0
gameplay/src/lua/lua_SpriteBatch.cpp

@@ -19,6 +19,7 @@ void luaRegister_SpriteBatch()
         {"getProjectionMatrix", lua_SpriteBatch_getProjectionMatrix},
         {"getSampler", lua_SpriteBatch_getSampler},
         {"getStateBlock", lua_SpriteBatch_getStateBlock},
+        {"isStarted", lua_SpriteBatch_isStarted},
         {"setProjectionMatrix", lua_SpriteBatch_setProjectionMatrix},
         {"start", lua_SpriteBatch_start},
         {NULL, NULL}
@@ -587,6 +588,67 @@ int lua_SpriteBatch_draw(lua_State* state)
                 }
             } while (0);
 
+            do
+            {
+                if ((lua_type(state, 1) == LUA_TUSERDATA) &&
+                    lua_type(state, 2) == LUA_TNUMBER &&
+                    lua_type(state, 3) == LUA_TNUMBER &&
+                    lua_type(state, 4) == LUA_TNUMBER &&
+                    lua_type(state, 5) == LUA_TNUMBER &&
+                    lua_type(state, 6) == LUA_TNUMBER &&
+                    lua_type(state, 7) == LUA_TNUMBER &&
+                    lua_type(state, 8) == LUA_TNUMBER &&
+                    lua_type(state, 9) == LUA_TNUMBER &&
+                    lua_type(state, 10) == LUA_TNUMBER &&
+                    (lua_type(state, 11) == LUA_TUSERDATA || lua_type(state, 11) == LUA_TNIL) &&
+                    (lua_type(state, 12) == LUA_TUSERDATA || lua_type(state, 12) == LUA_TNIL))
+                {
+                    // Get parameter 1 off the stack.
+                    float param1 = (float)luaL_checknumber(state, 2);
+
+                    // Get parameter 2 off the stack.
+                    float param2 = (float)luaL_checknumber(state, 3);
+
+                    // Get parameter 3 off the stack.
+                    float param3 = (float)luaL_checknumber(state, 4);
+
+                    // Get parameter 4 off the stack.
+                    float param4 = (float)luaL_checknumber(state, 5);
+
+                    // Get parameter 5 off the stack.
+                    float param5 = (float)luaL_checknumber(state, 6);
+
+                    // Get parameter 6 off the stack.
+                    float param6 = (float)luaL_checknumber(state, 7);
+
+                    // Get parameter 7 off the stack.
+                    float param7 = (float)luaL_checknumber(state, 8);
+
+                    // Get parameter 8 off the stack.
+                    float param8 = (float)luaL_checknumber(state, 9);
+
+                    // Get parameter 9 off the stack.
+                    float param9 = (float)luaL_checknumber(state, 10);
+
+                    // Get parameter 10 off the stack.
+                    bool param10Valid;
+                    gameplay::ScriptUtil::LuaArray<Vector4> param10 = gameplay::ScriptUtil::getObjectPointer<Vector4>(11, "Vector4", true, &param10Valid);
+                    if (!param10Valid)
+                        break;
+
+                    // Get parameter 11 off the stack.
+                    bool param11Valid;
+                    gameplay::ScriptUtil::LuaArray<Rectangle> param11 = gameplay::ScriptUtil::getObjectPointer<Rectangle>(12, "Rectangle", true, &param11Valid);
+                    if (!param11Valid)
+                        break;
+
+                    SpriteBatch* instance = getInstance(state);
+                    instance->draw(param1, param2, param3, param4, param5, param6, param7, param8, param9, *param10, *param11);
+                    
+                    return 0;
+                }
+            } while (0);
+
             do
             {
                 if ((lua_type(state, 1) == LUA_TUSERDATA) &&
@@ -1087,6 +1149,41 @@ int lua_SpriteBatch_getStateBlock(lua_State* state)
     return 0;
 }
 
+int lua_SpriteBatch_isStarted(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                SpriteBatch* instance = getInstance(state);
+                bool result = instance->isStarted();
+
+                // Push the return value onto the stack.
+                lua_pushboolean(state, result);
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_SpriteBatch_isStarted - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_SpriteBatch_setProjectionMatrix(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_SpriteBatch.h

@@ -12,6 +12,7 @@ int lua_SpriteBatch_getMaterial(lua_State* state);
 int lua_SpriteBatch_getProjectionMatrix(lua_State* state);
 int lua_SpriteBatch_getSampler(lua_State* state);
 int lua_SpriteBatch_getStateBlock(lua_State* state);
+int lua_SpriteBatch_isStarted(lua_State* state);
 int lua_SpriteBatch_setProjectionMatrix(lua_State* state);
 int lua_SpriteBatch_start(lua_State* state);
 int lua_SpriteBatch_static_create(lua_State* state);

+ 45 - 0
gameplay/src/lua/lua_Theme.cpp

@@ -18,6 +18,7 @@ void luaRegister_Theme()
         {"addRef", lua_Theme_addRef},
         {"getEmptyStyle", lua_Theme_getEmptyStyle},
         {"getRefCount", lua_Theme_getRefCount},
+        {"getSpriteBatch", lua_Theme_getSpriteBatch},
         {"getStyle", lua_Theme_getStyle},
         {"release", lua_Theme_release},
         {NULL, NULL}
@@ -189,6 +190,50 @@ int lua_Theme_getRefCount(lua_State* state)
     return 0;
 }
 
+int lua_Theme_getSpriteBatch(lua_State* state)
+{
+    // Get the number of parameters.
+    int paramCount = lua_gettop(state);
+
+    // Attempt to match the parameters to a valid binding.
+    switch (paramCount)
+    {
+        case 1:
+        {
+            if ((lua_type(state, 1) == LUA_TUSERDATA))
+            {
+                Theme* instance = getInstance(state);
+                void* returnPtr = (void*)instance->getSpriteBatch();
+                if (returnPtr)
+                {
+                    gameplay::ScriptUtil::LuaObject* object = (gameplay::ScriptUtil::LuaObject*)lua_newuserdata(state, sizeof(gameplay::ScriptUtil::LuaObject));
+                    object->instance = returnPtr;
+                    object->owns = false;
+                    luaL_getmetatable(state, "SpriteBatch");
+                    lua_setmetatable(state, -2);
+                }
+                else
+                {
+                    lua_pushnil(state);
+                }
+
+                return 1;
+            }
+
+            lua_pushstring(state, "lua_Theme_getSpriteBatch - Failed to match the given parameters to a valid function signature.");
+            lua_error(state);
+            break;
+        }
+        default:
+        {
+            lua_pushstring(state, "Invalid number of parameters (expected 1).");
+            lua_error(state);
+            break;
+        }
+    }
+    return 0;
+}
+
 int lua_Theme_getStyle(lua_State* state)
 {
     // Get the number of parameters.

+ 1 - 0
gameplay/src/lua/lua_Theme.h

@@ -9,6 +9,7 @@ int lua_Theme__gc(lua_State* state);
 int lua_Theme_addRef(lua_State* state);
 int lua_Theme_getEmptyStyle(lua_State* state);
 int lua_Theme_getRefCount(lua_State* state);
+int lua_Theme_getSpriteBatch(lua_State* state);
 int lua_Theme_getStyle(lua_State* state);
 int lua_Theme_release(lua_State* state);
 int lua_Theme_static_create(lua_State* state);

+ 1 - 0
samples/browser/res/common/forms/formBasicControls.form

@@ -3,6 +3,7 @@ form basicControls
     layout = LAYOUT_ABSOLUTE
     size = 600, 600
 	consumeInputEvents = true
+    batchingEnabled = false
     	
 	label title
 	{

+ 1 - 0
samples/browser/res/common/forms/formZOrder.form

@@ -5,6 +5,7 @@ form zOrder
     size = 600, 600
     scroll = SCROLL_BOTH
 	consumeInputEvents = true
+    batchingEnabled = false
 	
     label label1
     {

+ 1 - 2
samples/browser/src/FormsSample.cpp

@@ -150,8 +150,7 @@ void FormsSample::formChanged()
     _formNode->setScale(scale, scale, 1.0f);
     _formNodeParent->setTranslation(0, 0, -1.5f);
     _formNode->setTranslation(-0.5f, -0.5f, 0);
-    _activeForm->setAlignment(Control::ALIGN_VCENTER_HCENTER);
-    //_formNode->setForm(_activeForm);
+    _formNode->setForm(_activeForm);
 }
 
 void FormsSample::createSampleForm()