/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL Utilities * --------------------------------------------- * * Copyright 2014 The Android Open Source Project * * 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. * *//*! * \file * \brief Draw call utilities. *//*--------------------------------------------------------------------*/ #include "gluDrawUtil.hpp" #include "gluRenderContext.hpp" #include "gluObjectWrapper.hpp" #include "glwFunctions.hpp" #include "glwEnums.hpp" #include "deInt32.h" #include "deMemory.h" #include #include #include namespace glu { namespace { struct VertexAttributeDescriptor { int location; VertexComponentType componentType; VertexComponentConversion convert; int numComponents; int numElements; int stride; //!< Stride or 0 if using default stride. const void* pointer; //!< Pointer or offset. VertexAttributeDescriptor (int location_, VertexComponentType componentType_, VertexComponentConversion convert_, int numComponents_, int numElements_, int stride_, const void* pointer_) : location (location_) , componentType (componentType_) , convert (convert_) , numComponents (numComponents_) , numElements (numElements_) , stride (stride_) , pointer (pointer_) { } VertexAttributeDescriptor (void) : location (0) , componentType (VTX_COMP_TYPE_LAST) , convert (VTX_COMP_CONVERT_LAST) , numComponents (0) , numElements (0) , stride (0) , pointer (0) { } }; struct VertexBufferLayout { int size; std::vector attributes; VertexBufferLayout (int size_ = 0) : size(size_) { } }; struct VertexBufferDescriptor { deUint32 buffer; std::vector attributes; VertexBufferDescriptor (deUint32 buffer_ = 0) : buffer(buffer_) { } }; class VertexBuffer : public Buffer { public: enum Type { TYPE_PLANAR = 0, //!< Data for each vertex array resides in a separate contiguous block in buffer. TYPE_STRIDED, //!< Vertex arrays are interleaved. TYPE_LAST }; VertexBuffer (const RenderContext& context, int numBindings, const VertexArrayBinding* bindings, Type type = TYPE_PLANAR); ~VertexBuffer (void); const VertexBufferDescriptor& getDescriptor (void) const { return m_layout; } private: VertexBuffer (const VertexBuffer& other); VertexBuffer& operator= (const VertexBuffer& other); VertexBufferDescriptor m_layout; }; class IndexBuffer : public Buffer { public: IndexBuffer (const RenderContext& context, IndexType indexType, int numIndices, const void* indices); ~IndexBuffer (void); private: IndexBuffer (const IndexBuffer& other); IndexBuffer& operator= (const IndexBuffer& other); }; static deUint32 getVtxCompGLType (VertexComponentType type) { switch (type) { case VTX_COMP_UNSIGNED_INT8: return GL_UNSIGNED_BYTE; case VTX_COMP_UNSIGNED_INT16: return GL_UNSIGNED_SHORT; case VTX_COMP_UNSIGNED_INT32: return GL_UNSIGNED_INT; case VTX_COMP_SIGNED_INT8: return GL_BYTE; case VTX_COMP_SIGNED_INT16: return GL_SHORT; case VTX_COMP_SIGNED_INT32: return GL_INT; case VTX_COMP_FIXED: return GL_FIXED; case VTX_COMP_HALF_FLOAT: return GL_HALF_FLOAT; case VTX_COMP_FLOAT: return GL_FLOAT; default: DE_ASSERT(false); return GL_NONE; } } static int getVtxCompSize (VertexComponentType type) { switch (type) { case VTX_COMP_UNSIGNED_INT8: return 1; case VTX_COMP_UNSIGNED_INT16: return 2; case VTX_COMP_UNSIGNED_INT32: return 4; case VTX_COMP_SIGNED_INT8: return 1; case VTX_COMP_SIGNED_INT16: return 2; case VTX_COMP_SIGNED_INT32: return 4; case VTX_COMP_FIXED: return 4; case VTX_COMP_HALF_FLOAT: return 2; case VTX_COMP_FLOAT: return 4; default: DE_ASSERT(false); return 0; } } static deUint32 getIndexGLType (IndexType type) { switch (type) { case INDEXTYPE_UINT8: return GL_UNSIGNED_BYTE; case INDEXTYPE_UINT16: return GL_UNSIGNED_SHORT; case INDEXTYPE_UINT32: return GL_UNSIGNED_INT; default: DE_ASSERT(false); return 0; } } static int getIndexSize (IndexType type) { switch (type) { case INDEXTYPE_UINT8: return 1; case INDEXTYPE_UINT16: return 2; case INDEXTYPE_UINT32: return 4; default: DE_ASSERT(false); return 0; } } static deUint32 getPrimitiveGLType (PrimitiveType type) { switch (type) { case PRIMITIVETYPE_TRIANGLES: return GL_TRIANGLES; case PRIMITIVETYPE_TRIANGLE_STRIP: return GL_TRIANGLE_STRIP; case PRIMITIVETYPE_TRIANGLE_FAN: return GL_TRIANGLE_FAN; case PRIMITIVETYPE_LINES: return GL_LINES; case PRIMITIVETYPE_LINE_STRIP: return GL_LINE_STRIP; case PRIMITIVETYPE_LINE_LOOP: return GL_LINE_LOOP; case PRIMITIVETYPE_POINTS: return GL_POINTS; case PRIMITIVETYPE_PATCHES: return GL_PATCHES; default: DE_ASSERT(false); return 0; } } //! Lower named bindings to locations and eliminate bindings that are not used by program. template static OutputIter namedBindingsToProgramLocations (const glw::Functions& gl, deUint32 program, InputIter first, InputIter end, OutputIter out) { for (InputIter cur = first; cur != end; ++cur) { const BindingPoint& binding = cur->binding; if (binding.type == BindingPoint::TYPE_NAME) { DE_ASSERT(binding.location >= 0); int location = gl.getAttribLocation(program, binding.name.c_str()); if (location >= 0) { // Add binding.location as an offset to accommodate matrices. *out = VertexArrayBinding(BindingPoint(location + binding.location), cur->pointer); ++out; } } else { *out = *cur; ++out; } } return out; } static deUint32 getMinimumAlignment (const VertexArrayPointer& pointer) { // \todo [2013-05-07 pyry] What is the actual min? DE_UNREF(pointer); return (deUint32)sizeof(float); } template static bool areVertexArrayLocationsValid (BindingIter first, BindingIter end) { std::set usedLocations; for (BindingIter cur = first; cur != end; ++cur) { const BindingPoint& binding = cur->binding; if (binding.type != BindingPoint::TYPE_LOCATION) return false; if (usedLocations.find(binding.location) != usedLocations.end()) return false; usedLocations.insert(binding.location); } return true; } // \todo [2013-05-08 pyry] Buffer upload should try to match pointers to reduce dataset size. static void appendAttributeNonStrided (VertexBufferLayout& layout, const VertexArrayBinding& va) { const int offset = deAlign32(layout.size, getMinimumAlignment(va.pointer)); const int elementSize = getVtxCompSize(va.pointer.componentType)*va.pointer.numComponents; const int size = elementSize*va.pointer.numElements; // Must be assigned to location at this point. DE_ASSERT(va.binding.type == BindingPoint::TYPE_LOCATION); layout.attributes.push_back(VertexAttributeDescriptor(va.binding.location, va.pointer.componentType, va.pointer.convert, va.pointer.numComponents, va.pointer.numElements, 0, // default stride (const void*)(deUintptr)offset)); layout.size = offset+size; } template static void computeNonStridedBufferLayout (VertexBufferLayout& layout, BindingIter first, BindingIter end) { for (BindingIter iter = first; iter != end; ++iter) appendAttributeNonStrided(layout, *iter); } static void copyToLayout (void* dstBasePtr, const VertexAttributeDescriptor& dstVA, const VertexArrayPointer& srcPtr) { DE_ASSERT(dstVA.componentType == srcPtr.componentType && dstVA.numComponents == srcPtr.numComponents && dstVA.numElements == srcPtr.numElements); const int elementSize = getVtxCompSize(dstVA.componentType)*dstVA.numComponents; const bool srcHasCustomStride = srcPtr.stride != 0 && srcPtr.stride != elementSize; const bool dstHasCustomStride = dstVA.stride != 0 && dstVA.stride != elementSize; if (srcHasCustomStride || dstHasCustomStride) { const int dstStride = dstVA.stride != 0 ? dstVA.stride : elementSize; const int srcStride = srcPtr.stride != 0 ? srcPtr.stride : elementSize; for (int ndx = 0; ndx < dstVA.numElements; ndx++) deMemcpy((deUint8*)dstBasePtr + (deUintptr)dstVA.pointer + ndx*dstStride, (const deUint8*)srcPtr.data + ndx*srcStride, elementSize); } else deMemcpy((deUint8*)dstBasePtr + (deUintptr)dstVA.pointer, srcPtr.data, elementSize*dstVA.numElements); } void uploadBufferData (const glw::Functions& gl, deUint32 buffer, deUint32 usage, const VertexBufferLayout& layout, const VertexArrayPointer* srcArrays) { // Create temporary data buffer for upload. std::vector localBuf(layout.size); for (int attrNdx = 0; attrNdx < (int)layout.attributes.size(); ++attrNdx) copyToLayout(&localBuf[0], layout.attributes[attrNdx], srcArrays[attrNdx]); gl.bindBuffer(GL_ARRAY_BUFFER, buffer); gl.bufferData(GL_ARRAY_BUFFER, (int)localBuf.size(), &localBuf[0], usage); gl.bindBuffer(GL_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading buffer data failed"); } // VertexBuffer VertexBuffer::VertexBuffer (const RenderContext& context, int numBindings, const VertexArrayBinding* bindings, Type type) : Buffer(context) { const glw::Functions& gl = context.getFunctions(); const deUint32 usage = GL_STATIC_DRAW; VertexBufferLayout layout; if (!areVertexArrayLocationsValid(bindings, bindings+numBindings)) throw tcu::TestError("Invalid vertex array locations"); if (type == TYPE_PLANAR) computeNonStridedBufferLayout(layout, bindings, bindings+numBindings); else throw tcu::InternalError("Strided layout is not yet supported"); std::vector srcPtrs(numBindings); for (int ndx = 0; ndx < numBindings; ndx++) srcPtrs[ndx] = bindings[ndx].pointer; DE_ASSERT(srcPtrs.size() == layout.attributes.size()); if (!srcPtrs.empty()) uploadBufferData(gl, m_object, usage, layout, &srcPtrs[0]); // Construct descriptor. m_layout.buffer = m_object; m_layout.attributes = layout.attributes; } VertexBuffer::~VertexBuffer (void) { } // IndexBuffer IndexBuffer::IndexBuffer (const RenderContext& context, IndexType indexType, int numIndices, const void* indices) : Buffer(context) { const glw::Functions& gl = context.getFunctions(); const deUint32 usage = GL_STATIC_DRAW; gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_object); gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices*getIndexSize(indexType), indices, usage); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); GLU_EXPECT_NO_ERROR(gl.getError(), "Uploading index data failed"); } IndexBuffer::~IndexBuffer (void) { } static inline VertexAttributeDescriptor getUserPointerDescriptor (const VertexArrayBinding& vertexArray) { DE_ASSERT(vertexArray.binding.type == BindingPoint::TYPE_LOCATION); return VertexAttributeDescriptor(vertexArray.binding.location, vertexArray.pointer.componentType, vertexArray.pointer.convert, vertexArray.pointer.numComponents, vertexArray.pointer.numElements, vertexArray.pointer.stride, vertexArray.pointer.data); } //! Setup VA according to allocation spec. Assumes that other state (VAO binding, buffer) is set already. static void setVertexAttribPointer (const glw::Functions& gl, const VertexAttributeDescriptor& va) { const bool isIntType = de::inRange(va.componentType, VTX_COMP_UNSIGNED_INT8, VTX_COMP_SIGNED_INT32); const bool isSpecialType = de::inRange(va.componentType, VTX_COMP_FIXED, VTX_COMP_FLOAT); const deUint32 compTypeGL = getVtxCompGLType(va.componentType); DE_ASSERT(isIntType != isSpecialType); // Must be either int or special type. DE_ASSERT(isIntType || va.convert == VTX_COMP_CONVERT_NONE); // Conversion allowed only for special types. DE_UNREF(isSpecialType); gl.enableVertexAttribArray(va.location); if (isIntType && va.convert == VTX_COMP_CONVERT_NONE) gl.vertexAttribIPointer(va.location, va.numComponents, compTypeGL, va.stride, va.pointer); else gl.vertexAttribPointer(va.location, va.numComponents, compTypeGL, va.convert == VTX_COMP_CONVERT_NORMALIZE_TO_FLOAT ? GL_TRUE : GL_FALSE, va.stride, va.pointer); } //! Setup vertex buffer and attributes. static void setVertexBufferAttributes (const glw::Functions& gl, const VertexBufferDescriptor& buffer) { gl.bindBuffer(GL_ARRAY_BUFFER, buffer.buffer); for (std::vector::const_iterator vaIter = buffer.attributes.begin(); vaIter != buffer.attributes.end(); ++vaIter) setVertexAttribPointer(gl, *vaIter); gl.bindBuffer(GL_ARRAY_BUFFER, 0); } static void disableVertexArrays (const glw::Functions& gl, const std::vector& bindings) { for (std::vector::const_iterator vaIter = bindings.begin(); vaIter != bindings.end(); ++vaIter) { DE_ASSERT(vaIter->binding.type == BindingPoint::TYPE_LOCATION); gl.disableVertexAttribArray(vaIter->binding.location); } } #if defined(DE_DEBUG) static bool isProgramActive (const RenderContext& context, deUint32 program) { // \todo [2013-05-08 pyry] Is this query broken? /* deUint32 activeProgram = 0; context.getFunctions().getIntegerv(GL_ACTIVE_PROGRAM, (int*)&activeProgram); GLU_EXPECT_NO_ERROR(context.getFunctions().getError(), "oh"); return activeProgram == program;*/ DE_UNREF(context); DE_UNREF(program); return true; } static bool isDrawCallValid (int numVertexArrays, const VertexArrayBinding* vertexArrays, const PrimitiveList& primitives) { if (numVertexArrays < 0) return false; if ((primitives.indexType == INDEXTYPE_LAST) != (primitives.indices == 0)) return false; if (primitives.numElements < 0) return false; if (!primitives.indices) { for (int ndx = 0; ndx < numVertexArrays; ndx++) { if (primitives.numElements > vertexArrays[ndx].pointer.numElements) return false; } } // \todo [2013-05-08 pyry] We could walk whole index array and determine index range return true; } #endif // DE_DEBUG static inline void drawNonIndexed (const glw::Functions& gl, PrimitiveType type, int numElements) { deUint32 mode = getPrimitiveGLType(type); gl.drawArrays(mode, 0, numElements); } static inline void drawIndexed (const glw::Functions& gl, PrimitiveType type, int numElements, IndexType indexType, const void* indexPtr) { deUint32 mode = getPrimitiveGLType(type); deUint32 indexGLType = getIndexGLType(indexType); gl.drawElements(mode, numElements, indexGLType, indexPtr); } } // anonymous void drawFromUserPointers (const RenderContext& context, deUint32 program, int numVertexArrays, const VertexArrayBinding* vertexArrays, const PrimitiveList& primitives, DrawUtilCallback* callback) { const glw::Functions& gl = context.getFunctions(); std::vector bindingsWithLocations; DE_ASSERT(isDrawCallValid(numVertexArrays, vertexArrays, primitives)); DE_ASSERT(isProgramActive(context, program)); // Lower bindings to locations. namedBindingsToProgramLocations(gl, program, vertexArrays, vertexArrays+numVertexArrays, std::inserter(bindingsWithLocations, bindingsWithLocations.begin())); TCU_CHECK(areVertexArrayLocationsValid(bindingsWithLocations.begin(), bindingsWithLocations.end())); // Set VA state. for (std::vector::const_iterator vaIter = bindingsWithLocations.begin(); vaIter != bindingsWithLocations.end(); ++vaIter) setVertexAttribPointer(gl, getUserPointerDescriptor(*vaIter)); if (callback) callback->beforeDrawCall(); if (primitives.indices) drawIndexed(gl, primitives.type, primitives.numElements, primitives.indexType, primitives.indices); else drawNonIndexed(gl, primitives.type, primitives.numElements); if (callback) callback->afterDrawCall(); // Disable attribute arrays or otherwise someone later on might get crash thanks to invalid pointers. disableVertexArrays(gl, bindingsWithLocations); } void drawFromBuffers (const RenderContext& context, deUint32 program, int numVertexArrays, const VertexArrayBinding* vertexArrays, const PrimitiveList& primitives, DrawUtilCallback* callback) { const glw::Functions& gl = context.getFunctions(); std::vector bindingsWithLocations; DE_ASSERT(isDrawCallValid(numVertexArrays, vertexArrays, primitives)); DE_ASSERT(isProgramActive(context, program)); // Lower bindings to locations. namedBindingsToProgramLocations(gl, program, vertexArrays, vertexArrays+numVertexArrays, std::inserter(bindingsWithLocations, bindingsWithLocations.begin())); TCU_CHECK(areVertexArrayLocationsValid(bindingsWithLocations.begin(), bindingsWithLocations.end())); // Create buffers for duration of draw call. { VertexBuffer vertexBuffer (context, (int)bindingsWithLocations.size(), (bindingsWithLocations.empty()) ? (DE_NULL) : (&bindingsWithLocations[0])); // Set state. setVertexBufferAttributes(gl, vertexBuffer.getDescriptor()); if (primitives.indices) { IndexBuffer indexBuffer(context, primitives.indexType, primitives.numElements, primitives.indices); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer); if (callback) callback->beforeDrawCall(); drawIndexed(gl, primitives.type, primitives.numElements, primitives.indexType, 0); if (callback) callback->afterDrawCall(); gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } else { if (callback) callback->beforeDrawCall(); drawNonIndexed(gl, primitives.type, primitives.numElements); if (callback) callback->afterDrawCall(); } } // Disable attribute arrays or otherwise someone later on might get crash thanks to invalid pointers. for (std::vector::const_iterator vaIter = bindingsWithLocations.begin(); vaIter != bindingsWithLocations.end(); ++vaIter) gl.disableVertexAttribArray(vaIter->binding.location); } void drawFromVAOBuffers (const RenderContext& context, deUint32 program, int numVertexArrays, const VertexArrayBinding* vertexArrays, const PrimitiveList& primitives, DrawUtilCallback* callback) { const glw::Functions& gl = context.getFunctions(); VertexArray vao (context); gl.bindVertexArray(*vao); drawFromBuffers(context, program, numVertexArrays, vertexArrays, primitives, callback); gl.bindVertexArray(0); } void draw (const RenderContext& context, deUint32 program, int numVertexArrays, const VertexArrayBinding* vertexArrays, const PrimitiveList& primitives, DrawUtilCallback* callback) { const glu::ContextType ctxType = context.getType(); if (isContextTypeGLCore(ctxType) || contextSupports(ctxType, ApiType::es(3,1))) drawFromVAOBuffers(context, program, numVertexArrays, vertexArrays, primitives, callback); else { DE_ASSERT(isContextTypeES(ctxType)); drawFromUserPointers(context, program, numVertexArrays, vertexArrays, primitives, callback); } } } // glu