blob: dc8264fab94b5919b7122d7982815e35bb079cbd [file] [log] [blame]
Geoff Langf9a6f082015-01-22 13:32:49 -05001//
2// Copyright 2015 The ANGLE Project Authors. All rights reserved.
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6
7// VertexArrayGL.cpp: Implements the class methods for VertexArrayGL.
8
9#include "libANGLE/renderer/gl/VertexArrayGL.h"
10
Jamie Madill20e005b2017-04-07 14:19:22 -040011#include "common/bitset_utils.h"
Geoff Langf9a6f082015-01-22 13:32:49 -050012#include "common/debug.h"
Geoff Lang7c82bc42015-03-09 16:18:08 -040013#include "common/mathutil.h"
Geoff Lang831b1952015-05-05 11:02:27 -040014#include "common/utilities.h"
Geoff Lang6ae6efc2015-03-09 14:42:35 -040015#include "libANGLE/Buffer.h"
Jamie Madilldc358af2018-07-31 11:22:13 -040016#include "libANGLE/Context.h"
Geoff Langba4c4a82015-02-24 12:38:46 -050017#include "libANGLE/angletypes.h"
Geoff Lang7c82bc42015-03-09 16:18:08 -040018#include "libANGLE/formatutils.h"
Geoff Langba4c4a82015-02-24 12:38:46 -050019#include "libANGLE/renderer/gl/BufferGL.h"
Jamie Madille39e8f42018-10-05 08:17:38 -040020#include "libANGLE/renderer/gl/ContextGL.h"
Geoff Langba4c4a82015-02-24 12:38:46 -050021#include "libANGLE/renderer/gl/FunctionsGL.h"
22#include "libANGLE/renderer/gl/StateManagerGL.h"
Jamie Madill231c7f52017-04-26 13:45:37 -040023#include "libANGLE/renderer/gl/renderergl_utils.h"
Geoff Langf9a6f082015-01-22 13:32:49 -050024
Jamie Madill0b9e9032015-08-17 11:51:52 +000025using namespace gl;
26
Geoff Langf9a6f082015-01-22 13:32:49 -050027namespace rx
28{
Jamie Madill0b9e9032015-08-17 11:51:52 +000029namespace
30{
Shaodf682a82017-03-31 15:13:21 +080031bool SameVertexAttribFormat(const VertexAttribute &a, const VertexAttribute &b)
32{
33 return a.size == b.size && a.type == b.type && a.normalized == b.normalized &&
34 a.pureInteger == b.pureInteger && a.relativeOffset == b.relativeOffset;
35}
36
37bool SameVertexBuffer(const VertexBinding &a, const VertexBinding &b)
38{
Martin Radevdd5f27e2017-06-07 10:17:09 +030039 return a.getStride() == b.getStride() && a.getOffset() == b.getOffset() &&
40 a.getBuffer().get() == b.getBuffer().get();
Shaodf682a82017-03-31 15:13:21 +080041}
42
43bool IsVertexAttribPointerSupported(size_t attribIndex, const VertexAttribute &attrib)
44{
45 return (attribIndex == attrib.bindingIndex && attrib.relativeOffset == 0);
46}
Martin Radev553590a2017-07-31 16:40:39 +030047
48GLuint GetAdjustedDivisor(GLuint numViews, GLuint divisor)
49{
50 return numViews * divisor;
51}
52
Jamie Madill0b9e9032015-08-17 11:51:52 +000053} // anonymous namespace
54
Jamie Madill3f572682016-04-26 13:41:36 -040055VertexArrayGL::VertexArrayGL(const VertexArrayState &state,
Jamie Madill77a90c22015-08-11 16:33:17 -040056 const FunctionsGL *functions,
57 StateManagerGL *stateManager)
Jamie Madill3f572682016-04-26 13:41:36 -040058 : VertexArrayImpl(state),
Geoff Langba4c4a82015-02-24 12:38:46 -050059 mFunctions(functions),
60 mStateManager(stateManager),
61 mVertexArrayID(0),
Martin Radev553590a2017-07-31 16:40:39 +030062 mAppliedNumViews(1),
Jamie Madill77a90c22015-08-11 16:33:17 -040063 mAppliedElementArrayBuffer(),
Jiawei-Shao2597fb62016-12-09 16:38:02 +080064 mAppliedBindings(state.getMaxBindings()),
Geoff Lang7c82bc42015-03-09 16:18:08 -040065 mStreamingElementArrayBufferSize(0),
66 mStreamingElementArrayBuffer(0),
67 mStreamingArrayBufferSize(0),
68 mStreamingArrayBuffer(0)
Geoff Langba4c4a82015-02-24 12:38:46 -050069{
70 ASSERT(mFunctions);
71 ASSERT(mStateManager);
72 mFunctions->genVertexArrays(1, &mVertexArrayID);
73
Jiawei-Shao2597fb62016-12-09 16:38:02 +080074 // Set the cached vertex attribute array and vertex attribute binding array size
75 GLuint maxVertexAttribs = static_cast<GLuint>(state.getMaxAttribs());
76 for (GLuint i = 0; i < maxVertexAttribs; i++)
77 {
78 mAppliedAttributes.emplace_back(i);
79 }
Geoff Langba4c4a82015-02-24 12:38:46 -050080}
Geoff Langf9a6f082015-01-22 13:32:49 -050081
Jamie Madillb980c562018-11-27 11:34:27 -050082VertexArrayGL::~VertexArrayGL() {}
Jamie Madillacf2f3a2017-11-21 19:22:44 -050083
Jamie Madill4928b7c2017-06-20 12:57:39 -040084void VertexArrayGL::destroy(const gl::Context *context)
Geoff Langba4c4a82015-02-24 12:38:46 -050085{
Geoff Lang1eb708e2015-05-04 14:58:23 -040086 mStateManager->deleteVertexArray(mVertexArrayID);
Jamie Madille4634a12018-11-14 09:54:35 -050087 mVertexArrayID = 0;
Martin Radev553590a2017-07-31 16:40:39 +030088 mAppliedNumViews = 1;
Geoff Langba4c4a82015-02-24 12:38:46 -050089
Geoff Lang1eb708e2015-05-04 14:58:23 -040090 mStateManager->deleteBuffer(mStreamingElementArrayBuffer);
91 mStreamingElementArrayBufferSize = 0;
Shao80957d92017-02-20 21:25:59 +080092 mStreamingElementArrayBuffer = 0;
Geoff Lang7c82bc42015-03-09 16:18:08 -040093
Geoff Lang1eb708e2015-05-04 14:58:23 -040094 mStateManager->deleteBuffer(mStreamingArrayBuffer);
95 mStreamingArrayBufferSize = 0;
Shao80957d92017-02-20 21:25:59 +080096 mStreamingArrayBuffer = 0;
Geoff Lang7c82bc42015-03-09 16:18:08 -040097
Jamie Madill4928b7c2017-06-20 12:57:39 -040098 mAppliedElementArrayBuffer.set(context, nullptr);
Jiawei-Shao2597fb62016-12-09 16:38:02 +080099 for (auto &binding : mAppliedBindings)
Geoff Langba4c4a82015-02-24 12:38:46 -0500100 {
Jamie Madillcf9383e2019-01-31 19:52:49 -0500101 binding.setBuffer(context, nullptr);
Geoff Langba4c4a82015-02-24 12:38:46 -0500102 }
103}
Geoff Langf9a6f082015-01-22 13:32:49 -0500104
Jamie Madill0cc11c62018-10-12 18:07:18 -0400105angle::Result VertexArrayGL::syncClientSideData(const gl::Context *context,
106 const gl::AttributesMask &activeAttributesMask,
107 GLint first,
108 GLsizei count,
109 GLsizei instanceCount) const
Geoff Lang7c82bc42015-03-09 16:18:08 -0400110{
Jamie Madill8dc27f92018-11-29 11:45:44 -0500111 return syncDrawState(context, activeAttributesMask, first, count,
112 gl::DrawElementsType::InvalidEnum, nullptr, instanceCount, false, nullptr);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400113}
114
Jiajia Qin47474142017-12-29 13:41:00 +0800115void VertexArrayGL::updateElementArrayBufferBinding(const gl::Context *context) const
Jiajia Qind9671222016-11-29 16:30:31 +0800116{
Jamie Madillcd0a0a32018-10-18 18:41:57 -0400117 gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer();
Jiajia Qin47474142017-12-29 13:41:00 +0800118 if (elementArrayBuffer != nullptr && elementArrayBuffer != mAppliedElementArrayBuffer.get())
Jiajia Qind9671222016-11-29 16:30:31 +0800119 {
120 const BufferGL *bufferGL = GetImplAs<BufferGL>(elementArrayBuffer);
Corentin Wallez336129f2017-10-17 15:55:40 -0400121 mStateManager->bindBuffer(gl::BufferBinding::ElementArray, bufferGL->getBufferID());
Jamie Madill4928b7c2017-06-20 12:57:39 -0400122 mAppliedElementArrayBuffer.set(context, elementArrayBuffer);
Jiajia Qind9671222016-11-29 16:30:31 +0800123 }
Jiajia Qind9671222016-11-29 16:30:31 +0800124}
125
Jamie Madille39e8f42018-10-05 08:17:38 -0400126angle::Result VertexArrayGL::syncDrawState(const gl::Context *context,
127 const gl::AttributesMask &activeAttributesMask,
128 GLint first,
129 GLsizei count,
Jamie Madill8dc27f92018-11-29 11:45:44 -0500130 gl::DrawElementsType type,
Jamie Madille39e8f42018-10-05 08:17:38 -0400131 const void *indices,
132 GLsizei instanceCount,
133 bool primitiveRestartEnabled,
134 const void **outIndices) const
Geoff Lang6ae6efc2015-03-09 14:42:35 -0400135{
Shao80957d92017-02-20 21:25:59 +0800136 // Check if any attributes need to be streamed, determines if the index range needs to be
137 // computed
Jamie Madillbcef3222018-04-13 15:19:11 -0400138 const gl::AttributesMask &needsStreamingAttribs =
Jamie Madilldc358af2018-07-31 11:22:13 -0400139 context->getStateCache().getActiveClientAttribsMask();
Geoff Lang7c82bc42015-03-09 16:18:08 -0400140
Shao80957d92017-02-20 21:25:59 +0800141 // Determine if an index buffer needs to be streamed and the range of vertices that need to be
142 // copied
Geoff Lang3edfe032015-09-04 16:38:24 -0400143 IndexRange indexRange;
Jamie Madill8dc27f92018-11-29 11:45:44 -0500144 if (type != gl::DrawElementsType::InvalidEnum)
Geoff Langba4c4a82015-02-24 12:38:46 -0500145 {
Jamie Madill4928b7c2017-06-20 12:57:39 -0400146 ANGLE_TRY(syncIndexData(context, count, type, indices, primitiveRestartEnabled,
Jamie Madillbcef3222018-04-13 15:19:11 -0400147 needsStreamingAttribs.any(), &indexRange, outIndices));
Geoff Lang7c82bc42015-03-09 16:18:08 -0400148 }
149 else
150 {
Corentin Wallez2c34a4b2015-08-25 16:26:02 -0400151 // Not an indexed call, set the range to [first, first + count - 1]
Geoff Lang7c82bc42015-03-09 16:18:08 -0400152 indexRange.start = first;
Shao80957d92017-02-20 21:25:59 +0800153 indexRange.end = first + count - 1;
Geoff Langba4c4a82015-02-24 12:38:46 -0500154 }
155
Jamie Madillbcef3222018-04-13 15:19:11 -0400156 if (needsStreamingAttribs.any())
Geoff Langba4c4a82015-02-24 12:38:46 -0500157 {
Jamie Madille39e8f42018-10-05 08:17:38 -0400158 ANGLE_TRY(streamAttributes(context, needsStreamingAttribs, instanceCount, indexRange));
Geoff Lang7c82bc42015-03-09 16:18:08 -0400159 }
160
Jamie Madill7c985f52018-11-29 18:16:17 -0500161 return angle::Result::Continue;
Geoff Lang7c82bc42015-03-09 16:18:08 -0400162}
163
Jamie Madille39e8f42018-10-05 08:17:38 -0400164angle::Result VertexArrayGL::syncIndexData(const gl::Context *context,
165 GLsizei count,
Jamie Madill8dc27f92018-11-29 11:45:44 -0500166 gl::DrawElementsType type,
Jamie Madille39e8f42018-10-05 08:17:38 -0400167 const void *indices,
168 bool primitiveRestartEnabled,
169 bool attributesNeedStreaming,
170 IndexRange *outIndexRange,
171 const void **outIndices) const
Geoff Lang7c82bc42015-03-09 16:18:08 -0400172{
173 ASSERT(outIndices);
174
Jamie Madillcd0a0a32018-10-18 18:41:57 -0400175 gl::Buffer *elementArrayBuffer = mState.getElementArrayBuffer();
Jamie Madill8e344942015-07-09 14:22:07 -0400176
Geoff Lang7c82bc42015-03-09 16:18:08 -0400177 // Need to check the range of indices if attributes need to be streamed
Jamie Madill8e344942015-07-09 14:22:07 -0400178 if (elementArrayBuffer != nullptr)
Geoff Lang7c82bc42015-03-09 16:18:08 -0400179 {
Jiajia Qin47474142017-12-29 13:41:00 +0800180 ASSERT(elementArrayBuffer == mAppliedElementArrayBuffer.get());
Geoff Lang7c82bc42015-03-09 16:18:08 -0400181 // Only compute the index range if the attributes also need to be streamed
182 if (attributesNeedStreaming)
183 {
184 ptrdiff_t elementArrayBufferOffset = reinterpret_cast<ptrdiff_t>(indices);
Jamie Madill526392d2018-11-16 09:35:14 -0500185 ANGLE_TRY(mState.getElementArrayBuffer()->getIndexRange(
186 context, type, elementArrayBufferOffset, count, primitiveRestartEnabled,
187 outIndexRange));
Geoff Lang7c82bc42015-03-09 16:18:08 -0400188 }
189
Shao80957d92017-02-20 21:25:59 +0800190 // Indices serves as an offset into the index buffer in this case, use the same value for
191 // the draw call
Geoff Lang7c82bc42015-03-09 16:18:08 -0400192 *outIndices = indices;
193 }
194 else
195 {
196 // Need to stream the index buffer
197 // TODO: if GLES, nothing needs to be streamed
198
199 // Only compute the index range if the attributes also need to be streamed
200 if (attributesNeedStreaming)
201 {
Geoff Lang3edfe032015-09-04 16:38:24 -0400202 *outIndexRange = ComputeIndexRange(type, indices, count, primitiveRestartEnabled);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400203 }
204
205 // Allocate the streaming element array buffer
206 if (mStreamingElementArrayBuffer == 0)
207 {
208 mFunctions->genBuffers(1, &mStreamingElementArrayBuffer);
209 mStreamingElementArrayBufferSize = 0;
210 }
211
Geoff Lang5f3047c2018-02-14 14:39:12 -0500212 mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
213
Corentin Wallez336129f2017-10-17 15:55:40 -0400214 mStateManager->bindBuffer(gl::BufferBinding::ElementArray, mStreamingElementArrayBuffer);
Jamie Madill4928b7c2017-06-20 12:57:39 -0400215 mAppliedElementArrayBuffer.set(context, nullptr);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400216
217 // Make sure the element array buffer is large enough
Jamie Madill8dc27f92018-11-29 11:45:44 -0500218 const GLuint indexTypeBytes = gl::GetDrawElementsTypeSize(type);
219 size_t requiredStreamingBufferSize = indexTypeBytes * count;
Geoff Lang7c82bc42015-03-09 16:18:08 -0400220 if (requiredStreamingBufferSize > mStreamingElementArrayBufferSize)
221 {
222 // Copy the indices in while resizing the buffer
Shao80957d92017-02-20 21:25:59 +0800223 mFunctions->bufferData(GL_ELEMENT_ARRAY_BUFFER, requiredStreamingBufferSize, indices,
224 GL_DYNAMIC_DRAW);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400225 mStreamingElementArrayBufferSize = requiredStreamingBufferSize;
226 }
227 else
228 {
229 // Put the indices at the beginning of the buffer
Shao80957d92017-02-20 21:25:59 +0800230 mFunctions->bufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, requiredStreamingBufferSize,
231 indices);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400232 }
233
Shao80957d92017-02-20 21:25:59 +0800234 // Set the index offset for the draw call to zero since the supplied index pointer is to
235 // client data
Geoff Lang7c82bc42015-03-09 16:18:08 -0400236 *outIndices = nullptr;
237 }
238
Jamie Madill7c985f52018-11-29 18:16:17 -0500239 return angle::Result::Continue;
Geoff Lang7c82bc42015-03-09 16:18:08 -0400240}
241
Jamie Madillbcef3222018-04-13 15:19:11 -0400242void VertexArrayGL::computeStreamingAttributeSizes(const gl::AttributesMask &attribsToStream,
Geoff Lang3cf12ce2015-08-27 14:40:48 -0400243 GLsizei instanceCount,
Geoff Lang3edfe032015-09-04 16:38:24 -0400244 const gl::IndexRange &indexRange,
Jamie Madill0b9e9032015-08-17 11:51:52 +0000245 size_t *outStreamingDataSize,
246 size_t *outMaxAttributeDataSize) const
Geoff Lang7c82bc42015-03-09 16:18:08 -0400247{
Jamie Madill0b9e9032015-08-17 11:51:52 +0000248 *outStreamingDataSize = 0;
249 *outMaxAttributeDataSize = 0;
250
Jamie Madillbcef3222018-04-13 15:19:11 -0400251 ASSERT(attribsToStream.any());
Jamie Madill0b9e9032015-08-17 11:51:52 +0000252
Jamie Madill492f58e2017-10-09 19:41:33 -0400253 const auto &attribs = mState.getVertexAttributes();
254 const auto &bindings = mState.getVertexBindings();
Jamie Madill6de51852017-04-12 09:53:01 -0400255
Jamie Madill6de51852017-04-12 09:53:01 -0400256 for (auto idx : attribsToStream)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000257 {
Shao80957d92017-02-20 21:25:59 +0800258 const auto &attrib = attribs[idx];
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800259 const auto &binding = bindings[attrib.bindingIndex];
Jamie Madill0b9e9032015-08-17 11:51:52 +0000260
Jamie Madill0b9e9032015-08-17 11:51:52 +0000261 // If streaming is going to be required, compute the size of the required buffer
262 // and how much slack space at the beginning of the buffer will be required by determining
263 // the attribute with the largest data size.
Jamie Madille4634a12018-11-14 09:54:35 -0500264 size_t typeSize = ComputeVertexAttributeTypeSize(attrib);
Martin Radev553590a2017-07-31 16:40:39 +0300265 GLuint adjustedDivisor = GetAdjustedDivisor(mAppliedNumViews, binding.getDivisor());
266 *outStreamingDataSize +=
267 typeSize * ComputeVertexBindingElementCount(adjustedDivisor, indexRange.vertexCount(),
268 instanceCount);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000269 *outMaxAttributeDataSize = std::max(*outMaxAttributeDataSize, typeSize);
270 }
271}
272
Jamie Madille39e8f42018-10-05 08:17:38 -0400273angle::Result VertexArrayGL::streamAttributes(const gl::Context *context,
274 const gl::AttributesMask &attribsToStream,
275 GLsizei instanceCount,
276 const gl::IndexRange &indexRange) const
Jamie Madill0b9e9032015-08-17 11:51:52 +0000277{
278 // Sync the vertex attribute state and track what data needs to be streamed
279 size_t streamingDataSize = 0;
280 size_t maxAttributeDataSize = 0;
281
Jamie Madillbcef3222018-04-13 15:19:11 -0400282 computeStreamingAttributeSizes(attribsToStream, instanceCount, indexRange, &streamingDataSize,
283 &maxAttributeDataSize);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000284
285 if (streamingDataSize == 0)
286 {
Jamie Madill7c985f52018-11-29 18:16:17 -0500287 return angle::Result::Continue;
Jamie Madill0b9e9032015-08-17 11:51:52 +0000288 }
289
Geoff Lang7c82bc42015-03-09 16:18:08 -0400290 if (mStreamingArrayBuffer == 0)
291 {
292 mFunctions->genBuffers(1, &mStreamingArrayBuffer);
293 mStreamingArrayBufferSize = 0;
294 }
295
Shao80957d92017-02-20 21:25:59 +0800296 // If first is greater than zero, a slack space needs to be left at the beginning of the buffer
297 // so that the same 'first' argument can be passed into the draw call.
298 const size_t bufferEmptySpace = maxAttributeDataSize * indexRange.start;
Geoff Lang7c82bc42015-03-09 16:18:08 -0400299 const size_t requiredBufferSize = streamingDataSize + bufferEmptySpace;
300
Corentin Wallez336129f2017-10-17 15:55:40 -0400301 mStateManager->bindBuffer(gl::BufferBinding::Array, mStreamingArrayBuffer);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400302 if (requiredBufferSize > mStreamingArrayBufferSize)
303 {
304 mFunctions->bufferData(GL_ARRAY_BUFFER, requiredBufferSize, nullptr, GL_DYNAMIC_DRAW);
305 mStreamingArrayBufferSize = requiredBufferSize;
306 }
307
Geoff Lang5f3047c2018-02-14 14:39:12 -0500308 mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
309
Geoff Lang7c82bc42015-03-09 16:18:08 -0400310 // Unmapping a buffer can return GL_FALSE to indicate that the system has corrupted the data
Shao80957d92017-02-20 21:25:59 +0800311 // somehow (such as by a screen change), retry writing the data a few times and return
312 // OUT_OF_MEMORY if that fails.
313 GLboolean unmapResult = GL_FALSE;
Geoff Lang7c82bc42015-03-09 16:18:08 -0400314 size_t unmapRetryAttempts = 5;
315 while (unmapResult != GL_TRUE && --unmapRetryAttempts > 0)
316 {
Geoff Langb2ebb5e2016-06-08 18:17:55 +0000317 uint8_t *bufferPointer = MapBufferRangeWithFallback(mFunctions, GL_ARRAY_BUFFER, 0,
318 requiredBufferSize, GL_MAP_WRITE_BIT);
Geoff Lang7c82bc42015-03-09 16:18:08 -0400319 size_t curBufferOffset = bufferEmptySpace;
320
Jamie Madill492f58e2017-10-09 19:41:33 -0400321 const auto &attribs = mState.getVertexAttributes();
322 const auto &bindings = mState.getVertexBindings();
Jamie Madill6de51852017-04-12 09:53:01 -0400323
Jamie Madill6de51852017-04-12 09:53:01 -0400324 for (auto idx : attribsToStream)
Geoff Lang7c82bc42015-03-09 16:18:08 -0400325 {
Jamie Madille4634a12018-11-14 09:54:35 -0500326 const auto &attrib = attribs[idx];
Shaodde78e82017-05-22 14:13:27 +0800327 ASSERT(IsVertexAttribPointerSupported(idx, attrib));
328
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800329 const auto &binding = bindings[attrib.bindingIndex];
Geoff Lang7c82bc42015-03-09 16:18:08 -0400330
Martin Radev553590a2017-07-31 16:40:39 +0300331 GLuint adjustedDivisor = GetAdjustedDivisor(mAppliedNumViews, binding.getDivisor());
332 const size_t streamedVertexCount = ComputeVertexBindingElementCount(
333 adjustedDivisor, indexRange.vertexCount(), instanceCount);
Geoff Lang3cf12ce2015-08-27 14:40:48 -0400334
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800335 const size_t sourceStride = ComputeVertexAttributeStride(attrib, binding);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000336 const size_t destStride = ComputeVertexAttributeTypeSize(attrib);
337
Geoff Lang38a24a92017-02-15 13:53:06 -0500338 // Vertices do not apply the 'start' offset when the divisor is non-zero even when doing
339 // a non-instanced draw call
Martin Radev553590a2017-07-31 16:40:39 +0300340 const size_t firstIndex = adjustedDivisor == 0 ? indexRange.start : 0;
Geoff Lang38a24a92017-02-15 13:53:06 -0500341
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800342 // Attributes using client memory ignore the VERTEX_ATTRIB_BINDING state.
343 // https://www.opengl.org/registry/specs/ARB/vertex_attrib_binding.txt
Rafael Cintron05a449a2018-06-20 18:08:04 -0700344 const uint8_t *inputPointer = static_cast<const uint8_t *>(attrib.pointer);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000345
346 // Pack the data when copying it, user could have supplied a very large stride that
347 // would cause the buffer to be much larger than needed.
348 if (destStride == sourceStride)
Jamie Madill8e344942015-07-09 14:22:07 -0400349 {
Jamie Madill0b9e9032015-08-17 11:51:52 +0000350 // Can copy in one go, the data is packed
Geoff Lang38a24a92017-02-15 13:53:06 -0500351 memcpy(bufferPointer + curBufferOffset, inputPointer + (sourceStride * firstIndex),
Jamie Madill0b9e9032015-08-17 11:51:52 +0000352 destStride * streamedVertexCount);
Jamie Madill6d51c702015-08-14 10:38:10 -0400353 }
Jamie Madill0b9e9032015-08-17 11:51:52 +0000354 else
355 {
356 // Copy each vertex individually
Geoff Lang6b10ddb2015-09-02 15:55:10 -0400357 for (size_t vertexIdx = 0; vertexIdx < streamedVertexCount; vertexIdx++)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000358 {
Shao80957d92017-02-20 21:25:59 +0800359 uint8_t *out = bufferPointer + curBufferOffset + (destStride * vertexIdx);
Geoff Lang38a24a92017-02-15 13:53:06 -0500360 const uint8_t *in = inputPointer + sourceStride * (vertexIdx + firstIndex);
Corentin Wallez4d5362d2015-08-25 11:25:14 -0400361 memcpy(out, in, destStride);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000362 }
363 }
364
365 // Compute where the 0-index vertex would be.
Geoff Lang38a24a92017-02-15 13:53:06 -0500366 const size_t vertexStartOffset = curBufferOffset - (firstIndex * destStride);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000367
Shaodf682a82017-03-31 15:13:21 +0800368 callVertexAttribPointer(static_cast<GLuint>(idx), attrib,
369 static_cast<GLsizei>(destStride),
370 static_cast<GLintptr>(vertexStartOffset));
Jamie Madill0b9e9032015-08-17 11:51:52 +0000371
372 curBufferOffset += destStride * streamedVertexCount;
Geoff Lang7c82bc42015-03-09 16:18:08 -0400373 }
374
375 unmapResult = mFunctions->unmapBuffer(GL_ARRAY_BUFFER);
376 }
377
Jamie Madille39e8f42018-10-05 08:17:38 -0400378 ANGLE_CHECK(GetImplAs<ContextGL>(context), unmapResult == GL_TRUE,
379 "Failed to unmap the client data streaming buffer.", GL_OUT_OF_MEMORY);
Jamie Madill7c985f52018-11-29 18:16:17 -0500380 return angle::Result::Continue;
Geoff Langba4c4a82015-02-24 12:38:46 -0500381}
382
383GLuint VertexArrayGL::getVertexArrayID() const
384{
385 return mVertexArrayID;
Geoff Langf9a6f082015-01-22 13:32:49 -0500386}
387
Geoff Lang294cad92015-05-26 15:11:23 -0400388GLuint VertexArrayGL::getAppliedElementArrayBufferID() const
389{
Jamie Madill77a90c22015-08-11 16:33:17 -0400390 if (mAppliedElementArrayBuffer.get() == nullptr)
391 {
392 return mStreamingElementArrayBuffer;
393 }
394
395 return GetImplAs<BufferGL>(mAppliedElementArrayBuffer.get())->getBufferID();
Geoff Lang294cad92015-05-26 15:11:23 -0400396}
397
Jamie Madill0b9e9032015-08-17 11:51:52 +0000398void VertexArrayGL::updateAttribEnabled(size_t attribIndex)
399{
Shahbaz Youssefi337bd692018-08-22 16:16:38 -0400400 const bool enabled = mState.getVertexAttribute(attribIndex).enabled &
401 mProgramActiveAttribLocationsMask.test(attribIndex);
Shaodf682a82017-03-31 15:13:21 +0800402 if (mAppliedAttributes[attribIndex].enabled == enabled)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000403 {
404 return;
405 }
406
Shaodf682a82017-03-31 15:13:21 +0800407 if (enabled)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000408 {
409 mFunctions->enableVertexAttribArray(static_cast<GLuint>(attribIndex));
410 }
411 else
412 {
413 mFunctions->disableVertexAttribArray(static_cast<GLuint>(attribIndex));
414 }
Shaodf682a82017-03-31 15:13:21 +0800415
416 mAppliedAttributes[attribIndex].enabled = enabled;
Jamie Madill0b9e9032015-08-17 11:51:52 +0000417}
418
Jamie Madill4928b7c2017-06-20 12:57:39 -0400419void VertexArrayGL::updateAttribPointer(const gl::Context *context, size_t attribIndex)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000420{
Jamie Madill492f58e2017-10-09 19:41:33 -0400421 const VertexAttribute &attrib = mState.getVertexAttribute(attribIndex);
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800422
Shaodde78e82017-05-22 14:13:27 +0800423 // According to SPEC, VertexAttribPointer should update the binding indexed attribIndex instead
424 // of the binding indexed attrib.bindingIndex (unless attribIndex == attrib.bindingIndex).
Jamie Madill492f58e2017-10-09 19:41:33 -0400425 const VertexBinding &binding = mState.getVertexBinding(attribIndex);
Shaodf682a82017-03-31 15:13:21 +0800426
Shaodde78e82017-05-22 14:13:27 +0800427 // Early return when the vertex attribute isn't using a buffer object:
428 // - If we need to stream, defer the attribPointer to the draw call.
429 // - Skip the attribute that is disabled and uses a client memory pointer.
430 // - Skip the attribute whose buffer is detached by BindVertexBuffer. Since it cannot have a
431 // client memory pointer either, it must be disabled and shouldn't affect the draw.
Jamie Madillbcef3222018-04-13 15:19:11 -0400432 const auto &bindingBuffer = binding.getBuffer();
Shaodde78e82017-05-22 14:13:27 +0800433 const Buffer *arrayBuffer = bindingBuffer.get();
434 if (arrayBuffer == nullptr)
435 {
436 // Mark the applied binding isn't using a buffer by setting its buffer to nullptr so that if
437 // it starts to use a buffer later, there is no chance that the caching will skip it.
Jamie Madillcf9383e2019-01-31 19:52:49 -0500438 mAppliedBindings[attribIndex].setBuffer(context, nullptr);
Shaodde78e82017-05-22 14:13:27 +0800439 return;
440 }
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800441
Shaodf682a82017-03-31 15:13:21 +0800442 // We do not need to compare attrib.pointer because when we use a different client memory
443 // pointer, we don't need to update mAttributesNeedStreaming by binding.buffer and we won't
444 // update attribPointer in this function.
Shaodde78e82017-05-22 14:13:27 +0800445 if ((SameVertexAttribFormat(mAppliedAttributes[attribIndex], attrib)) &&
446 (mAppliedAttributes[attribIndex].bindingIndex == attrib.bindingIndex) &&
447 (SameVertexBuffer(mAppliedBindings[attribIndex], binding)))
Jamie Madill0b9e9032015-08-17 11:51:52 +0000448 {
449 return;
450 }
451
Shao4181bc92017-03-17 14:55:25 +0800452 // Since ANGLE always uses a non-zero VAO, we cannot use a client memory pointer on it:
453 // [OpenGL ES 3.0.2] Section 2.8 page 24:
454 // An INVALID_OPERATION error is generated when a non-zero vertex array object is bound,
455 // zero is bound to the ARRAY_BUFFER buffer object binding point, and the pointer argument
456 // is not NULL.
Shaodf682a82017-03-31 15:13:21 +0800457
Shao4181bc92017-03-17 14:55:25 +0800458 const BufferGL *arrayBufferGL = GetImplAs<BufferGL>(arrayBuffer);
Corentin Wallez336129f2017-10-17 15:55:40 -0400459 mStateManager->bindBuffer(gl::BufferBinding::Array, arrayBufferGL->getBufferID());
Martin Radevdd5f27e2017-06-07 10:17:09 +0300460 callVertexAttribPointer(static_cast<GLuint>(attribIndex), attrib, binding.getStride(),
461 binding.getOffset());
Shaodf682a82017-03-31 15:13:21 +0800462
Shaodde78e82017-05-22 14:13:27 +0800463 mAppliedAttributes[attribIndex].size = attrib.size;
464 mAppliedAttributes[attribIndex].type = attrib.type;
465 mAppliedAttributes[attribIndex].normalized = attrib.normalized;
466 mAppliedAttributes[attribIndex].pureInteger = attrib.pureInteger;
Shaodf682a82017-03-31 15:13:21 +0800467
Shaodde78e82017-05-22 14:13:27 +0800468 // After VertexAttribPointer, attrib.relativeOffset is set to 0 and attrib.bindingIndex is set
469 // to attribIndex in driver. If attrib.relativeOffset != 0 or attrib.bindingIndex !=
470 // attribIndex, they should be set in updateAttribFormat and updateAttribBinding. The cache
471 // should be consistent with driver so that we won't miss anything.
472 mAppliedAttributes[attribIndex].relativeOffset = 0;
473 mAppliedAttributes[attribIndex].bindingIndex = static_cast<GLuint>(attribIndex);
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800474
Shaodde78e82017-05-22 14:13:27 +0800475 mAppliedBindings[attribIndex].setStride(binding.getStride());
476 mAppliedBindings[attribIndex].setOffset(binding.getOffset());
Jamie Madillcf9383e2019-01-31 19:52:49 -0500477 mAppliedBindings[attribIndex].setBuffer(context, binding.getBuffer().get());
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800478}
479
Shaodf682a82017-03-31 15:13:21 +0800480void VertexArrayGL::callVertexAttribPointer(GLuint attribIndex,
481 const VertexAttribute &attrib,
482 GLsizei stride,
483 GLintptr offset) const
484{
485 const GLvoid *pointer = reinterpret_cast<const GLvoid *>(offset);
486 if (attrib.pureInteger)
487 {
488 ASSERT(!attrib.normalized);
Jamie Madilldd34b3b2019-01-16 09:59:54 -0500489 mFunctions->vertexAttribIPointer(attribIndex, attrib.size, gl::ToGLenum(attrib.type),
490 stride, pointer);
Shaodf682a82017-03-31 15:13:21 +0800491 }
492 else
493 {
Jamie Madilldd34b3b2019-01-16 09:59:54 -0500494 mFunctions->vertexAttribPointer(attribIndex, attrib.size, gl::ToGLenum(attrib.type),
495 attrib.normalized, stride, pointer);
Shaodf682a82017-03-31 15:13:21 +0800496 }
497}
498
Shaodde78e82017-05-22 14:13:27 +0800499bool VertexArrayGL::supportVertexAttribBinding() const
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800500{
Shaodde78e82017-05-22 14:13:27 +0800501 ASSERT(mFunctions);
502 return (mFunctions->vertexAttribBinding != nullptr);
503}
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800504
Shaodde78e82017-05-22 14:13:27 +0800505void VertexArrayGL::updateAttribFormat(size_t attribIndex)
506{
507 ASSERT(supportVertexAttribBinding());
508
Jamie Madill492f58e2017-10-09 19:41:33 -0400509 const VertexAttribute &attrib = mState.getVertexAttribute(attribIndex);
Shaodde78e82017-05-22 14:13:27 +0800510 if (SameVertexAttribFormat(mAppliedAttributes[attribIndex], attrib))
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800511 {
Shaodf682a82017-03-31 15:13:21 +0800512 return;
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800513 }
Shaodf682a82017-03-31 15:13:21 +0800514
Shaodde78e82017-05-22 14:13:27 +0800515 if (attrib.pureInteger)
516 {
517 ASSERT(!attrib.normalized);
Jamie Madilldd34b3b2019-01-16 09:59:54 -0500518 mFunctions->vertexAttribIFormat(static_cast<GLuint>(attribIndex), attrib.size,
519 gl::ToGLenum(attrib.type), attrib.relativeOffset);
Shaodde78e82017-05-22 14:13:27 +0800520 }
521 else
522 {
Jamie Madilldd34b3b2019-01-16 09:59:54 -0500523 mFunctions->vertexAttribFormat(static_cast<GLuint>(attribIndex), attrib.size,
524 gl::ToGLenum(attrib.type), attrib.normalized,
525 attrib.relativeOffset);
Shaodde78e82017-05-22 14:13:27 +0800526 }
527
528 mAppliedAttributes[attribIndex].size = attrib.size;
529 mAppliedAttributes[attribIndex].type = attrib.type;
530 mAppliedAttributes[attribIndex].normalized = attrib.normalized;
531 mAppliedAttributes[attribIndex].pureInteger = attrib.pureInteger;
532 mAppliedAttributes[attribIndex].relativeOffset = attrib.relativeOffset;
533}
534
535void VertexArrayGL::updateAttribBinding(size_t attribIndex)
536{
537 ASSERT(supportVertexAttribBinding());
538
Jamie Madill492f58e2017-10-09 19:41:33 -0400539 GLuint bindingIndex = mState.getVertexAttribute(attribIndex).bindingIndex;
Shaodde78e82017-05-22 14:13:27 +0800540 if (mAppliedAttributes[attribIndex].bindingIndex == bindingIndex)
541 {
542 return;
543 }
544
545 mFunctions->vertexAttribBinding(static_cast<GLuint>(attribIndex), bindingIndex);
Shaodf682a82017-03-31 15:13:21 +0800546
547 mAppliedAttributes[attribIndex].bindingIndex = bindingIndex;
Shaodde78e82017-05-22 14:13:27 +0800548}
549
550void VertexArrayGL::updateBindingBuffer(const gl::Context *context, size_t bindingIndex)
551{
552 ASSERT(supportVertexAttribBinding());
553
Jamie Madill492f58e2017-10-09 19:41:33 -0400554 const VertexBinding &binding = mState.getVertexBinding(bindingIndex);
Shaodde78e82017-05-22 14:13:27 +0800555 if (SameVertexBuffer(mAppliedBindings[bindingIndex], binding))
556 {
557 return;
558 }
559
560 const Buffer *arrayBuffer = binding.getBuffer().get();
561 GLuint bufferId = 0;
562 if (arrayBuffer != nullptr)
563 {
564 bufferId = GetImplAs<BufferGL>(arrayBuffer)->getBufferID();
565 }
566
567 mFunctions->bindVertexBuffer(static_cast<GLuint>(bindingIndex), bufferId, binding.getOffset(),
568 binding.getStride());
569
570 mAppliedBindings[bindingIndex].setStride(binding.getStride());
571 mAppliedBindings[bindingIndex].setOffset(binding.getOffset());
Jamie Madillcf9383e2019-01-31 19:52:49 -0500572 mAppliedBindings[bindingIndex].setBuffer(context, binding.getBuffer().get());
Shaodde78e82017-05-22 14:13:27 +0800573}
574
575void VertexArrayGL::updateBindingDivisor(size_t bindingIndex)
576{
Martin Radev553590a2017-07-31 16:40:39 +0300577 GLuint adjustedDivisor =
Jamie Madill492f58e2017-10-09 19:41:33 -0400578 GetAdjustedDivisor(mAppliedNumViews, mState.getVertexBinding(bindingIndex).getDivisor());
Martin Radev553590a2017-07-31 16:40:39 +0300579 if (mAppliedBindings[bindingIndex].getDivisor() == adjustedDivisor)
Shaodde78e82017-05-22 14:13:27 +0800580 {
581 return;
582 }
583
584 if (supportVertexAttribBinding())
585 {
Martin Radev553590a2017-07-31 16:40:39 +0300586 mFunctions->vertexBindingDivisor(static_cast<GLuint>(bindingIndex), adjustedDivisor);
Shaodde78e82017-05-22 14:13:27 +0800587 }
588 else
589 {
590 // We can only use VertexAttribDivisor on platforms that don't support Vertex Attrib
591 // Binding.
Martin Radev553590a2017-07-31 16:40:39 +0300592 mFunctions->vertexAttribDivisor(static_cast<GLuint>(bindingIndex), adjustedDivisor);
Shaodde78e82017-05-22 14:13:27 +0800593 }
594
Martin Radev553590a2017-07-31 16:40:39 +0300595 mAppliedBindings[bindingIndex].setDivisor(adjustedDivisor);
Jamie Madill0b9e9032015-08-17 11:51:52 +0000596}
597
Jamie Madille858cb12018-03-27 09:44:32 -0400598void VertexArrayGL::syncDirtyAttrib(const gl::Context *context,
599 size_t attribIndex,
600 const gl::VertexArray::DirtyAttribBits &dirtyAttribBits)
601{
602 ASSERT(dirtyAttribBits.any());
603
604 for (size_t dirtyBit : dirtyAttribBits)
605 {
606 switch (dirtyBit)
607 {
608 case VertexArray::DIRTY_ATTRIB_ENABLED:
609 updateAttribEnabled(attribIndex);
610 break;
611
612 case VertexArray::DIRTY_ATTRIB_POINTER:
613 updateAttribPointer(context, attribIndex);
614 break;
615
616 case VertexArray::DIRTY_ATTRIB_FORMAT:
617 ASSERT(supportVertexAttribBinding());
618 updateAttribFormat(attribIndex);
619 break;
620
621 case VertexArray::DIRTY_ATTRIB_BINDING:
622 ASSERT(supportVertexAttribBinding());
623 updateAttribBinding(attribIndex);
624 break;
625
626 default:
627 UNREACHABLE();
628 break;
629 }
630 }
631}
632
633void VertexArrayGL::syncDirtyBinding(const gl::Context *context,
634 size_t bindingIndex,
635 const gl::VertexArray::DirtyBindingBits &dirtyBindingBits)
636{
Jamie Madill02c9c042018-04-17 13:43:48 -0400637 // Dependent state changes in buffers can trigger updates with no dirty bits set.
Jamie Madille858cb12018-03-27 09:44:32 -0400638
639 for (size_t dirtyBit : dirtyBindingBits)
640 {
641 switch (dirtyBit)
642 {
643 case VertexArray::DIRTY_BINDING_BUFFER:
644 ASSERT(supportVertexAttribBinding());
645 updateBindingBuffer(context, bindingIndex);
646 break;
647
648 case VertexArray::DIRTY_BINDING_DIVISOR:
649 updateBindingDivisor(bindingIndex);
650 break;
651
652 default:
653 UNREACHABLE();
654 break;
655 }
656 }
657}
658
Jamie Madillc759b8b2019-01-03 15:16:50 -0500659#define ANGLE_DIRTY_ATTRIB_FUNC(INDEX) \
660 case VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX: \
661 syncDirtyAttrib(context, INDEX, (*attribBits)[INDEX]); \
662 (*attribBits)[INDEX].reset(); \
Jamie Madilla56467e2018-04-11 16:19:41 -0400663 break;
664
Jamie Madillc759b8b2019-01-03 15:16:50 -0500665#define ANGLE_DIRTY_BINDING_FUNC(INDEX) \
666 case VertexArray::DIRTY_BIT_BINDING_0 + INDEX: \
667 syncDirtyBinding(context, INDEX, (*bindingBits)[INDEX]); \
668 (*bindingBits)[INDEX].reset(); \
Jamie Madilla56467e2018-04-11 16:19:41 -0400669 break;
670
671#define ANGLE_DIRTY_BUFFER_DATA_FUNC(INDEX) \
672 case VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX: \
673 break;
674
Jamie Madill6f755b22018-10-09 12:48:54 -0400675angle::Result VertexArrayGL::syncState(const gl::Context *context,
676 const gl::VertexArray::DirtyBits &dirtyBits,
Jamie Madillc759b8b2019-01-03 15:16:50 -0500677 gl::VertexArray::DirtyAttribBitsArray *attribBits,
678 gl::VertexArray::DirtyBindingBitsArray *bindingBits)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000679{
Shaodf682a82017-03-31 15:13:21 +0800680 mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
681
Jamie Madill6de51852017-04-12 09:53:01 -0400682 for (size_t dirtyBit : dirtyBits)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000683 {
Jamie Madille858cb12018-03-27 09:44:32 -0400684 switch (dirtyBit)
Jamie Madill0b9e9032015-08-17 11:51:52 +0000685 {
Jamie Madille858cb12018-03-27 09:44:32 -0400686 case VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
687 updateElementArrayBufferBinding(context);
688 break;
Jiawei-Shao2597fb62016-12-09 16:38:02 +0800689
Jamie Madill09463932018-04-04 05:26:59 -0400690 case VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
691 break;
692
Jamie Madillc09ae152019-02-01 14:16:32 -0500693 ANGLE_VERTEX_INDEX_CASES(ANGLE_DIRTY_ATTRIB_FUNC)
694 ANGLE_VERTEX_INDEX_CASES(ANGLE_DIRTY_BINDING_FUNC)
695 ANGLE_VERTEX_INDEX_CASES(ANGLE_DIRTY_BUFFER_DATA_FUNC)
Jamie Madilla56467e2018-04-11 16:19:41 -0400696
Jamie Madille858cb12018-03-27 09:44:32 -0400697 default:
Jamie Madilla56467e2018-04-11 16:19:41 -0400698 UNREACHABLE();
Jamie Madille858cb12018-03-27 09:44:32 -0400699 break;
Jamie Madill0b9e9032015-08-17 11:51:52 +0000700 }
Jamie Madill0b9e9032015-08-17 11:51:52 +0000701 }
Frank Henigman0af5b862018-03-27 20:19:33 -0400702
Jamie Madill7c985f52018-11-29 18:16:17 -0500703 return angle::Result::Continue;
Jamie Madill0b9e9032015-08-17 11:51:52 +0000704}
705
Martin Radev553590a2017-07-31 16:40:39 +0300706void VertexArrayGL::applyNumViewsToDivisor(int numViews)
707{
708 if (numViews != mAppliedNumViews)
709 {
710 mStateManager->bindVertexArray(mVertexArrayID, getAppliedElementArrayBufferID());
711 mAppliedNumViews = numViews;
712 for (size_t index = 0u; index < mAppliedBindings.size(); ++index)
713 {
714 updateBindingDivisor(index);
715 }
716 }
717}
718
Shahbaz Youssefi337bd692018-08-22 16:16:38 -0400719void VertexArrayGL::applyActiveAttribLocationsMask(const gl::AttributesMask &activeMask)
720{
Jamie Madille4634a12018-11-14 09:54:35 -0500721 gl::AttributesMask updateMask = mProgramActiveAttribLocationsMask ^ activeMask;
Qin Jiajiafcf10b62018-09-19 12:53:18 +0800722 if (!updateMask.any())
723 {
724 return;
725 }
726 ASSERT(mVertexArrayID == mStateManager->getVertexArrayID());
Shahbaz Youssefi337bd692018-08-22 16:16:38 -0400727 mProgramActiveAttribLocationsMask = activeMask;
728
729 for (size_t attribIndex : updateMask)
730 {
731 updateAttribEnabled(attribIndex);
732 }
733}
734
Jamie Madill09463932018-04-04 05:26:59 -0400735} // namespace rx