rewrite buffers implementation to support static buffers more efficiently
Bug=89
Trac #13565

Signed-off-by: Daniel Koch
Author: Nicolas Capens

git-svn-id: https://angleproject.googlecode.com/svn/trunk@526 736b8ea6-26fd-11df-bfd4-992fa37f6226
diff --git a/src/libGLESv2/geometry/IndexDataManager.cpp b/src/libGLESv2/geometry/IndexDataManager.cpp
index a8227a8..0dc25f9 100644
--- a/src/libGLESv2/geometry/IndexDataManager.cpp
+++ b/src/libGLESv2/geometry/IndexDataManager.cpp
@@ -13,7 +13,6 @@
 
 #include "libGLESv2/Buffer.h"
 #include "libGLESv2/mathutil.h"
-#include "libGLESv2/geometry/backend.h"
 
 namespace
 {
@@ -23,254 +22,352 @@
 namespace gl
 {
 
-IndexDataManager::IndexDataManager(Context *context, BufferBackEnd *backend)
-  : mContext(context), mBackend(backend), mIntIndicesSupported(backend->supportIntIndices())
+IndexDataManager::IndexDataManager(Context *context, IDirect3DDevice9 *device) : mDevice(device)
 {
-    mCountingBuffer = NULL;
-    mCountingBufferSize = 0;
+    mStreamingBufferShort = new StreamingIndexBuffer(mDevice, INITIAL_INDEX_BUFFER_SIZE, D3DFMT_INDEX16);
 
-    mLineLoopBuffer = NULL;
-
-    mStreamBufferShort = mBackend->createIndexBuffer(INITIAL_INDEX_BUFFER_SIZE, GL_UNSIGNED_SHORT);
-
-    if (mIntIndicesSupported)
+    if (context->supports32bitIndices())
     {
-        mStreamBufferInt = mBackend->createIndexBuffer(INITIAL_INDEX_BUFFER_SIZE, GL_UNSIGNED_INT);
+        mStreamingBufferInt = new StreamingIndexBuffer(mDevice, INITIAL_INDEX_BUFFER_SIZE, D3DFMT_INDEX32);
     }
     else
     {
-        mStreamBufferInt = NULL;
+        mStreamingBufferInt = NULL;
     }
 }
 
 IndexDataManager::~IndexDataManager()
 {
-    delete mStreamBufferShort;
-    delete mStreamBufferInt;
-    delete mCountingBuffer;
-    delete mLineLoopBuffer;
+    delete mStreamingBufferShort;
+    delete mStreamingBufferInt;
 }
 
-namespace
+void convertIndices(GLenum type, const void *input, GLsizei count, void *output)
 {
+    if (type == GL_UNSIGNED_BYTE)
+    {
+        const GLubyte *in = static_cast<const GLubyte*>(input);
+        GLushort *out = static_cast<GLushort*>(output);
 
-template <class InputIndexType, class OutputIndexType>
-void copyIndices(const InputIndexType *in, GLsizei count, OutputIndexType *out, GLuint *minIndex, GLuint *maxIndex)
+        for (GLsizei i = 0; i < count; i++)
+        {
+            out[i] = in[i];
+        }
+    }
+    else if (type == GL_UNSIGNED_INT)
+    {
+        memcpy(output, input, count * sizeof(GLuint));
+    }
+    else if (type == GL_UNSIGNED_SHORT)
+    {
+        memcpy(output, input, count * sizeof(GLushort));
+    }
+    else UNREACHABLE();
+}
+
+template <class IndexType>
+void computeRange(const IndexType *indices, GLsizei count, GLuint *minIndex, GLuint *maxIndex)
 {
-    InputIndexType first = *in;
-    GLuint minIndexSoFar = first;
-    GLuint maxIndexSoFar = first;
+    *minIndex = indices[0];
+    *maxIndex = indices[0];
 
     for (GLsizei i = 0; i < count; i++)
     {
-        if (minIndexSoFar > *in) minIndexSoFar = *in;
-        if (maxIndexSoFar < *in) maxIndexSoFar = *in;
-
-        *out++ = *in++;
+        if (*minIndex > indices[i]) *minIndex = indices[i];
+        if (*maxIndex < indices[i]) *maxIndex = indices[i];
     }
-
-    // It might be a line loop, so copy the loop index.
-    *out = first;
-
-    *minIndex = minIndexSoFar;
-    *maxIndex = maxIndexSoFar;
 }
 
-}
-
-GLenum IndexDataManager::preRenderValidate(GLenum mode, GLenum type, GLsizei count, Buffer *arrayElementBuffer, const void *indices, TranslatedIndexData *translated)
+void computeRange(GLenum type, const void *indices, GLsizei count, GLuint *minIndex, GLuint *maxIndex)
 {
-    ASSERT(type == GL_UNSIGNED_SHORT || type == GL_UNSIGNED_BYTE || type == GL_UNSIGNED_INT);
-    ASSERT(count > 0);
-
-    if (arrayElementBuffer != NULL)
+    if (type == GL_UNSIGNED_BYTE)
     {
-        GLsizei offset = reinterpret_cast<GLsizei>(indices);
+        computeRange(static_cast<const GLubyte*>(indices), count, minIndex, maxIndex);
+    }
+    else if (type == GL_UNSIGNED_INT)
+    {
+        computeRange(static_cast<const GLuint*>(indices), count, minIndex, maxIndex);
+    }
+    else if (type == GL_UNSIGNED_SHORT)
+    {
+        computeRange(static_cast<const GLushort*>(indices), count, minIndex, maxIndex);
+    }
+    else UNREACHABLE();
+}
 
-        if (typeSize(type) * count + offset > static_cast<std::size_t>(arrayElementBuffer->size()))
+GLenum IndexDataManager::prepareIndexData(GLenum type, GLsizei count, Buffer *buffer, const void *indices, TranslatedIndexData *translated)
+{
+    D3DFORMAT format = (type == GL_UNSIGNED_INT) ? D3DFMT_INDEX32 : D3DFMT_INDEX16;
+    intptr_t offset = reinterpret_cast<intptr_t>(indices);
+    bool alignedOffset = false;
+
+    if (buffer != NULL)
+    {
+        switch (type)
+        {
+          case GL_UNSIGNED_BYTE:  alignedOffset = (offset % sizeof(GLubyte) == 0);  break;
+          case GL_UNSIGNED_SHORT: alignedOffset = (offset % sizeof(GLushort) == 0); break;
+          case GL_UNSIGNED_INT:   alignedOffset = (offset % sizeof(GLuint) == 0);   break;
+          default: UNREACHABLE(); alignedOffset = false;
+        }
+
+        if (typeSize(type) * count + offset > static_cast<std::size_t>(buffer->size()))
         {
             return GL_INVALID_OPERATION;
         }
 
-        indices = static_cast<const GLubyte*>(arrayElementBuffer->data()) + offset;
+        indices = static_cast<const GLubyte*>(buffer->data()) + offset;
     }
 
-    translated->count = count;
+    StreamingIndexBuffer *streamingBuffer = (type == GL_UNSIGNED_INT) ? mStreamingBufferInt : mStreamingBufferShort;
 
-    std::size_t requiredSpace = spaceRequired(type, count);
+    StaticIndexBuffer *staticBuffer = buffer ? buffer->getIndexBuffer() : NULL;
+    IndexBuffer *indexBuffer = streamingBuffer;
+    UINT streamOffset = 0;
 
-    TranslatedIndexBuffer *streamIb = prepareIndexBuffer(type, requiredSpace);
-
-    size_t offset;
-    void *output = streamIb->map(requiredSpace, &offset);
-    if (output == NULL)
+    if (staticBuffer && staticBuffer->lookupType(type) && alignedOffset)
     {
-        ERR(" failed to map index buffer.");
-        return GL_OUT_OF_MEMORY;
-    }
+        indexBuffer = staticBuffer;
+        streamOffset = staticBuffer->lookupRange(offset, count, &translated->minIndex, &translated->maxIndex);
 
-    translated->buffer = streamIb;
-    translated->offset = offset;
-    translated->indexSize = indexSize(type);
-
-    if (type == GL_UNSIGNED_BYTE)
-    {
-        const GLubyte *in = static_cast<const GLubyte*>(indices);
-        GLushort *out = static_cast<GLushort*>(output);
-
-        copyIndices(in, count, out, &translated->minIndex, &translated->maxIndex);
-    }
-    else if (type == GL_UNSIGNED_INT)
-    {
-        const GLuint *in = static_cast<const GLuint*>(indices);
-
-        if (mIntIndicesSupported)
+        if (streamOffset == -1)
         {
-            GLuint *out = static_cast<GLuint*>(output);
-
-            copyIndices(in, count, out, &translated->minIndex, &translated->maxIndex);
-        }
-        else
-        {
-            // When 32-bit indices are unsupported, fake them by truncating to 16-bit.
-
-            GLushort *out = static_cast<GLushort*>(output);
-
-            copyIndices(in, count, out, &translated->minIndex, &translated->maxIndex);
+            streamOffset = (offset / typeSize(type)) * indexSize(format);
+            computeRange(type, indices, count, &translated->minIndex, &translated->maxIndex);
+            staticBuffer->addRange(offset, count, translated->minIndex, translated->maxIndex, streamOffset);
         }
     }
     else
     {
-        const GLushort *in = static_cast<const GLushort*>(indices);
-        GLushort *out = static_cast<GLushort*>(output);
+        int convertCount = count;
 
-        copyIndices(in, count, out, &translated->minIndex, &translated->maxIndex);
+        if (staticBuffer)
+        {
+            if (staticBuffer->size() == 0 && alignedOffset)
+            {
+                indexBuffer = staticBuffer;
+                convertCount = buffer->size() / typeSize(type);
+            }
+            else
+            {
+                buffer->invalidateStaticData();
+                staticBuffer = NULL;
+            }
+        }
+
+        indexBuffer->reserveSpace(convertCount * indexSize(format), type);
+
+        void *output = indexBuffer->map(indexSize(format) * convertCount, &streamOffset);
+        if (output == NULL)
+        {
+            ERR("Failed to map index buffer.");
+            return GL_OUT_OF_MEMORY;
+        }
+
+        convertIndices(type, staticBuffer ? buffer->data() : indices, convertCount, output);
+        indexBuffer->unmap();
+
+        computeRange(type, indices, count, &translated->minIndex, &translated->maxIndex);
+
+        if (staticBuffer)
+        {
+            streamOffset = (offset / typeSize(type)) * indexSize(format);
+            staticBuffer->addRange(offset, count, translated->minIndex, translated->maxIndex, streamOffset);
+        }
     }
 
-    streamIb->unmap();
+    translated->indexBuffer = indexBuffer->getBuffer();
+    translated->startIndex = streamOffset / indexSize(format);
 
     return GL_NO_ERROR;
 }
 
-std::size_t IndexDataManager::indexSize(GLenum type) const
+std::size_t IndexDataManager::indexSize(D3DFORMAT format) const
 {
-    return (type == GL_UNSIGNED_INT && mIntIndicesSupported) ? sizeof(GLuint) : sizeof(GLushort);
+    return (format == D3DFMT_INDEX32) ? sizeof(unsigned int) : sizeof(unsigned short);
 }
 
 std::size_t IndexDataManager::typeSize(GLenum type) const
 {
     switch (type)
     {
-      case GL_UNSIGNED_INT: return sizeof(GLuint);
+      case GL_UNSIGNED_INT:   return sizeof(GLuint);
       case GL_UNSIGNED_SHORT: return sizeof(GLushort);
-      default: UNREACHABLE();
-      case GL_UNSIGNED_BYTE: return sizeof(GLubyte);
+      case GL_UNSIGNED_BYTE:  return sizeof(GLubyte);
+      default: UNREACHABLE(); return sizeof(GLushort);
     }
 }
 
-std::size_t IndexDataManager::spaceRequired(GLenum type, GLsizei count) const
+IndexBuffer::IndexBuffer(IDirect3DDevice9 *device, UINT size, D3DFORMAT format) : mDevice(device), mBufferSize(size), mIndexBuffer(NULL)
 {
-    return (count + 1) * indexSize(type); // +1 because we always leave an extra for line loops
+    if (size > 0)
+    {
+        HRESULT result = device->CreateIndexBuffer(size, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, format, D3DPOOL_DEFAULT, &mIndexBuffer, NULL);
+
+        if (FAILED(result))
+        {
+            ERR("Out of memory allocating an index buffer of size %lu.", size);
+        }
+    }
 }
 
-TranslatedIndexBuffer *IndexDataManager::prepareIndexBuffer(GLenum type, std::size_t requiredSpace)
+IndexBuffer::~IndexBuffer()
 {
-    bool use32 = (type == GL_UNSIGNED_INT && mIntIndicesSupported);
-
-    TranslatedIndexBuffer *streamIb = use32 ? mStreamBufferInt : mStreamBufferShort;
-
-    if (requiredSpace > streamIb->size())
+    if (mIndexBuffer)
     {
-        std::size_t newSize = std::max(requiredSpace, 2 * streamIb->size());
-
-        TranslatedIndexBuffer *newStreamBuffer = mBackend->createIndexBuffer(newSize, use32 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT);
-
-        delete streamIb;
-
-        streamIb = newStreamBuffer;
-
-        if (use32)
-        {
-            mStreamBufferInt = streamIb;
-        }
-        else
-        {
-            mStreamBufferShort = streamIb;
-        }
+        mIndexBuffer->Release();
     }
-
-    streamIb->reserveSpace(requiredSpace);
-
-    return streamIb;
 }
 
-GLenum IndexDataManager::preRenderValidateUnindexed(GLenum mode, GLsizei count, TranslatedIndexData *indexInfo)
+IDirect3DIndexBuffer9 *IndexBuffer::getBuffer() const
 {
-    if (count >= 65535) return GL_OUT_OF_MEMORY;
+    return mIndexBuffer;
+}
 
-    if (mode == GL_LINE_LOOP)
+void IndexBuffer::unmap()
+{
+    if (mIndexBuffer)
     {
-        // For line loops, create a single-use buffer that runs 0 - count-1, 0.
-        delete mLineLoopBuffer;
-        mLineLoopBuffer = mBackend->createIndexBuffer((count+1) * sizeof(unsigned short), GL_UNSIGNED_SHORT);
-
-        unsigned short *indices = static_cast<unsigned short *>(mLineLoopBuffer->map());
-        if (indices == NULL)
-        {
-            ERR(" failed to map index buffer.");
-            return GL_OUT_OF_MEMORY;
-        }
-
-        for (int i = 0; i < count; i++)
-        {
-            indices[i] = i;
-        }
-
-        indices[count] = 0;
-
-        mLineLoopBuffer->unmap();
-
-        indexInfo->buffer = mLineLoopBuffer;
-        indexInfo->count = count + 1;
-        indexInfo->maxIndex = count - 1;
+        mIndexBuffer->Unlock();
     }
-    else if (mCountingBufferSize < count)
+}
+
+StreamingIndexBuffer::StreamingIndexBuffer(IDirect3DDevice9 *device, UINT initialSize, D3DFORMAT format) : IndexBuffer(device, initialSize, format)
+{
+    mWritePosition = 0;
+}
+
+StreamingIndexBuffer::~StreamingIndexBuffer()
+{
+}
+
+void *StreamingIndexBuffer::map(UINT requiredSpace, UINT *offset)
+{
+    void *mapPtr = NULL;
+
+    if (mIndexBuffer)
     {
-        mCountingBufferSize = std::max(static_cast<GLsizei>(ceilPow2(count)), mCountingBufferSize*2);
-
-        delete mCountingBuffer;
-        mCountingBuffer = mBackend->createIndexBuffer(count * sizeof(unsigned short), GL_UNSIGNED_SHORT);
-
-        unsigned short *indices = static_cast<unsigned short *>(mCountingBuffer->map());
-        if (indices == NULL)
+        HRESULT result = mIndexBuffer->Lock(mWritePosition, requiredSpace, &mapPtr, D3DLOCK_NOOVERWRITE);
+     
+        if (FAILED(result))
         {
-            ERR(" failed to map index buffer.");
-            return GL_OUT_OF_MEMORY;
+            ERR(" Lock failed with error 0x%08x", result);
+            return NULL;
         }
 
-        for (int i = 0; i < count; i++)
-        {
-            indices[i] = i;
-        }
-
-        mCountingBuffer->unmap();
-
-        indexInfo->buffer = mCountingBuffer;
-        indexInfo->count = count;
-        indexInfo->maxIndex = count - 1;
-    }
-    else
-    {
-        indexInfo->buffer = mCountingBuffer;
-        indexInfo->count = count;
-        indexInfo->maxIndex = count - 1;
+        *offset = mWritePosition;
+        mWritePosition += requiredSpace;
     }
 
-    indexInfo->indexSize = sizeof(unsigned short);
-    indexInfo->minIndex = 0;
-    indexInfo->offset = 0;
+    return mapPtr;
+}
 
-    return GL_NO_ERROR;
+void StreamingIndexBuffer::reserveSpace(UINT requiredSpace, GLenum type)
+{
+    if (requiredSpace > mBufferSize)
+    {
+        if (mIndexBuffer)
+        {
+            mIndexBuffer->Release();
+            mIndexBuffer = NULL;
+        }
+
+        mBufferSize = std::max(requiredSpace, 2 * mBufferSize);
+
+        HRESULT result = mDevice->CreateIndexBuffer(mBufferSize, D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, type == GL_UNSIGNED_INT ? D3DFMT_INDEX32 : D3DFMT_INDEX16, D3DPOOL_DEFAULT, &mIndexBuffer, NULL);
+    
+        if (FAILED(result))
+        {
+            ERR("Out of memory allocating a vertex buffer of size %lu.", mBufferSize);
+        }
+
+        mWritePosition = 0;
+    }
+    else if (mWritePosition + requiredSpace > mBufferSize)   // Recycle
+    {
+        void *dummy;
+        mIndexBuffer->Lock(0, 1, &dummy, D3DLOCK_DISCARD);
+        mIndexBuffer->Unlock();
+
+        mWritePosition = 0;
+    }
+}
+
+StaticIndexBuffer::StaticIndexBuffer(IDirect3DDevice9 *device) : IndexBuffer(device, 0, D3DFMT_UNKNOWN)
+{
+    mCacheType = GL_NONE;
+}
+
+StaticIndexBuffer::~StaticIndexBuffer()
+{
+}
+
+void *StaticIndexBuffer::map(UINT requiredSpace, UINT *offset)
+{
+    void *mapPtr = NULL;
+
+    if (mIndexBuffer)
+    {
+        HRESULT result = mIndexBuffer->Lock(0, requiredSpace, &mapPtr, 0);
+     
+        if (FAILED(result))
+        {
+            ERR(" Lock failed with error 0x%08x", result);
+            return NULL;
+        }
+
+        *offset = 0;
+    }
+
+    return mapPtr;
+}
+
+void StaticIndexBuffer::reserveSpace(UINT requiredSpace, GLenum type)
+{
+    if (!mIndexBuffer && mBufferSize == 0)
+    {
+        HRESULT result = mDevice->CreateIndexBuffer(requiredSpace, D3DUSAGE_WRITEONLY, type == GL_UNSIGNED_INT ? D3DFMT_INDEX32 : D3DFMT_INDEX16, D3DPOOL_DEFAULT, &mIndexBuffer, NULL);
+    
+        if (FAILED(result))
+        {
+            ERR("Out of memory allocating a vertex buffer of size %lu.", mBufferSize);
+        }
+
+        mBufferSize = requiredSpace;
+        mCacheType = type;
+    }
+    else if (mIndexBuffer && mBufferSize >= requiredSpace && mCacheType == type)
+    {
+        // Already allocated
+    }
+    else UNREACHABLE();   // Static index buffers can't be resized
+}
+
+bool StaticIndexBuffer::lookupType(GLenum type)
+{
+    return mCacheType == type;
+}
+
+UINT StaticIndexBuffer::lookupRange(intptr_t offset, GLsizei count, UINT *minIndex, UINT *maxIndex)
+{
+    for (unsigned int range = 0; range < mCache.size(); range++)
+    {
+        if (mCache[range].offset == offset && mCache[range].count == count)
+        {
+            *minIndex = mCache[range].minIndex;
+            *maxIndex = mCache[range].maxIndex;
+
+            return mCache[range].streamOffset;
+        }
+    }
+
+    return -1;
+}
+
+void StaticIndexBuffer::addRange(intptr_t offset, GLsizei count, UINT minIndex, UINT maxIndex, UINT streamOffset)
+{
+    IndexRange indexRange = {offset, count, minIndex, maxIndex, streamOffset};
+    mCache.push_back(indexRange);
 }
 
 }