| // 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 (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); |
| if(restartIndices && restartIndices->empty()) |
| { |
| delete restartIndices; |
| restartIndices = nullptr; |
| } |
| |
| 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, staticBuffer ? buffer->data() : 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; |
| } |
| |
| } |