@@ -197,89 +197,127 @@ void WGPUBufferBase::updateGPUBuffer(BufferDescriptor& bufferDescriptor, uint32_
197197 }
198198}
199199
200- static constexpr uint32_t DUMMY_WEBGPU_SLOT = 0 ;
201-
202200WGPUVertexBufferInfo::WGPUVertexBufferInfo (uint8_t bufferCount, uint8_t attributeCount,
203- AttributeArray const & attributes)
204- : HwVertexBufferInfo(bufferCount, attributeCount),
205- // TODO: max limits may not be supported by webgpu driver. This should be addressed in the
206- // hardening part.
207- mVertexBufferLayouts (MAX_VERTEX_BUFFER_COUNT),
208- mVertexAttributes(MAX_VERTEX_BUFFER_COUNT) {
209-
210- // It starts from 1 because slot 0 is now reserved for the dummy.
211- uint32_t currentWebGPUSlotIndex = 1 ;
212- // A reasonable dummy stride (e.g., for vec4)
213- const uint32_t DUMMY_STRIDE = 16 ;
214-
215- // Initialize the layout for the dummy slot (slot 0)
216- mVertexBufferLayouts [DUMMY_WEBGPU_SLOT].arrayStride = DUMMY_STRIDE;
217- mVertexBufferLayouts [DUMMY_WEBGPU_SLOT].stepMode = wgpu::VertexStepMode::Vertex;
218- mVertexBufferLayouts [DUMMY_WEBGPU_SLOT].attributeCount = 0 ;
219-
220- mWebGPUSlotBindingInfos .push_back ({
221- .sourceBuffer = 0 ,
222- .slot = DUMMY_WEBGPU_SLOT,
223- .bufferOffset = 0 ,
224- .stride = DUMMY_STRIDE,
225- });
226-
201+ AttributeArray const & attributes, wgpu::Limits const & deviceLimits)
202+ : HwVertexBufferInfo(bufferCount, attributeCount) {
203+ FILAMENT_CHECK_PRECONDITION (attributeCount <= MAX_VERTEX_ATTRIBUTE_COUNT &&
204+ attributeCount <= deviceLimits.maxVertexAttributes )
205+ << " The number of vertex attributes requested (" << attributeCount
206+ << " ) exceeds Filament's MAX_VERTEX_ATTRIBUTE_COUNT limit ("
207+ << MAX_VERTEX_ATTRIBUTE_COUNT << " ) and/or the device's limit ("
208+ << deviceLimits.maxVertexAttributes << " )" ;
209+ mVertexAttributes .reserve (attributeCount);
210+ if (attributeCount == 0 ) {
211+ mVertexBufferLayouts .resize (0 );
212+ mWebGPUSlotBindingInfos .resize (0 );
213+ return ; // should not be possible, but being defensive. nothing to do otherwise
214+ }
215+ // populate mWebGPUSlotBindingInfos and attribute info (slot + attribute) by going through each
216+ // attribute...
217+ constexpr uint32_t IMPOSSIBLE_SLOT_INDEX = MAX_VERTEX_BUFFER_COUNT;
218+ struct AttributeInfo final {
219+ uint8_t slot = IMPOSSIBLE_SLOT_INDEX;
220+ wgpu::VertexAttribute attribute = {};
221+ };
222+ std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT> attributeInfos{};
223+ uint8_t currentWebGPUSlotIndex = 0 ;
224+ uint8_t currentAttributeIndex = 0 ;
225+ mVertexBufferLayouts .reserve (MAX_VERTEX_BUFFER_COUNT);
226+ mWebGPUSlotBindingInfos .reserve (mVertexBufferLayouts .capacity ());
227227 for (uint32_t attributeIndex = 0 ; attributeIndex < attributes.size (); ++attributeIndex) {
228228 const auto & attribute = attributes[attributeIndex];
229-
230- bool const isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
231- bool const isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
232- wgpu::VertexFormat vertexFormat = getVertexFormat (attribute.type , isNormalized, isInteger);
233-
234229 if (attribute.buffer == Attribute::BUFFER_UNUSED) {
235- // Use some dummy format
236- vertexFormat = isInteger ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Unorm8x4;
237- mVertexAttributes [DUMMY_WEBGPU_SLOT].push_back ({
238- .format = vertexFormat,
239- .offset = 0 ,
240- .shaderLocation = attributeIndex,
241- });
242- mVertexBufferLayouts [DUMMY_WEBGPU_SLOT].attributeCount ++;
230+ // We ignore "unused" attributes, ones not associated with a buffer. If a shader
231+ // references such an attribute we have a bug one way or another. Either the API/CPU
232+ // will produce an error (best case scenario), where an attribute is referenced in the
233+ // shader but not provided by the backend API (CPU-side), or the shader would be getting
234+ // junk/undefined data from the GPU, since we do not have a valid buffer of data to
235+ // provide to the shader/GPU.
243236 continue ;
244237 }
245-
246- auto it = std::find_if (mWebGPUSlotBindingInfos .begin (), mWebGPUSlotBindingInfos .end (),
247- [&](const auto & info) {
248- return info.sourceBuffer == attribute.buffer && info.stride == attribute.stride ;
249- });
250-
251- uint32_t assignedSlot;
252- if (it != mWebGPUSlotBindingInfos .end ()) {
253- assignedSlot = it->slot ;
254- } else {
255- // New combination, allocate a new WebGPU slot
256- assert_invariant (currentWebGPUSlotIndex < MAX_VERTEX_BUFFER_COUNT);
257- assignedSlot = currentWebGPUSlotIndex++;
258-
238+ bool const isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
239+ bool const isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
240+ wgpu::VertexFormat const vertexFormat =
241+ getVertexFormat (attribute.type , isNormalized, isInteger);
242+ uint8_t existingSlot = IMPOSSIBLE_SLOT_INDEX;
243+ for (uint32_t slot = 0 ; slot < currentWebGPUSlotIndex; slot++) {
244+ WebGPUSlotBindingInfo const & info = mWebGPUSlotBindingInfos [slot];
245+ // We consider attributes to be in the same slot/layout only if they belong to the
246+ // same buffer and are interleaved; they cannot belong to separate partitions in the
247+ // same buffer, for example.
248+ // For the attributes to be interleaved, the stride must match (among other things).
249+ // The attribute offset being less than the slot's/layout's buffer offset indicates
250+ // that it is in a separate partition before this slot/layout, thus not part of it.
251+ // The difference from the attribute's offset and this slot's/layout's buffer offset
252+ // being greater than the stride indicates it is in a separate partition after this
253+ // slot/layout, thus not part of it.
254+ if (info.sourceBufferIndex == attribute.buffer && info.stride == attribute.stride &&
255+ attribute.offset >= info.bufferOffset &&
256+ ((attribute.offset - info.bufferOffset ) < attribute.stride )) {
257+ existingSlot = slot;
258+ break ;
259+ }
260+ }
261+ if (existingSlot == IMPOSSIBLE_SLOT_INDEX) {
262+ // New combination, use a new WebGPU slot
263+ FILAMENT_CHECK_PRECONDITION (currentWebGPUSlotIndex < MAX_VERTEX_BUFFER_COUNT &&
264+ currentWebGPUSlotIndex < deviceLimits.maxVertexBuffers )
265+ << " Number of vertex buffer layouts must not exceed MAX_VERTEX_BUFFER_COUNT ("
266+ << MAX_VERTEX_BUFFER_COUNT << " ) or the device limit ("
267+ << deviceLimits.maxVertexBuffers << " )" ;
268+ existingSlot = currentWebGPUSlotIndex++;
259269 mWebGPUSlotBindingInfos .push_back ({
260- .sourceBuffer = attribute.buffer ,
261- .slot = assignedSlot,
262- .bufferOffset = attribute.offset - (attribute.offset % attribute.stride ),
263- .stride = attribute.stride ,
270+ .sourceBufferIndex = attribute.buffer ,
271+ .bufferOffset = attribute.offset ,
272+ .stride = attribute.stride
273+ });
274+ // we need to have a vertex buffer layout for each slot
275+ mVertexBufferLayouts .push_back ({
276+ .stepMode = wgpu::VertexStepMode::Vertex,
277+ .arrayStride = attribute.stride
278+ // we do not know attributeCount or attributes at this time. We get those
279+ // in a subsequent pass over the attributeInfos we collect in this loop.
264280 });
265-
266- mVertexBufferLayouts [assignedSlot].arrayStride = attribute.stride ;
267- mVertexBufferLayouts [assignedSlot].stepMode = wgpu::VertexStepMode::Vertex;
268- mVertexBufferLayouts [assignedSlot].attributeCount = 0 ;
269281 }
270-
271- mVertexAttributes [assignedSlot].push_back ({
272- .format = vertexFormat,
273- .offset = attribute.offset % attribute.stride ,
274- .shaderLocation = attributeIndex,
275- });
276- mVertexBufferLayouts [assignedSlot].attributeCount ++;
282+ attributeInfos[currentAttributeIndex++] = {
283+ .slot = existingSlot,
284+ .attribute = {
285+ .format = vertexFormat,
286+ .offset = attribute.offset - mWebGPUSlotBindingInfos [existingSlot].bufferOffset ,
287+ .shaderLocation = attributeIndex
288+ }
289+ };
277290 }
278-
279- mVertexBufferLayouts .resize (currentWebGPUSlotIndex);
280-
281- for (const auto & info: mWebGPUSlotBindingInfos ) {
282- mVertexBufferLayouts [info.slot ].attributes = mVertexAttributes [info.slot ].data ();
291+ FILAMENT_CHECK_POSTCONDITION (currentAttributeIndex == attributeCount)
292+ << " Using " << currentAttributeIndex << " attributes, but " << attributeCount
293+ << " where indicated." ;
294+ mVertexBufferLayouts .shrink_to_fit ();
295+ mWebGPUSlotBindingInfos .shrink_to_fit ();
296+ // sort attribute infos by increasing slot (by increasing offset within each slot).
297+ // We do this to ensure that attributes for the same slot/layout are contiguous in
298+ // the vector, so the vertex buffer layout associated with these contiguous attributes
299+ // can directly reference them in the mVertexAttributes vector below.
300+ std::sort (attributeInfos.data (), attributeInfos.data () + attributeCount,
301+ [](AttributeInfo const & first, AttributeInfo const & second) {
302+ if (first.slot < second.slot ) {
303+ return true ;
304+ }
305+ if (first.slot == second.slot ) {
306+ if (first.attribute .offset < second.attribute .offset ) {
307+ return true ;
308+ }
309+ }
310+ return false ;
311+ });
312+ // populate mVertexAttributes and update mVertexBufferLayouts to reference the correct
313+ // attributes in it (which will be contiguous in memory as ensured by the sorting above)...
314+ for (uint32_t attributeIndex = 0 ; attributeIndex < attributeCount; ++attributeIndex) {
315+ AttributeInfo const & info = attributeInfos[attributeIndex];
316+ mVertexAttributes .push_back (info.attribute );
317+ if (mVertexBufferLayouts [info.slot ].attributeCount == 0 ) {
318+ mVertexBufferLayouts [info.slot ].attributes = &mVertexAttributes [attributeIndex];
319+ }
320+ mVertexBufferLayouts [info.slot ].attributeCount ++;
283321 }
284322}
285323
0 commit comments