// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// IndexDataManager.cpp: Defines the IndexDataManager, a class that
// runs the Buffer translation process for index buffers.
#include "IndexDataManager.h"
#include "Buffer.h"
#include "common/debug.h"
#include <string.h>
#include <algorithm>
namespace
{
enum { INITIAL_INDEX_BUFFER_SIZE = 4096 * sizeof(GLuint) };
}
namespace es2
{
IndexDataManager::IndexDataManager()
{
mStreamingBuffer = new StreamingIndexBuffer(INITIAL_INDEX_BUFFER_SIZE);
if(!mStreamingBuffer)
{
ERR("Failed to allocate the streaming index buffer.");
}
}
IndexDataManager::~IndexDataManager()
{
delete mStreamingBuffer;
}
void copyIndices(GLenum type, const void *input, GLsizei count, void *output)
{
if(type == GL_UNSIGNED_BYTE)
{
memcpy(output, input, count * sizeof(GLubyte));
}
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(type);
}
inline GLsizei getNumIndices(const std::vector<GLsizei>& restartIndices, size_t i, GLsizei count)
{
return restartIndices.empty() ? count :
((i == 0) ? restartIndices[0] : ((i == restartIndices.size()) ? (count - restartIndices[i - 1] - 1) : (restartIndices[i] - restartIndices[i - 1] - 1)));
}
void copyIndices(GLenum mode, GLenum type, const std::vector<GLsizei>& restartIndices, const void *input, GLsizei count, void* output)
{
size_t bytesPerIndex = 0;
const unsigned char* inPtr = static_cast<const unsigned char*>(input);
unsigned char* outPtr = static_cast<unsigned char*>(output);
switch(type)
{
case GL_UNSIGNED_BYTE:
bytesPerIndex = sizeof(GLubyte);
break;
case GL_UNSIGNED_INT:
bytesPerIndex = sizeof(GLuint);
break;
case GL_UNSIGNED_SHORT:
bytesPerIndex = sizeof(GLushort);
break;
default:
UNREACHABLE(type);
}
size_t numRestarts = restartIndices.size();
switch(mode)
{
case GL_TRIANGLES:
case GL_LINES:
case GL_POINTS:
{
GLsizei verticesPerPrimitive = (mode == GL_TRIANGLES) ? 3 : ((mode == GL_LINES) ? 2 : 1);
for(size_t i = 0; i <= numRestarts; ++i)
{
GLsizei numIndices = getNumIndices(restartIndices, i, count);
size_t numBytes = (numIndices / verticesPerPrimitive) * verticesPerPrimitive * bytesPerIndex;
if(numBytes > 0)
{
memcpy(outPtr, inPtr, numBytes);
outPtr += numBytes;
}
inPtr += (numIndices + 1) * bytesPerIndex;
}
}
break;
case GL_TRIANGLE_FAN:
for(size_t i = 0; i <= numRestarts; ++i)
{
GLsizei numIndices = getNumIndices(restartIndices, i, count);
GLsizei numTriangles = (numIndices - 2);
for(GLsizei tri = 0; tri < numTriangles; ++tri)
{
memcpy(outPtr, inPtr, bytesPerIndex);
outPtr += bytesPerIndex;
memcpy(outPtr, inPtr + ((tri + 1) * bytesPerIndex), bytesPerIndex + bytesPerIndex);
outPtr += bytesPerIndex + bytesPerIndex;
}
inPtr += (numIndices + 1) * bytesPerIndex;
}
break;
case GL_TRIANGLE_STRIP:
for(size_t i = 0; i <= numRestarts; ++i)
{
GLsizei numIndices = getNumIndices(restartIndices, i, count);
GLsizei numTriangles = (numIndices - 2);
for(GLsizei tri = 0; tri < numTriangles; ++tri)
{
if(tri & 1) // Reverse odd triangles
{
memcpy(outPtr, inPtr + ((tri + 1) * bytesPerIndex), bytesPerIndex);
outPtr += bytesPerIndex;
memcpy(outPtr, inPtr + ((tri + 0) * bytesPerIndex), bytesPerIndex);
outPtr += bytesPerIndex;
memcpy(outPtr, inPtr + ((tri + 2) * bytesPerIndex), bytesPerIndex);
outPtr += bytesPerIndex;
}
else
{
size_t numBytes = 3 * bytesPerIndex;
memcpy(outPtr, inPtr + (tri * bytesPerIndex), numBytes);
outPtr += numBytes;
}
}
inPtr += (numIndices + 1) * bytesPerIndex;
}
break;
case GL_LINE_LOOP:
for(size_t i = 0; i <= numRestarts; ++i)
{
GLsizei numIndices = getNumIndices(restartIndices, i, count);
if(numIndices >= 2)
{
GLsizei numLines = numIndices;
memcpy(outPtr, inPtr + (numIndices - 1) * bytesPerIndex, bytesPerIndex); // Last vertex
outPtr += bytesPerIndex;
memcpy(outPtr, inPtr, bytesPerIndex); // First vertex
outPtr += bytesPerIndex;
size_t bytesPerLine = 2 * bytesPerIndex;
for(GLsizei tri = 0; tri < (numLines - 1); ++tri)
{
memcpy(outPtr, inPtr + tri * bytesPerIndex, bytesPerLine);
outPtr += bytesPerLine;
}
}
inPtr += (numIndices + 1) * bytesPerIndex;
}
break;
case GL_LINE_STRIP:
for(size_t i = 0; i <= numRestarts; ++i)
{
GLsizei numIndices = getNumIndices(restartIndices, i, count);
GLsizei numLines = numIndices - 1;
size_t bytesPerLine = 2 * bytesPerIndex;
for(GLsizei tri = 0; tri < numLines; ++tri)
{
memcpy(outPtr, inPtr + tri * bytesPerIndex, bytesPerLine);
outPtr += bytesPerLine;
}
inPtr += (numIndices + 1) * bytesPerIndex;
}
break;
default:
UNREACHABLE(mode);
break;
}
}
template<class IndexType>
void computeRange(const IndexType *indices, GLsizei count, GLuint *minIndex, GLuint *maxIndex, std::vector<GLsizei>* restartIndices)
{
*maxIndex = 0;
*minIndex = MAX_ELEMENTS_INDICES;
for(GLsizei i = 0; i < count; i++)
{
if(restartIndices && indices[i] == IndexType(-1))
{
restartIndices->push_back(i);
continue;
}
if(*minIndex > indices[i]) *minIndex = indices[i];
if(*maxIndex < indices[i]) *maxIndex = indices[i];
}
}
void computeRange(GLenum type, const void *indices, GLsizei count, GLuint *minIndex, GLuint *maxIndex, std::vector<GLsizei>* restartIndices)
{
if(type == GL_UNSIGNED_BYTE)
{
computeRange(static_cast<const GLubyte*>(indices), count, minIndex, maxIndex, restartIndices);
}
else if(type == GL_UNSIGNED_INT)
{
computeRange(static_cast<const GLuint*>(indices), count, minIndex, maxIndex, restartIndices);
}
else if(type == GL_UNSIGNED_SHORT)
{
computeRange(static_cast<const GLushort*>(indices), count, minIndex, maxIndex, restartIndices);
}
else UNREACHABLE(type);
}
int recomputePrimitiveCount(GLenum mode, GLsizei count, const std::vector<GLsizei>& restartIndices, unsigned int* primitiveCount)
{
size_t numRestarts = restartIndices.size();
*primitiveCount = 0;
unsigned int countOffset = 0;
unsigned int vertexPerPrimitive = 0;
switch(mode)
{
case GL_TRIANGLES: // 3 vertex per primitive
++vertexPerPrimitive;
case GL_LINES: // 2 vertex per primitive
vertexPerPrimitive += 2;
for(size_t i = 0; i <= numRestarts; ++i)
{
unsigned int nbIndices = getNumIndices(restartIndices, i, count);
*primitiveCount += nbIndices / vertexPerPrimitive;
}
return vertexPerPrimitive;
case GL_TRIANGLE_FAN:
case GL_TRIANGLE_STRIP: // (N - 2) polygons, 3 vertex per primitive
++vertexPerPrimitive;
--countOffset;
case GL_LINE_STRIP: // (N - 1) polygons, 2 vertex per primitive
--countOffset;
case GL_LINE_LOOP: // N polygons, 2 vertex per primitive
vertexPerPrimitive += 2;
for(size_t i = 0; i <= numRestarts; ++i)
{
unsigned int nbIndices = getNumIndices(restartIndices, i, count);
*primitiveCount += (nbIndices >= vertexPerPrimitive) ? (nbIndices + countOffset) : 0;
}
return vertexPerPrimitive;
case GL_POINTS:
*primitiveCount = static_cast<unsigned int>(count - restartIndices.size());
return 1;
default:
UNREACHABLE(mode);
return -1;
}
}
GLenum IndexDataManager::prepareIndexData(GLenum mode, GLenum type, GLuint start, GLuint end, GLsizei count, Buffer *buffer, const void *indices, TranslatedIndexData *translated, bool primitiveRestart)
{
if(!mStreamingBuffer)
{
return GL_OUT_OF_MEMORY;
}
intptr_t offset = reinterpret_cast<intptr_t>(indices);
if(buffer != NULL)
{
if(typeSize(type) * count + offset > static_cast<std::size_t>(buffer->size()))
{
return GL_INVALID_OPERATION;
}
indices = static_cast<const GLubyte*>(buffer->data()) + offset;
}
std::vector<GLsizei>* restartIndices = primitiveRestart ? new std::vector<GLsizei>() : nullptr;
computeRange(type, indices, count, &translated->minIndex, &translated->maxIndex, restartIndices);
StreamingIndexBuffer *streamingBuffer = mStreamingBuffer;
sw::Resource *staticBuffer = buffer ? buffer->getResource() : NULL;
if(restartIndices)
{
int vertexPerPrimitive = recomputePrimitiveCount(mode, count, *restartIndices, &translated->primitiveCount);
if(vertexPerPrimitive == -1)
{
delete restartIndices;
return GL_INVALID_ENUM;
}
size_t streamOffset = 0;
int convertCount = translated->primitiveCount * vertexPerPrimitive;
streamingBuffer->reserveSpace(convertCount * typeSize(type), type);
void *output = streamingBuffer->map(typeSize(type) * convertCount, &streamOffset);
if(output == NULL)
{
delete restartIndices;
ERR("Failed to map index buffer.");
return GL_OUT_OF_MEMORY;
}
copyIndices(mode, type, *restartIndices, indices, count, output);
streamingBuffer->unmap();
translated->indexBuffer = streamingBuffer->getResource();
translated->indexOffset = static_cast<unsigned int>(streamOffset);
delete restartIndices;
}
else if(staticBuffer)
{
translated->indexBuffer = staticBuffer;
translated->indexOffset = static_cast<unsigned int>(offset);
}
else
{
size_t streamOffset = 0;
int convertCount = count;
streamingBuffer->reserveSpace(convertCount * typeSize(type), type);
void *output = streamingBuffer->map(typeSize(type) * convertCount, &streamOffset);
if(output == NULL)
{
ERR("Failed to map index buffer.");
return GL_OUT_OF_MEMORY;
}
copyIndices(type, indices, convertCount, output);
streamingBuffer->unmap();
translated->indexBuffer = streamingBuffer->getResource();
translated->indexOffset = static_cast<unsigned int>(streamOffset);
}
if(translated->minIndex < start || translated->maxIndex > end)
{
ERR("glDrawRangeElements: out of range access. Range provided: [%d -> %d]. Range used: [%d -> %d].", start, end, translated->minIndex, translated->maxIndex);
}
return GL_NO_ERROR;
}
std::size_t IndexDataManager::typeSize(GLenum type)
{
switch(type)
{
case GL_UNSIGNED_INT: return sizeof(GLuint);
case GL_UNSIGNED_SHORT: return sizeof(GLushort);
case GL_UNSIGNED_BYTE: return sizeof(GLubyte);
default: UNREACHABLE(type); return sizeof(GLushort);
}
}
StreamingIndexBuffer::StreamingIndexBuffer(size_t initialSize) : mIndexBuffer(NULL), mBufferSize(initialSize)
{
if(initialSize > 0)
{
mIndexBuffer = new sw::Resource(initialSize + 16);
if(!mIndexBuffer)
{
ERR("Out of memory allocating an index buffer of size %u.", initialSize);
}
}
mWritePosition = 0;
}
StreamingIndexBuffer::~StreamingIndexBuffer()
{
if(mIndexBuffer)
{
mIndexBuffer->destruct();
}
}
void *StreamingIndexBuffer::map(size_t requiredSpace, size_t *offset)
{
void *mapPtr = NULL;
if(mIndexBuffer)
{
mapPtr = (char*)mIndexBuffer->lock(sw::PUBLIC) + mWritePosition;
if(!mapPtr)
{
ERR(" Lock failed");
return NULL;
}
*offset = mWritePosition;
mWritePosition += requiredSpace;
}
return mapPtr;
}
void StreamingIndexBuffer::unmap()
{
if(mIndexBuffer)
{
mIndexBuffer->unlock();
}
}
void StreamingIndexBuffer::reserveSpace(size_t requiredSpace, GLenum type)
{
if(requiredSpace > mBufferSize)
{
if(mIndexBuffer)
{
mIndexBuffer->destruct();
mIndexBuffer = 0;
}
mBufferSize = std::max(requiredSpace, 2 * mBufferSize);
mIndexBuffer = new sw::Resource(mBufferSize + 16);
if(!mIndexBuffer)
{
ERR("Out of memory allocating an index buffer of size %u.", mBufferSize);
}
mWritePosition = 0;
}
else if(mWritePosition + requiredSpace > mBufferSize) // Recycle
{
if(mIndexBuffer)
{
mIndexBuffer->destruct();
mIndexBuffer = new sw::Resource(mBufferSize + 16);
}
mWritePosition = 0;
}
}
sw::Resource *StreamingIndexBuffer::getResource() const
{
return mIndexBuffer;
}
}