// // Copyright (c) 2002-2012 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. // // VertexDataManager.h: Defines the VertexDataManager, a class that // runs the Buffer translation process. #include "libANGLE/renderer/d3d/VertexDataManager.h" #include "common/bitset_utils.h" #include "libANGLE/Buffer.h" #include "libANGLE/Context.h" #include "libANGLE/Program.h" #include "libANGLE/State.h" #include "libANGLE/VertexArray.h" #include "libANGLE/VertexAttribute.h" #include "libANGLE/formatutils.h" #include "libANGLE/renderer/d3d/BufferD3D.h" #include "libANGLE/renderer/d3d/RendererD3D.h" #include "libANGLE/renderer/d3d/VertexBuffer.h" using namespace angle; namespace rx { namespace { enum { INITIAL_STREAM_BUFFER_SIZE = 1024 * 1024 }; // This has to be at least 4k or else it fails on ATI cards. enum { CONSTANT_VERTEX_BUFFER_SIZE = 4096 }; // Warning: you should ensure binding really matches attrib.bindingIndex before using this function. int ElementsInBuffer(const gl::VertexAttribute &attrib, const gl::VertexBinding &binding, unsigned int size) { // Size cannot be larger than a GLsizei if (size > static_cast(std::numeric_limits::max())) { size = static_cast(std::numeric_limits::max()); } GLsizei stride = static_cast(ComputeVertexAttributeStride(attrib, binding)); GLsizei offset = static_cast(ComputeVertexAttributeOffset(attrib, binding)); return (size - offset % stride + (stride - static_cast(ComputeVertexAttributeTypeSize(attrib)))) / stride; } // Warning: you should ensure binding really matches attrib.bindingIndex before using this function. bool DirectStoragePossible(const gl::VertexAttribute &attrib, const gl::VertexBinding &binding) { // Current value attribs may not use direct storage. if (!attrib.enabled) { return false; } gl::Buffer *buffer = binding.getBuffer().get(); if (!buffer) { return false; } BufferD3D *bufferD3D = GetImplAs(buffer); ASSERT(bufferD3D); if (!bufferD3D->supportsDirectBinding()) { return false; } // Alignment restrictions: In D3D, vertex data must be aligned to the format stride, or to a // 4-byte boundary, whichever is smaller. (Undocumented, and experimentally confirmed) size_t alignment = 4; // TODO(jmadill): add VertexFormatCaps BufferFactoryD3D *factory = bufferD3D->getFactory(); gl::VertexFormatType vertexFormatType = gl::GetVertexFormatType(attrib); // CPU-converted vertex data must be converted (naturally). if ((factory->getVertexConversionType(vertexFormatType) & VERTEX_CONVERT_CPU) != 0) { return false; } if (attrib.type != GL_FLOAT) { auto errorOrElementSize = factory->getVertexSpaceRequired(attrib, binding, 1, 0); if (errorOrElementSize.isError()) { ERR() << "Unlogged error in DirectStoragePossible."; return false; } alignment = std::min(errorOrElementSize.getResult(), 4); } GLintptr offset = ComputeVertexAttributeOffset(attrib, binding); // Final alignment check - unaligned data must be converted. return (static_cast(ComputeVertexAttributeStride(attrib, binding)) % alignment == 0) && (static_cast(offset) % alignment == 0); } } // anonymous namespace TranslatedAttribute::TranslatedAttribute() : active(false), attribute(nullptr), binding(nullptr), currentValueType(GL_NONE), baseOffset(0), usesFirstVertexOffset(false), stride(0), vertexBuffer(), storage(nullptr), serial(0), divisor(0) { } TranslatedAttribute::TranslatedAttribute(const TranslatedAttribute &other) = default; gl::ErrorOrResult TranslatedAttribute::computeOffset(GLint startVertex) const { if (!usesFirstVertexOffset) { return baseOffset; } CheckedNumeric offset; offset = baseOffset + stride * static_cast(startVertex); ANGLE_TRY_CHECKED_MATH(offset); return offset.ValueOrDie(); } // Warning: you should ensure binding really matches attrib.bindingIndex before using this function. VertexStorageType ClassifyAttributeStorage(const gl::VertexAttribute &attrib, const gl::VertexBinding &binding) { // If attribute is disabled, we use the current value. if (!attrib.enabled) { return VertexStorageType::CURRENT_VALUE; } // If specified with immediate data, we must use dynamic storage. auto *buffer = binding.getBuffer().get(); if (!buffer) { return VertexStorageType::DYNAMIC; } // Check if the buffer supports direct storage. if (DirectStoragePossible(attrib, binding)) { return VertexStorageType::DIRECT; } // Otherwise the storage is static or dynamic. BufferD3D *bufferD3D = GetImplAs(buffer); ASSERT(bufferD3D); switch (bufferD3D->getUsage()) { case D3DBufferUsage::DYNAMIC: return VertexStorageType::DYNAMIC; case D3DBufferUsage::STATIC: return VertexStorageType::STATIC; default: UNREACHABLE(); return VertexStorageType::UNKNOWN; } } VertexDataManager::CurrentValueState::CurrentValueState() : buffer(), offset(0) { data.FloatValues[0] = std::numeric_limits::quiet_NaN(); data.FloatValues[1] = std::numeric_limits::quiet_NaN(); data.FloatValues[2] = std::numeric_limits::quiet_NaN(); data.FloatValues[3] = std::numeric_limits::quiet_NaN(); data.Type = GL_FLOAT; } VertexDataManager::CurrentValueState::~CurrentValueState() { } VertexDataManager::VertexDataManager(BufferFactoryD3D *factory) : mFactory(factory), mStreamingBuffer(), mCurrentValueCache(gl::MAX_VERTEX_ATTRIBS) { } VertexDataManager::~VertexDataManager() { } gl::Error VertexDataManager::initialize() { mStreamingBuffer.reset( new StreamingVertexBufferInterface(mFactory, INITIAL_STREAM_BUFFER_SIZE)); if (!mStreamingBuffer) { return gl::OutOfMemory() << "Failed to allocate the streaming vertex buffer."; } return gl::NoError(); } void VertexDataManager::deinitialize() { mStreamingBuffer.reset(); mCurrentValueCache.clear(); } gl::Error VertexDataManager::prepareVertexData(const gl::Context *context, GLint start, GLsizei count, std::vector *translatedAttribs, GLsizei instances) { ASSERT(mStreamingBuffer); const gl::State &state = context->getGLState(); const gl::VertexArray *vertexArray = state.getVertexArray(); const auto &vertexAttributes = vertexArray->getVertexAttributes(); const auto &vertexBindings = vertexArray->getVertexBindings(); mDynamicAttribsMaskCache.reset(); const gl::Program *program = state.getProgram(); translatedAttribs->clear(); for (size_t attribIndex = 0; attribIndex < vertexAttributes.size(); ++attribIndex) { // Skip attrib locations the program doesn't use. if (!program->isAttribLocationActive(attribIndex)) continue; const auto &attrib = vertexAttributes[attribIndex]; const auto &binding = vertexBindings[attrib.bindingIndex]; // Resize automatically puts in empty attribs translatedAttribs->resize(attribIndex + 1); TranslatedAttribute *translated = &(*translatedAttribs)[attribIndex]; auto currentValueData = state.getVertexAttribCurrentValue(attribIndex); // Record the attribute now translated->active = true; translated->attribute = &attrib; translated->binding = &binding; translated->currentValueType = currentValueData.Type; translated->divisor = binding.getDivisor(); switch (ClassifyAttributeStorage(attrib, binding)) { case VertexStorageType::STATIC: { // Store static attribute. ANGLE_TRY(StoreStaticAttrib(context, translated)); break; } case VertexStorageType::DYNAMIC: // Dynamic attributes must be handled together. mDynamicAttribsMaskCache.set(attribIndex); break; case VertexStorageType::DIRECT: // Update translated data for direct attributes. StoreDirectAttrib(translated); break; case VertexStorageType::CURRENT_VALUE: { ANGLE_TRY(storeCurrentValue(currentValueData, translated, attribIndex)); break; } default: UNREACHABLE(); break; } } if (mDynamicAttribsMaskCache.none()) { return gl::NoError(); } ANGLE_TRY(storeDynamicAttribs(context, translatedAttribs, mDynamicAttribsMaskCache, start, count, instances)); PromoteDynamicAttribs(context, *translatedAttribs, mDynamicAttribsMaskCache, count); return gl::NoError(); } // static void VertexDataManager::StoreDirectAttrib(TranslatedAttribute *directAttrib) { ASSERT(directAttrib->attribute && directAttrib->binding); const auto &attrib = *directAttrib->attribute; const auto &binding = *directAttrib->binding; gl::Buffer *buffer = binding.getBuffer().get(); ASSERT(buffer); BufferD3D *bufferD3D = GetImplAs(buffer); ASSERT(DirectStoragePossible(attrib, binding)); directAttrib->vertexBuffer.set(nullptr); directAttrib->storage = bufferD3D; directAttrib->serial = bufferD3D->getSerial(); directAttrib->stride = static_cast(ComputeVertexAttributeStride(attrib, binding)); directAttrib->baseOffset = static_cast(ComputeVertexAttributeOffset(attrib, binding)); // Instanced vertices do not apply the 'start' offset directAttrib->usesFirstVertexOffset = (binding.getDivisor() == 0); } // static gl::Error VertexDataManager::StoreStaticAttrib(const gl::Context *context, TranslatedAttribute *translated) { ASSERT(translated->attribute && translated->binding); const auto &attrib = *translated->attribute; const auto &binding = *translated->binding; gl::Buffer *buffer = binding.getBuffer().get(); ASSERT(buffer && attrib.enabled && !DirectStoragePossible(attrib, binding)); BufferD3D *bufferD3D = GetImplAs(buffer); // Compute source data pointer const uint8_t *sourceData = nullptr; const int offset = static_cast(ComputeVertexAttributeOffset(attrib, binding)); ANGLE_TRY(bufferD3D->getData(context, &sourceData)); sourceData += offset; unsigned int streamOffset = 0; translated->storage = nullptr; ANGLE_TRY_RESULT(bufferD3D->getFactory()->getVertexSpaceRequired(attrib, binding, 1, 0), translated->stride); auto *staticBuffer = bufferD3D->getStaticVertexBuffer(attrib, binding); ASSERT(staticBuffer); if (staticBuffer->empty()) { // Convert the entire buffer int totalCount = ElementsInBuffer(attrib, binding, static_cast(bufferD3D->getSize())); int startIndex = offset / static_cast(ComputeVertexAttributeStride(attrib, binding)); ANGLE_TRY(staticBuffer->storeStaticAttribute(attrib, binding, -startIndex, totalCount, 0, sourceData)); } unsigned int firstElementOffset = (static_cast(offset) / static_cast(ComputeVertexAttributeStride(attrib, binding))) * translated->stride; VertexBuffer *vertexBuffer = staticBuffer->getVertexBuffer(); CheckedNumeric checkedOffset(streamOffset); checkedOffset += firstElementOffset; if (!checkedOffset.IsValid()) { return gl::InternalError() << "Integer overflow in VertexDataManager::StoreStaticAttrib"; } translated->vertexBuffer.set(vertexBuffer); translated->serial = vertexBuffer->getSerial(); translated->baseOffset = streamOffset + firstElementOffset; // Instanced vertices do not apply the 'start' offset translated->usesFirstVertexOffset = (binding.getDivisor() == 0); return gl::NoError(); } gl::Error VertexDataManager::storeDynamicAttribs( const gl::Context *context, std::vector *translatedAttribs, const gl::AttributesMask &dynamicAttribsMask, GLint start, GLsizei count, GLsizei instances) { // Instantiating this class will ensure the streaming buffer is never left mapped. class StreamingBufferUnmapper final : NonCopyable { public: StreamingBufferUnmapper(StreamingVertexBufferInterface *streamingBuffer) : mStreamingBuffer(streamingBuffer) { ASSERT(mStreamingBuffer); } ~StreamingBufferUnmapper() { mStreamingBuffer->getVertexBuffer()->hintUnmapResource(); } private: StreamingVertexBufferInterface *mStreamingBuffer; }; // Will trigger unmapping on return. StreamingBufferUnmapper localUnmapper(mStreamingBuffer.get()); // Reserve the required space for the dynamic buffers. for (auto attribIndex : dynamicAttribsMask) { const auto &dynamicAttrib = (*translatedAttribs)[attribIndex]; ANGLE_TRY(reserveSpaceForAttrib(dynamicAttrib, start, count, instances)); } // Store dynamic attributes for (auto attribIndex : dynamicAttribsMask) { auto *dynamicAttrib = &(*translatedAttribs)[attribIndex]; ANGLE_TRY(storeDynamicAttrib(context, dynamicAttrib, start, count, instances)); } return gl::NoError(); } void VertexDataManager::PromoteDynamicAttribs( const gl::Context *context, const std::vector &translatedAttribs, const gl::AttributesMask &dynamicAttribsMask, GLsizei count) { for (auto attribIndex : dynamicAttribsMask) { const auto &dynamicAttrib = translatedAttribs[attribIndex]; ASSERT(dynamicAttrib.attribute && dynamicAttrib.binding); const auto &binding = *dynamicAttrib.binding; gl::Buffer *buffer = binding.getBuffer().get(); if (buffer) { BufferD3D *bufferD3D = GetImplAs(buffer); size_t typeSize = ComputeVertexAttributeTypeSize(*dynamicAttrib.attribute); bufferD3D->promoteStaticUsage(context, count * static_cast(typeSize)); } } } gl::Error VertexDataManager::reserveSpaceForAttrib(const TranslatedAttribute &translatedAttrib, GLint start, GLsizei count, GLsizei instances) const { ASSERT(translatedAttrib.attribute && translatedAttrib.binding); const auto &attrib = *translatedAttrib.attribute; const auto &binding = *translatedAttrib.binding; ASSERT(!DirectStoragePossible(attrib, binding)); gl::Buffer *buffer = binding.getBuffer().get(); BufferD3D *bufferD3D = buffer ? GetImplAs(buffer) : nullptr; ASSERT(!bufferD3D || bufferD3D->getStaticVertexBuffer(attrib, binding) == nullptr); size_t totalCount = gl::ComputeVertexBindingElementCount( binding.getDivisor(), static_cast(count), static_cast(instances)); // TODO(jiajia.qin@intel.com): force the index buffer to clamp any out of range indices instead // of invalid operation here. if (bufferD3D) { // Vertices do not apply the 'start' offset when the divisor is non-zero even when doing // a non-instanced draw call GLint firstVertexIndex = binding.getDivisor() > 0 ? 0 : start; int64_t maxVertexCount = static_cast(firstVertexIndex) + static_cast(totalCount); int elementsInBuffer = ElementsInBuffer(attrib, binding, static_cast(bufferD3D->getSize())); if (maxVertexCount > elementsInBuffer) { return gl::InvalidOperation() << "Vertex buffer is not big enough for the draw call."; } } return mStreamingBuffer->reserveVertexSpace(attrib, binding, static_cast(totalCount), instances); } gl::Error VertexDataManager::storeDynamicAttrib(const gl::Context *context, TranslatedAttribute *translated, GLint start, GLsizei count, GLsizei instances) { ASSERT(translated->attribute && translated->binding); const auto &attrib = *translated->attribute; const auto &binding = *translated->binding; gl::Buffer *buffer = binding.getBuffer().get(); ASSERT(buffer || attrib.pointer); ASSERT(attrib.enabled); BufferD3D *storage = buffer ? GetImplAs(buffer) : nullptr; // Instanced vertices do not apply the 'start' offset GLint firstVertexIndex = (binding.getDivisor() > 0 ? 0 : start); // Compute source data pointer const uint8_t *sourceData = nullptr; if (buffer) { ANGLE_TRY(storage->getData(context, &sourceData)); sourceData += static_cast(ComputeVertexAttributeOffset(attrib, binding)); } else { // Attributes using client memory ignore the VERTEX_ATTRIB_BINDING state. // https://www.opengl.org/registry/specs/ARB/vertex_attrib_binding.txt sourceData = static_cast(attrib.pointer); } unsigned int streamOffset = 0; translated->storage = nullptr; ANGLE_TRY_RESULT(mFactory->getVertexSpaceRequired(attrib, binding, 1, 0), translated->stride); size_t totalCount = gl::ComputeVertexBindingElementCount( binding.getDivisor(), static_cast(count), static_cast(instances)); ANGLE_TRY(mStreamingBuffer->storeDynamicAttribute( attrib, binding, translated->currentValueType, firstVertexIndex, static_cast(totalCount), instances, &streamOffset, sourceData)); VertexBuffer *vertexBuffer = mStreamingBuffer->getVertexBuffer(); translated->vertexBuffer.set(vertexBuffer); translated->serial = vertexBuffer->getSerial(); translated->baseOffset = streamOffset; translated->usesFirstVertexOffset = false; return gl::NoError(); } gl::Error VertexDataManager::storeCurrentValue(const gl::VertexAttribCurrentValueData ¤tValue, TranslatedAttribute *translated, size_t attribIndex) { CurrentValueState *cachedState = &mCurrentValueCache[attribIndex]; auto &buffer = cachedState->buffer; if (!buffer) { buffer.reset(new StreamingVertexBufferInterface(mFactory, CONSTANT_VERTEX_BUFFER_SIZE)); } if (cachedState->data != currentValue) { ASSERT(translated->attribute && translated->binding); const auto &attrib = *translated->attribute; const auto &binding = *translated->binding; ANGLE_TRY(buffer->reserveVertexSpace(attrib, binding, 1, 0)); const uint8_t *sourceData = reinterpret_cast(currentValue.FloatValues); unsigned int streamOffset; ANGLE_TRY(buffer->storeDynamicAttribute(attrib, binding, currentValue.Type, 0, 1, 0, &streamOffset, sourceData)); buffer->getVertexBuffer()->hintUnmapResource(); cachedState->data = currentValue; cachedState->offset = streamOffset; } translated->vertexBuffer.set(buffer->getVertexBuffer()); translated->storage = nullptr; translated->serial = buffer->getSerial(); translated->divisor = 0; translated->stride = 0; translated->baseOffset = static_cast(cachedState->offset); translated->usesFirstVertexOffset = false; return gl::NoError(); } // VertexBufferBinding implementation VertexBufferBinding::VertexBufferBinding() : mBoundVertexBuffer(nullptr) { } VertexBufferBinding::VertexBufferBinding(const VertexBufferBinding &other) : mBoundVertexBuffer(other.mBoundVertexBuffer) { if (mBoundVertexBuffer) { mBoundVertexBuffer->addRef(); } } VertexBufferBinding::~VertexBufferBinding() { if (mBoundVertexBuffer) { mBoundVertexBuffer->release(); } } VertexBufferBinding &VertexBufferBinding::operator=(const VertexBufferBinding &other) { mBoundVertexBuffer = other.mBoundVertexBuffer; if (mBoundVertexBuffer) { mBoundVertexBuffer->addRef(); } return *this; } void VertexBufferBinding::set(VertexBuffer *vertexBuffer) { if (mBoundVertexBuffer == vertexBuffer) return; if (mBoundVertexBuffer) { mBoundVertexBuffer->release(); } if (vertexBuffer) { vertexBuffer->addRef(); } mBoundVertexBuffer = vertexBuffer; } VertexBuffer *VertexBufferBinding::get() const { return mBoundVertexBuffer; } } // namespace rx