blob: 13c95393635bf1abb8282ee37b70ec0658fb2867 [file] [log] [blame]
//
// Copyright 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// VertexArrayGL.cpp: Implements the class methods for VertexArrayGL.
#include "libANGLE/renderer/gl/VertexArrayGL.h"
#include "common/debug.h"
#include "common/mathutil.h"
#include "common/utilities.h"
#include "libANGLE/Buffer.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/gl/BufferGL.h"
#include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h"
namespace rx
{
VertexArrayGL::VertexArrayGL(const gl::VertexArray::Data &data, const FunctionsGL *functions, StateManagerGL *stateManager)
: VertexArrayImpl(data),
mFunctions(functions),
mStateManager(stateManager),
mVertexArrayID(0),
mAppliedElementArrayBuffer(0),
mStreamingElementArrayBufferSize(0),
mStreamingElementArrayBuffer(0),
mStreamingArrayBufferSize(0),
mStreamingArrayBuffer(0)
{
ASSERT(mFunctions);
ASSERT(mStateManager);
mFunctions->genVertexArrays(1, &mVertexArrayID);
// Set the cached vertex attribute array size
GLint maxVertexAttribs;
mFunctions->getIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
mAppliedAttributes.resize(maxVertexAttribs);
}
VertexArrayGL::~VertexArrayGL()
{
mStateManager->deleteVertexArray(mVertexArrayID);
mVertexArrayID = 0;
mStateManager->deleteBuffer(mStreamingElementArrayBuffer);
mStreamingElementArrayBufferSize = 0;
mStreamingElementArrayBuffer = 0;
mStateManager->deleteBuffer(mStreamingArrayBuffer);
mStreamingArrayBufferSize = 0;
mStreamingArrayBuffer = 0;
for (size_t idx = 0; idx < mAppliedAttributes.size(); idx++)
{
mAppliedAttributes[idx].buffer.set(nullptr);
}
}
void VertexArrayGL::setElementArrayBuffer(const gl::Buffer *buffer)
{
// If the buffer is being unbound/deleted, reset the currently applied buffer ID
// so that even if a new buffer is generated with the same ID, it will be re-bound.
if (buffer == nullptr && mAppliedElementArrayBuffer != mStreamingElementArrayBuffer)
{
mAppliedElementArrayBuffer = 0;
}
}
void VertexArrayGL::setAttribute(size_t idx, const gl::VertexAttribute &attr)
{
}
void VertexArrayGL::setAttributeDivisor(size_t idx, GLuint divisor)
{
}
void VertexArrayGL::enableAttribute(size_t idx, bool enabledState)
{
}
gl::Error VertexArrayGL::syncDrawArraysState(const std::vector<GLuint> &activeAttribLocations, GLint first, GLsizei count) const
{
return syncDrawState(activeAttribLocations, first, count, GL_NONE, nullptr, nullptr);
}
gl::Error VertexArrayGL::syncDrawElementsState(const std::vector<GLuint> &activeAttribLocations, GLsizei count,
GLenum type, const GLvoid *indices, const GLvoid **outIndices) const
{
return syncDrawState(activeAttribLocations, 0, count, type, indices, outIndices);
}
gl::Error VertexArrayGL::syncDrawState(const std::vector<GLuint> &activeAttribLocations, GLint first, GLsizei count, GLenum type, const GLvoid *indices, const GLvoid **outIndices) const
{
mStateManager->bindVertexArray(mVertexArrayID, mAppliedElementArrayBuffer);
// Check if any attributes need to be streamed, determines if the index range needs to be computed
bool attributesNeedStreaming = doAttributesNeedStreaming(activeAttribLocations);
// Determine if an index buffer needs to be streamed and the range of vertices that need to be copied
gl::RangeUI indexRange(0, 0);
if (type != GL_NONE)
{
gl::Error error = syncIndexData(count, type, indices, attributesNeedStreaming, &indexRange, outIndices);
if (error.isError())
{
return error;
}
}
else
{
// Not an indexed call, set the range to [first, first + count)
indexRange.start = first;
indexRange.end = first + count;
}
// Sync the vertex attribute state and track what data needs to be streamed
size_t streamingDataSize = 0;
size_t maxAttributeDataSize = 0;
gl::Error error = syncAttributeState(activeAttribLocations, attributesNeedStreaming, indexRange,
&streamingDataSize, &maxAttributeDataSize);
if (error.isError())
{
return error;
}
if (streamingDataSize > 0)
{
ASSERT(attributesNeedStreaming);
error = streamAttributes(activeAttribLocations, streamingDataSize, maxAttributeDataSize,
indexRange);
if (error.isError())
{
return error;
}
}
return gl::Error(GL_NO_ERROR);
}
bool VertexArrayGL::doAttributesNeedStreaming(const std::vector<GLuint> &activeAttribLocations) const
{
// TODO: if GLES, nothing needs to be streamed
const auto &attribs = mData.getVertexAttributes();
for (size_t activeAttrib = 0; activeAttrib < activeAttribLocations.size(); activeAttrib++)
{
GLuint idx = activeAttribLocations[activeAttrib];
if (attribs[idx].enabled && attribs[idx].buffer.get() == nullptr)
{
return true;
}
}
return false;
}
gl::Error VertexArrayGL::syncAttributeState(const std::vector<GLuint> &activeAttribLocations, bool attributesNeedStreaming,
const gl::RangeUI &indexRange, size_t *outStreamingDataSize, size_t *outMaxAttributeDataSize) const
{
*outStreamingDataSize = 0;
*outMaxAttributeDataSize = 0;
const auto &attribs = mData.getVertexAttributes();
for (size_t activeAttrib = 0; activeAttrib < activeAttribLocations.size(); activeAttrib++)
{
GLuint idx = activeAttribLocations[activeAttrib];
const auto &attrib = attribs[idx];
// Always sync the enabled and divisor state, they are required for both streaming and buffered
// attributes
if (mAppliedAttributes[idx].enabled != attrib.enabled)
{
if (attrib.enabled)
{
mFunctions->enableVertexAttribArray(idx);
}
else
{
mFunctions->disableVertexAttribArray(idx);
}
mAppliedAttributes[idx].enabled = attrib.enabled;
}
if (mAppliedAttributes[idx].divisor != attrib.divisor)
{
mFunctions->vertexAttribDivisor(idx, attrib.divisor);
mAppliedAttributes[idx].divisor = attrib.divisor;
}
if (attribs[idx].enabled && attrib.buffer.get() == nullptr)
{
ASSERT(attributesNeedStreaming);
const size_t streamedVertexCount = indexRange.end - indexRange.start + 1;
// If streaming is going to be required, compute the size of the required buffer
// and how much slack space at the beginning of the buffer will be required by determining
// the attribute with the largest data size.
size_t typeSize = ComputeVertexAttributeTypeSize(attrib);
*outStreamingDataSize += typeSize * streamedVertexCount;
*outMaxAttributeDataSize = std::max(*outMaxAttributeDataSize, typeSize);
}
else
{
// Sync the attribute with no translation
if (mAppliedAttributes[idx] != attrib)
{
const gl::Buffer *arrayBuffer = attrib.buffer.get();
if (arrayBuffer != nullptr)
{
const BufferGL *arrayBufferGL = GetImplAs<BufferGL>(arrayBuffer);
mStateManager->bindBuffer(GL_ARRAY_BUFFER, arrayBufferGL->getBufferID());
}
else
{
mStateManager->bindBuffer(GL_ARRAY_BUFFER, 0);
}
if (attrib.pureInteger)
{
mFunctions->vertexAttribIPointer(idx, attrib.size, attrib.type,
attrib.stride, attrib.pointer);
}
else
{
mFunctions->vertexAttribPointer(idx, attrib.size, attrib.type,
attrib.normalized, attrib.stride,
attrib.pointer);
}
mAppliedAttributes[idx] = attrib;
}
}
}
return gl::Error(GL_NO_ERROR);
}
gl::Error VertexArrayGL::syncIndexData(GLsizei count, GLenum type, const GLvoid *indices, bool attributesNeedStreaming,
gl::RangeUI *outIndexRange, const GLvoid **outIndices) const
{
ASSERT(outIndices);
gl::Buffer *elementArrayBuffer = mData.getElementArrayBuffer().get();
// Need to check the range of indices if attributes need to be streamed
if (elementArrayBuffer != nullptr)
{
const BufferGL *bufferGL = GetImplAs<BufferGL>(elementArrayBuffer);
GLuint elementArrayBufferID = bufferGL->getBufferID();
if (elementArrayBufferID != mAppliedElementArrayBuffer)
{
mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBufferID);
mAppliedElementArrayBuffer = elementArrayBufferID;
}
// Only compute the index range if the attributes also need to be streamed
if (attributesNeedStreaming)
{
ptrdiff_t elementArrayBufferOffset = reinterpret_cast<ptrdiff_t>(indices);
gl::Error error = mData.getElementArrayBuffer()->getIndexRange(type, static_cast<size_t>(elementArrayBufferOffset), count, outIndexRange);
if (error.isError())
{
return error;
}
}
// Indices serves as an offset into the index buffer in this case, use the same value for the draw call
*outIndices = indices;
}
else
{
// Need to stream the index buffer
// TODO: if GLES, nothing needs to be streamed
// Only compute the index range if the attributes also need to be streamed
if (attributesNeedStreaming)
{
*outIndexRange = gl::ComputeIndexRange(type, indices, count);
}
// Allocate the streaming element array buffer
if (mStreamingElementArrayBuffer == 0)
{
mFunctions->genBuffers(1, &mStreamingElementArrayBuffer);
mStreamingElementArrayBufferSize = 0;
}
mStateManager->bindBuffer(GL_ELEMENT_ARRAY_BUFFER, mStreamingElementArrayBuffer);
mAppliedElementArrayBuffer = mStreamingElementArrayBuffer;
// Make sure the element array buffer is large enough
const gl::Type &indexTypeInfo = gl::GetTypeInfo(type);
size_t requiredStreamingBufferSize = indexTypeInfo.bytes * count;
if (requiredStreamingBufferSize > mStreamingElementArrayBufferSize)
{
// Copy the indices in while resizing the buffer
mFunctions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize, indices, GL_DYNAMIC_DRAW);
mStreamingElementArrayBufferSize = requiredStreamingBufferSize;
}
else
{
// Put the indices at the beginning of the buffer
mFunctions->bufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, requiredStreamingBufferSize, indices);
}
// Set the index offset for the draw call to zero since the supplied index pointer is to client data
*outIndices = nullptr;
}
return gl::Error(GL_NO_ERROR);
}
gl::Error VertexArrayGL::streamAttributes(const std::vector<GLuint> &activeAttribLocations, size_t streamingDataSize,
size_t maxAttributeDataSize, const gl::RangeUI &indexRange) const
{
if (mStreamingArrayBuffer == 0)
{
mFunctions->genBuffers(1, &mStreamingArrayBuffer);
mStreamingArrayBufferSize = 0;
}
// If first is greater than zero, a slack space needs to be left at the beginning of the buffer so that
// the same 'first' argument can be passed into the draw call.
const size_t bufferEmptySpace = maxAttributeDataSize * indexRange.start;
const size_t requiredBufferSize = streamingDataSize + bufferEmptySpace;
mStateManager->bindBuffer(GL_ARRAY_BUFFER, mStreamingArrayBuffer);
if (requiredBufferSize > mStreamingArrayBufferSize)
{
mFunctions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr, GL_DYNAMIC_DRAW);
mStreamingArrayBufferSize = requiredBufferSize;
}
// Unmapping a buffer can return GL_FALSE to indicate that the system has corrupted the data
// somehow (such as by a screen change), retry writing the data a few times and return OUT_OF_MEMORY
// if that fails.
GLboolean unmapResult = GL_FALSE;
size_t unmapRetryAttempts = 5;
while (unmapResult != GL_TRUE && --unmapRetryAttempts > 0)
{
uint8_t *bufferPointer = reinterpret_cast<uint8_t*>(mFunctions->mapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY));
size_t curBufferOffset = bufferEmptySpace;
const size_t streamedVertexCount = indexRange.end - indexRange.start + 1;
const auto &attribs = mData.getVertexAttributes();
for (size_t activeAttrib = 0; activeAttrib < activeAttribLocations.size(); activeAttrib++)
{
GLuint idx = activeAttribLocations[activeAttrib];
const auto &attrib = attribs[idx];
if (attrib.enabled && attrib.buffer.get() == nullptr)
{
const size_t sourceStride = ComputeVertexAttributeStride(attrib);
const size_t destStride = ComputeVertexAttributeTypeSize(attrib);
const uint8_t *inputPointer = reinterpret_cast<const uint8_t*>(attrib.pointer);
// Pack the data when copying it, user could have supplied a very large stride that would
// cause the buffer to be much larger than needed.
if (destStride == sourceStride)
{
// Can copy in one go, the data is packed
memcpy(bufferPointer + curBufferOffset,
inputPointer + (sourceStride * indexRange.start),
destStride * streamedVertexCount);
}
else
{
// Copy each vertex individually
for (size_t vertexIdx = indexRange.start; vertexIdx <= indexRange.end; vertexIdx++)
{
memcpy(bufferPointer + curBufferOffset + (destStride * vertexIdx),
inputPointer + (sourceStride * vertexIdx),
destStride);
}
}
// Compute where the 0-index vertex would be.
const size_t vertexStartOffset = curBufferOffset - (indexRange.start * destStride);
mFunctions->vertexAttribPointer(
idx, attrib.size, attrib.type, attrib.normalized,
static_cast<GLsizei>(destStride),
reinterpret_cast<const GLvoid *>(vertexStartOffset));
curBufferOffset += destStride * streamedVertexCount;
// Mark the applied attribute as dirty by setting an invalid size so that if it doesn't
// need to be streamed later, there is no chance that the caching will skip it.
mAppliedAttributes[idx].size = static_cast<GLuint>(-1);
}
}
unmapResult = mFunctions->unmapBuffer(GL_ARRAY_BUFFER);
}
if (unmapResult != GL_TRUE)
{
return gl::Error(GL_OUT_OF_MEMORY, "Failed to unmap the client data streaming buffer.");
}
return gl::Error(GL_NO_ERROR);
}
GLuint VertexArrayGL::getVertexArrayID() const
{
return mVertexArrayID;
}
GLuint VertexArrayGL::getAppliedElementArrayBufferID() const
{
return mAppliedElementArrayBuffer;
}
}