Skip to content

Commit 8b87a54

Browse files
committed
webgpu: fix vertex buffer info for hellopbr
1 parent cd1d3e8 commit 8b87a54

File tree

4 files changed

+155
-111
lines changed

4 files changed

+155
-111
lines changed

filament/backend/src/webgpu/WebGPUDriver.cpp

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
5858
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags) {
5959
mAdapter = mPlatform.requestAdapter(nullptr);
6060
mDevice = mPlatform.requestDevice(mAdapter);
61-
wgpu::Limits supportedLimits{};
62-
mDevice.GetLimits(&supportedLimits);
63-
mMinUniformBufferOffsetAlignment = supportedLimits.minUniformBufferOffsetAlignment;
61+
mDevice.GetLimits(&mDeviceLimits);
6462
mQueue = mDevice.GetQueue();
6563
}
6664

@@ -337,9 +335,10 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
337335
void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
338336
uint32_t height, uint64_t flags) {}
339337

340-
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
341-
uint8_t attributeCount, AttributeArray attributes) {
342-
constructHandle<WGPUVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
338+
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vertexBufferInfoHandle,
339+
uint8_t bufferCount, uint8_t attributeCount, AttributeArray attributes) {
340+
constructHandle<WGPUVertexBufferInfo>(vertexBufferInfoHandle, bufferCount, attributeCount,
341+
attributes, mDeviceLimits);
343342
}
344343

345344
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
@@ -965,15 +964,18 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
965964
mRenderPassEncoder.SetPipeline(pipeline);
966965
}
967966

968-
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
969-
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
970-
auto vbi = handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih);
971-
for (const auto& webGPUSlotBindings: vbi->getWebGPUSlotBindingInfos()) {
972-
mRenderPassEncoder.SetVertexBuffer(webGPUSlotBindings.slot,
973-
renderPrimitive->vertexBuffer->buffers[webGPUSlotBindings.sourceBuffer],
974-
webGPUSlotBindings.bufferOffset);
967+
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> renderPrimitiveHandle) {
968+
const auto renderPrimitive = handleCast<WGPURenderPrimitive>(renderPrimitiveHandle);
969+
const auto vertexBufferInfo =
970+
handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih);
971+
for (size_t slotIndex = 0; slotIndex < vertexBufferInfo->getVertexBufferLayoutCount();
972+
slotIndex++) {
973+
WGPUVertexBufferInfo::WebGPUSlotBindingInfo const& bindingInfo =
974+
vertexBufferInfo->getWebGPUSlotBindingInfos()[slotIndex];
975+
mRenderPassEncoder.SetVertexBuffer(slotIndex,
976+
renderPrimitive->vertexBuffer->buffers[bindingInfo.sourceBufferIndex],
977+
bindingInfo.bufferOffset);
975978
}
976-
977979
mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->getBuffer(),
978980
renderPrimitive->indexBuffer->indexFormat);
979981
}
@@ -1026,9 +1028,10 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> dsh,
10261028
auto buffer = handleCast<WGPUBufferObject>(boh);
10271029
if (!bindGroup->getIsLocked()) {
10281030
// TODO making assumptions that size and offset mean the same thing here.
1029-
FILAMENT_CHECK_PRECONDITION(offset % mMinUniformBufferOffsetAlignment == 0)
1030-
<< "Binding offset must be multiple of " << mMinUniformBufferOffsetAlignment
1031-
<< "But requested offset is " << offset;
1031+
FILAMENT_CHECK_PRECONDITION(offset % mDeviceLimits.minUniformBufferOffsetAlignment == 0)
1032+
<< "Binding offset must be multiple of "
1033+
<< mDeviceLimits.minUniformBufferOffsetAlignment << "But requested offset is "
1034+
<< offset;
10321035
wgpu::BindGroupEntry entry{ .binding = static_cast<uint32_t>(binding * 2),
10331036
.buffer = buffer->getBuffer(),
10341037
.offset = offset,

filament/backend/src/webgpu/WebGPUDriver.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class WebGPUDriver final : public DriverBase {
6464
WebGPUPlatform& mPlatform;
6565
wgpu::Adapter mAdapter = nullptr;
6666
wgpu::Device mDevice = nullptr;
67-
uint32_t mMinUniformBufferOffsetAlignment;
67+
wgpu::Limits mDeviceLimits = {};
6868
wgpu::Queue mQueue = nullptr;
6969
void* mNativeWindow = nullptr;
7070
WebGPUSwapChain* mSwapChain = nullptr;

filament/backend/src/webgpu/WebGPUHandles.cpp

Lines changed: 111 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -197,89 +197,127 @@ void WGPUBufferBase::updateGPUBuffer(BufferDescriptor& bufferDescriptor, uint32_
197197
}
198198
}
199199

200-
static constexpr uint32_t DUMMY_WEBGPU_SLOT = 0;
201-
202200
WGPUVertexBufferInfo::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

filament/backend/src/webgpu/WebGPUHandles.h

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,44 +45,47 @@ class WGPUProgram final : public HwProgram {
4545

4646

4747
// WGPUVertexBufferInfo maps Filament vertex attributes to WebGPU buffer binding model.
48-
class WGPUVertexBufferInfo : public HwVertexBufferInfo {
48+
class WGPUVertexBufferInfo final : public HwVertexBufferInfo {
4949
public:
5050
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
51-
AttributeArray const& attributes);
51+
AttributeArray const& attributes, wgpu::Limits const& deviceLimits);
5252

53-
inline wgpu::VertexBufferLayout const* getVertexBufferLayouts() const {
53+
[[nodiscard]] inline wgpu::VertexBufferLayout const* getVertexBufferLayouts() const {
5454
return mVertexBufferLayouts.data();
5555
}
56-
inline uint32_t getVertexBufferLayoutCount() const {
57-
return static_cast<uint32_t>(mVertexBufferLayouts.size());
58-
}
5956

60-
inline wgpu::VertexAttribute const* getVertexAttributes(uint32_t i) const {
61-
return mVertexAttributes[i].data();
62-
}
63-
inline uint32_t getVertexAttributeCount(uint32_t i) const {
64-
return static_cast<uint32_t>(mVertexAttributes[i].size());
57+
[[nodiscard]] inline size_t getVertexBufferLayoutCount() const {
58+
return mVertexBufferLayouts.size();
6559
}
6660

67-
struct WebGPUSlotBindingInfo {
68-
uint8_t sourceBuffer;
69-
uint32_t slot;
70-
uint64_t bufferOffset;
71-
uint32_t stride;
61+
struct WebGPUSlotBindingInfo final {
62+
uint8_t sourceBufferIndex = 0; // limited by filament::backend::Attribute::buffer
63+
uint32_t bufferOffset = 0; // limited by filament::backend::Attribute::offset
64+
uint8_t stride = 0; // limited by filament::backend::Attribute::stride
7265
};
73-
inline const std::vector<WebGPUSlotBindingInfo>& getWebGPUSlotBindingInfos() const {
66+
67+
[[nodiscard]] inline std::vector<WebGPUSlotBindingInfo> const&
68+
getWebGPUSlotBindingInfos() const {
7469
return mWebGPUSlotBindingInfos;
7570
}
7671

7772
private:
7873
// This stores the final wgpu::VertexBufferLayout objects, one per WebGPU slot.
74+
// (this is a vector and not an array due to size limitations of the handle in the handle
75+
// allocator)
7976
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayouts;
8077

81-
// This stores all wgpu::VertexAttribute structs, indexed by their original
82-
// Filament attributeIndex.
83-
std::vector<std::vector<wgpu::VertexAttribute>> mVertexAttributes;
78+
// This stores all the vertex attributes across all the vertex buffer layouts, where
79+
// the attributes are contiguous for each buffer layout.
80+
// Each buffer layout references the first in a contiguous group of attributes in this array,
81+
// as well as the number of such attributes in this vector.
82+
// (this is a vector and not an array due to size limitations of the handle in the handle
83+
// allocator)
84+
std::vector<wgpu::VertexAttribute> mVertexAttributes;
8485

8586
// Stores information for the driver to perform setVertexBuffer calls
87+
// (this is a vector and not an array due to size limitations of the handle in the handle
88+
// allocator)
8689
std::vector<WebGPUSlotBindingInfo> mWebGPUSlotBindingInfos;
8790
};
8891

0 commit comments

Comments
 (0)