// 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; } }