| /*------------------------------------------------------------------------- |
| * 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 <vector> |
| #include <set> |
| #include <iterator> |
| |
| 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<VertexAttributeDescriptor> attributes; |
| |
| VertexBufferLayout(int size_ = 0) : size(size_) |
| { |
| } |
| }; |
| |
| struct VertexBufferDescriptor |
| { |
| uint32_t buffer; |
| std::vector<VertexAttributeDescriptor> attributes; |
| |
| VertexBufferDescriptor(uint32_t 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 uint32_t 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 uint32_t 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 uint32_t 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 <typename InputIter, typename OutputIter> |
| static OutputIter namedBindingsToProgramLocations(const glw::Functions &gl, uint32_t program, InputIter first, |
| InputIter end, OutputIter out) |
| { |
| for (InputIter cur = first; cur != end; ++cur) |
| { |
| const BindingPoint &binding = cur->binding; |
| if (binding.type == BindingPoint::BPTYPE_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 uint32_t getMinimumAlignment(const VertexArrayPointer &pointer) |
| { |
| // \todo [2013-05-07 pyry] What is the actual min? |
| DE_UNREF(pointer); |
| return (uint32_t)sizeof(float); |
| } |
| |
| template <typename BindingIter> |
| static bool areVertexArrayLocationsValid(BindingIter first, BindingIter end) |
| { |
| std::set<int> usedLocations; |
| for (BindingIter cur = first; cur != end; ++cur) |
| { |
| const BindingPoint &binding = cur->binding; |
| |
| if (binding.type != BindingPoint::BPTYPE_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::BPTYPE_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 *)(uintptr_t)offset)); |
| layout.size = offset + size; |
| } |
| |
| template <typename BindingIter> |
| 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((uint8_t *)dstBasePtr + (uintptr_t)dstVA.pointer + ndx * dstStride, |
| (const uint8_t *)srcPtr.data + ndx * srcStride, elementSize); |
| } |
| else |
| deMemcpy((uint8_t *)dstBasePtr + (uintptr_t)dstVA.pointer, srcPtr.data, elementSize * dstVA.numElements); |
| } |
| |
| void uploadBufferData(const glw::Functions &gl, uint32_t buffer, uint32_t usage, const VertexBufferLayout &layout, |
| const VertexArrayPointer *srcArrays) |
| { |
| // Create temporary data buffer for upload. |
| std::vector<uint8_t> 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 uint32_t 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<VertexArrayPointer> 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 uint32_t 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::BPTYPE_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<int>(va.componentType, VTX_COMP_UNSIGNED_INT8, VTX_COMP_SIGNED_INT32); |
| const bool isSpecialType = de::inRange<int>(va.componentType, VTX_COMP_FIXED, VTX_COMP_FLOAT); |
| const uint32_t 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<VertexAttributeDescriptor>::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<VertexArrayBinding> &bindings) |
| { |
| for (std::vector<VertexArrayBinding>::const_iterator vaIter = bindings.begin(); vaIter != bindings.end(); ++vaIter) |
| { |
| DE_ASSERT(vaIter->binding.type == BindingPoint::BPTYPE_LOCATION); |
| gl.disableVertexAttribArray(vaIter->binding.location); |
| } |
| } |
| |
| #if defined(DE_DEBUG) |
| static bool isProgramActive(const RenderContext &context, uint32_t program) |
| { |
| // \todo [2013-05-08 pyry] Is this query broken? |
| /* uint32_t 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) |
| { |
| uint32_t 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) |
| { |
| uint32_t mode = getPrimitiveGLType(type); |
| uint32_t indexGLType = getIndexGLType(indexType); |
| |
| gl.drawElements(mode, numElements, indexGLType, indexPtr); |
| } |
| |
| } // namespace |
| |
| void drawFromUserPointers(const RenderContext &context, uint32_t program, int numVertexArrays, |
| const VertexArrayBinding *vertexArrays, const PrimitiveList &primitives, |
| DrawUtilCallback *callback) |
| { |
| const glw::Functions &gl = context.getFunctions(); |
| std::vector<VertexArrayBinding> 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<VertexArrayBinding>::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, uint32_t program, int numVertexArrays, |
| const VertexArrayBinding *vertexArrays, const PrimitiveList &primitives, |
| DrawUtilCallback *callback) |
| { |
| const glw::Functions &gl = context.getFunctions(); |
| std::vector<VertexArrayBinding> 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<VertexArrayBinding>::const_iterator vaIter = bindingsWithLocations.begin(); |
| vaIter != bindingsWithLocations.end(); ++vaIter) |
| gl.disableVertexAttribArray(vaIter->binding.location); |
| } |
| |
| void drawFromVAOBuffers(const RenderContext &context, uint32_t 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, uint32_t 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); |
| } |
| } |
| |
| } // namespace glu |