1 //
2 // Copyright 2024 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 // VertexArrayWgpu.cpp:
7 // Implements the class methods for VertexArrayWgpu.
8 //
9
10 #include "libANGLE/renderer/wgpu/VertexArrayWgpu.h"
11
12 #include "common/debug.h"
13 #include "libANGLE/renderer/wgpu/ContextWgpu.h"
14
15 namespace rx
16 {
17
VertexArrayWgpu(const gl::VertexArrayState & data)18 VertexArrayWgpu::VertexArrayWgpu(const gl::VertexArrayState &data) : VertexArrayImpl(data)
19 {
20 // Pre-initialize mCurrentIndexBuffer to a streaming buffer because no index buffer dirty bit is
21 // triggered if our first draw call has no buffer bound.
22 mCurrentIndexBuffer = &mStreamingIndexBuffer;
23 }
24
syncState(const gl::Context * context,const gl::VertexArray::DirtyBits & dirtyBits,gl::VertexArray::DirtyAttribBitsArray * attribBits,gl::VertexArray::DirtyBindingBitsArray * bindingBits)25 angle::Result VertexArrayWgpu::syncState(const gl::Context *context,
26 const gl::VertexArray::DirtyBits &dirtyBits,
27 gl::VertexArray::DirtyAttribBitsArray *attribBits,
28 gl::VertexArray::DirtyBindingBitsArray *bindingBits)
29 {
30 ASSERT(dirtyBits.any());
31
32 ContextWgpu *contextWgpu = GetImplAs<ContextWgpu>(context);
33
34 const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
35 const std::vector<gl::VertexBinding> &bindings = mState.getVertexBindings();
36
37 gl::AttributesMask syncedAttributes;
38
39 for (auto iter = dirtyBits.begin(), endIter = dirtyBits.end(); iter != endIter; ++iter)
40 {
41 size_t dirtyBit = *iter;
42 switch (dirtyBit)
43 {
44 case gl::VertexArray::DIRTY_BIT_LOST_OBSERVATION:
45 break;
46
47 case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER:
48 ANGLE_TRY(syncDirtyElementArrayBuffer(contextWgpu));
49 contextWgpu->invalidateIndexBuffer();
50 break;
51
52 case gl::VertexArray::DIRTY_BIT_ELEMENT_ARRAY_BUFFER_DATA:
53 break;
54
55 #define ANGLE_VERTEX_DIRTY_ATTRIB_FUNC(INDEX) \
56 case gl::VertexArray::DIRTY_BIT_ATTRIB_0 + INDEX: \
57 ANGLE_TRY(syncDirtyAttrib(contextWgpu, attribs[INDEX], \
58 bindings[attribs[INDEX].bindingIndex], INDEX)); \
59 (*attribBits)[INDEX].reset(); \
60 syncedAttributes.set(INDEX); \
61 break;
62
63 ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_ATTRIB_FUNC)
64
65 #define ANGLE_VERTEX_DIRTY_BINDING_FUNC(INDEX) \
66 case gl::VertexArray::DIRTY_BIT_BINDING_0 + INDEX: \
67 ANGLE_TRY(syncDirtyAttrib(contextWgpu, attribs[INDEX], \
68 bindings[attribs[INDEX].bindingIndex], INDEX)); \
69 (*bindingBits)[INDEX].reset(); \
70 syncedAttributes.set(INDEX); \
71 break;
72
73 ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BINDING_FUNC)
74
75 #define ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC(INDEX) \
76 case gl::VertexArray::DIRTY_BIT_BUFFER_DATA_0 + INDEX: \
77 ANGLE_TRY(syncDirtyAttrib(contextWgpu, attribs[INDEX], \
78 bindings[attribs[INDEX].bindingIndex], INDEX)); \
79 syncedAttributes.set(INDEX); \
80 break;
81
82 ANGLE_VERTEX_INDEX_CASES(ANGLE_VERTEX_DIRTY_BUFFER_DATA_FUNC)
83 default:
84 break;
85 }
86 }
87
88 for (size_t syncedAttribIndex : syncedAttributes)
89 {
90 contextWgpu->setVertexAttribute(syncedAttribIndex, mCurrentAttribs[syncedAttribIndex]);
91 contextWgpu->invalidateVertexBuffer(syncedAttribIndex);
92 }
93 return angle::Result::Continue;
94 }
95
syncClientArrays(const gl::Context * context,const gl::AttributesMask & activeAttributesMask,GLint first,GLsizei count,GLsizei instanceCount,gl::DrawElementsType drawElementsTypeOrInvalid,const void * indices,GLint baseVertex,bool primitiveRestartEnabled,const void ** adjustedIndicesPtr)96 angle::Result VertexArrayWgpu::syncClientArrays(const gl::Context *context,
97 const gl::AttributesMask &activeAttributesMask,
98 GLint first,
99 GLsizei count,
100 GLsizei instanceCount,
101 gl::DrawElementsType drawElementsTypeOrInvalid,
102 const void *indices,
103 GLint baseVertex,
104 bool primitiveRestartEnabled,
105 const void **adjustedIndicesPtr)
106 {
107 *adjustedIndicesPtr = indices;
108
109 gl::AttributesMask clientAttributesToSync = mState.getClientMemoryAttribsMask() &
110 mState.getEnabledAttributesMask() &
111 activeAttributesMask;
112 bool indexedDrawCallWithNoIndexBuffer =
113 drawElementsTypeOrInvalid != gl::DrawElementsType::InvalidEnum &&
114 !mState.getElementArrayBuffer();
115
116 if (!clientAttributesToSync.any() && !indexedDrawCallWithNoIndexBuffer)
117 {
118 return angle::Result::Continue;
119 }
120
121 ContextWgpu *contextWgpu = webgpu::GetImpl(context);
122 wgpu::Device device = webgpu::GetDevice(context);
123
124 // If any attributes need to be streamed, we need to know the index range.
125 std::optional<gl::IndexRange> indexRange;
126 if (clientAttributesToSync.any())
127 {
128 GLint startVertex = 0;
129 size_t vertexCount = 0;
130 ANGLE_TRY(GetVertexRangeInfo(context, first, count, drawElementsTypeOrInvalid, indices,
131 baseVertex, &startVertex, &vertexCount));
132 indexRange = gl::IndexRange(startVertex, startVertex + vertexCount - 1, 0);
133 }
134
135 // Pre-compute the total size of all streamed vertex and index data so a single staging buffer
136 // can be used
137 size_t stagingBufferSize = 0;
138
139 std::optional<size_t> indexDataSize;
140 if (indexedDrawCallWithNoIndexBuffer)
141 {
142 indexDataSize = gl::GetDrawElementsTypeSize(drawElementsTypeOrInvalid) * count;
143 stagingBufferSize +=
144 rx::roundUpPow2(indexDataSize.value(), webgpu::kBufferCopyToBufferAlignment);
145 }
146
147 const std::vector<gl::VertexAttribute> &attribs = mState.getVertexAttributes();
148 const std::vector<gl::VertexBinding> &bindings = mState.getVertexBindings();
149
150 if (clientAttributesToSync.any())
151 {
152 for (size_t attribIndex : clientAttributesToSync)
153 {
154 const gl::VertexAttribute &attrib = attribs[attribIndex];
155 const gl::VertexBinding &binding = bindings[attrib.bindingIndex];
156
157 size_t typeSize = gl::ComputeVertexAttributeTypeSize(attrib);
158 size_t attribSize =
159 typeSize * gl::ComputeVertexBindingElementCount(
160 binding.getDivisor(), indexRange->vertexCount(), instanceCount);
161 stagingBufferSize += rx::roundUpPow2(attribSize, webgpu::kBufferCopyToBufferAlignment);
162 }
163 }
164
165 ASSERT(stagingBufferSize > 0);
166 ASSERT(stagingBufferSize % webgpu::kBufferSizeAlignment == 0);
167 webgpu::BufferHelper stagingBuffer;
168 ANGLE_TRY(stagingBuffer.initBuffer(device, stagingBufferSize,
169 wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::MapWrite,
170 webgpu::MapAtCreation::Yes));
171
172 struct BufferCopy
173 {
174 uint64_t sourceOffset;
175 webgpu::BufferHelper *dest;
176 uint64_t size;
177 };
178 std::vector<BufferCopy> stagingUploads;
179
180 uint8_t *stagingData = stagingBuffer.getMapWritePointer(0, stagingBufferSize);
181 size_t currentStagingDataPosition = 0;
182
183 auto ensureStreamingBufferCreated = [device](webgpu::BufferHelper &buffer, size_t size,
184 wgpu::BufferUsage usage, bool *outNewBuffer) {
185 if (buffer.valid() && buffer.requestedSize() >= size)
186 {
187 ASSERT(buffer.getBuffer().GetUsage() == usage);
188 *outNewBuffer = false;
189 return angle::Result::Continue;
190 }
191
192 ANGLE_TRY(buffer.initBuffer(device, size, usage, webgpu::MapAtCreation::No));
193 *outNewBuffer = true;
194 return angle::Result::Continue;
195 };
196
197 if (indexedDrawCallWithNoIndexBuffer)
198 {
199 ASSERT(indexDataSize.has_value());
200 memcpy(stagingData + currentStagingDataPosition, indices, indexDataSize.value());
201
202 size_t copySize =
203 rx::roundUpPow2(indexDataSize.value(), webgpu::kBufferCopyToBufferAlignment);
204
205 bool newBuffer = false;
206 ANGLE_TRY(ensureStreamingBufferCreated(
207 mStreamingIndexBuffer, copySize, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
208 &newBuffer));
209 if (newBuffer)
210 {
211 contextWgpu->invalidateIndexBuffer();
212 }
213
214 stagingUploads.push_back({currentStagingDataPosition, &mStreamingIndexBuffer, copySize});
215
216 currentStagingDataPosition += copySize;
217
218 // Indices are streamed to the start of the buffer. Tell the draw call command to use 0 for
219 // firstIndex.
220 *adjustedIndicesPtr = 0;
221 }
222
223 for (size_t attribIndex : clientAttributesToSync)
224 {
225 const gl::VertexAttribute &attrib = attribs[attribIndex];
226 const gl::VertexBinding &binding = bindings[attrib.bindingIndex];
227
228 size_t streamedVertexCount = gl::ComputeVertexBindingElementCount(
229 binding.getDivisor(), indexRange->vertexCount(), instanceCount);
230
231 const size_t sourceStride = ComputeVertexAttributeStride(attrib, binding);
232 const size_t typeSize = gl::ComputeVertexAttributeTypeSize(attrib);
233
234 // Vertices do not apply the 'start' offset when the divisor is non-zero even when doing
235 // a non-instanced draw call
236 const size_t firstIndex = (binding.getDivisor() == 0) ? indexRange->start : 0;
237
238 // Attributes using client memory ignore the VERTEX_ATTRIB_BINDING state.
239 // https://www.opengl.org/registry/specs/ARB/vertex_attrib_binding.txt
240 const uint8_t *inputPointer = static_cast<const uint8_t *>(attrib.pointer);
241
242 // Pack the data when copying it, user could have supplied a very large stride that
243 // would cause the buffer to be much larger than needed.
244 if (typeSize == sourceStride)
245 {
246 // Can copy in one go, the data is packed
247 memcpy(stagingData + currentStagingDataPosition,
248 inputPointer + (sourceStride * firstIndex), streamedVertexCount * typeSize);
249 }
250 else
251 {
252 for (size_t vertexIdx = 0; vertexIdx < streamedVertexCount; vertexIdx++)
253 {
254 uint8_t *out = stagingData + currentStagingDataPosition + (typeSize * vertexIdx);
255 const uint8_t *in = inputPointer + sourceStride * (vertexIdx + firstIndex);
256 memcpy(out, in, typeSize);
257 }
258 }
259
260 size_t copySize =
261 rx::roundUpPow2(streamedVertexCount * typeSize, webgpu::kBufferCopyToBufferAlignment);
262
263 bool newBuffer = false;
264 ANGLE_TRY(ensureStreamingBufferCreated(
265 mStreamingArrayBuffers[attribIndex], copySize,
266 wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex, &newBuffer));
267 if (newBuffer)
268 {
269 contextWgpu->invalidateVertexBuffer(attribIndex);
270 }
271
272 stagingUploads.push_back(
273 {currentStagingDataPosition, &mStreamingArrayBuffers[attribIndex], copySize});
274
275 currentStagingDataPosition += copySize;
276 }
277
278 ANGLE_TRY(stagingBuffer.unmap());
279 ANGLE_TRY(contextWgpu->flush(webgpu::RenderPassClosureReason::VertexArrayStreaming));
280
281 contextWgpu->ensureCommandEncoderCreated();
282 wgpu::CommandEncoder &commandEncoder = contextWgpu->getCurrentCommandEncoder();
283
284 for (const BufferCopy © : stagingUploads)
285 {
286 commandEncoder.CopyBufferToBuffer(stagingBuffer.getBuffer(), copy.sourceOffset,
287 copy.dest->getBuffer(), 0, copy.size);
288 }
289
290 return angle::Result::Continue;
291 }
292
syncDirtyAttrib(ContextWgpu * contextWgpu,const gl::VertexAttribute & attrib,const gl::VertexBinding & binding,size_t attribIndex)293 angle::Result VertexArrayWgpu::syncDirtyAttrib(ContextWgpu *contextWgpu,
294 const gl::VertexAttribute &attrib,
295 const gl::VertexBinding &binding,
296 size_t attribIndex)
297 {
298 if (attrib.enabled)
299 {
300 SetBitField(mCurrentAttribs[attribIndex].enabled, true);
301 const webgpu::Format &webgpuFormat =
302 contextWgpu->getFormat(attrib.format->glInternalFormat);
303 SetBitField(mCurrentAttribs[attribIndex].format, webgpuFormat.getActualWgpuVertexFormat());
304 SetBitField(mCurrentAttribs[attribIndex].shaderLocation, attribIndex);
305 SetBitField(mCurrentAttribs[attribIndex].stride, binding.getStride());
306
307 gl::Buffer *bufferGl = binding.getBuffer().get();
308 if (bufferGl && bufferGl->getSize() > 0)
309 {
310 SetBitField(mCurrentAttribs[attribIndex].offset,
311 reinterpret_cast<uintptr_t>(attrib.pointer));
312 BufferWgpu *bufferWgpu = webgpu::GetImpl(bufferGl);
313 mCurrentArrayBuffers[attribIndex] = &(bufferWgpu->getBuffer());
314 }
315 else
316 {
317 SetBitField(mCurrentAttribs[attribIndex].offset, 0);
318 mCurrentArrayBuffers[attribIndex] = &mStreamingArrayBuffers[attribIndex];
319 }
320 }
321 else
322 {
323 memset(&mCurrentAttribs[attribIndex], 0, sizeof(webgpu::PackedVertexAttribute));
324 mCurrentArrayBuffers[attribIndex] = nullptr;
325 }
326
327 return angle::Result::Continue;
328 }
329
syncDirtyElementArrayBuffer(ContextWgpu * contextWgpu)330 angle::Result VertexArrayWgpu::syncDirtyElementArrayBuffer(ContextWgpu *contextWgpu)
331 {
332 gl::Buffer *bufferGl = mState.getElementArrayBuffer();
333 if (bufferGl)
334 {
335 BufferWgpu *buffer = webgpu::GetImpl(bufferGl);
336 mCurrentIndexBuffer = &buffer->getBuffer();
337 }
338 else
339 {
340 mCurrentIndexBuffer = &mStreamingIndexBuffer;
341 }
342
343 return angle::Result::Continue;
344 }
345
346 } // namespace rx
347