webgpu: Support skinning on WebGPU (#9137)

BUGS = [436886048]
This commit is contained in:
Juan Caldas
2025-08-28 20:24:14 -04:00
committed by GitHub
parent 088900e6e0
commit 74ba3eb00c
7 changed files with 53 additions and 20 deletions

View File

@@ -633,7 +633,7 @@ endif()
# With WebGPU, Tint does not support ClipDistance which is used in Stereo. Mentioned in comment
# https://github.com/google/dawn/blob/855d17b08abdf02f9142bf5a8f14d0ea088810a4/src/tint/lang/spirv/reader/ast_parser/function.cc#L4434
if (FILAMENT_SUPPORTS_WEBGPU)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=skinning,stereo)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=stereo)
endif()
# Disable ESSL 1.0 code generation.

View File

@@ -146,7 +146,7 @@ abstract class MaterialCompiler extends TaskWithBinary {
.gradleProperty("com.google.android.filament.include-webgpu")
.forUseAtConfigurationTime().present
if (include_webgpu) {
matcArgs += ['-a', 'webgpu', '--variant-filter=skinning,stereo']
matcArgs += ['-a', 'webgpu', '--variant-filter=stereo']
}
def mat_no_opt = providers

View File

@@ -629,6 +629,18 @@ enum class PrimitiveType : uint8_t {
TRIANGLE_STRIP = 5 //!< triangle strip
};
[[nodiscard]] constexpr bool isStripPrimitiveType(const PrimitiveType type) {
switch (type) {
case PrimitiveType::POINTS:
case PrimitiveType::LINES:
case PrimitiveType::TRIANGLES:
return false;
case PrimitiveType::LINE_STRIP:
case PrimitiveType::TRIANGLE_STRIP:
return true;
}
}
/**
* Supported uniform types
*/

View File

@@ -293,10 +293,11 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
},
.primitive = {
.topology = toWebGPU(request.primitiveType),
// TODO should we assume some constant format here or is there a way to get
// this from PipelineState somehow or elsewhere?
// Perhaps, cache/assert format from index buffers as they are requested?
.stripIndexFormat = wgpu::IndexFormat::Undefined,
// TODO This won't always be the case for the primitives bound. But this particular field
// in the pipeline struct is only meant for restarting the strip, which we don't need so far.
.stripIndexFormat =
isStripPrimitiveType(request.primitiveType)? wgpu::IndexFormat::Uint16 :
wgpu::IndexFormat::Undefined,
.frontFace = request.rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
.cullMode = toWebGPU(request.rasterState.culling),
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
@@ -312,12 +313,10 @@ wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
},
.fragment = nullptr // will add below if fragment module is included
};
// TODO:
if (pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::LineStrip ||
pipelineDescriptor.primitive.topology == wgpu::PrimitiveTopology::TriangleStrip) {
PANIC_POSTCONDITION("stripIndexFormat must be set for strip topologies. "
"This needs to be plumbed through from the RenderPrimitive.");
}
FILAMENT_CHECK_POSTCONDITION(!isStripPrimitiveType(request.primitiveType) ||
pipelineDescriptor.primitive.stripIndexFormat != wgpu::IndexFormat::Undefined)
<< "If the topology is a strip format, e.g. TriangleStrip, the stripIndexFormat cannot "
"be Undefined.";
wgpu::FragmentState fragmentState = {};
const wgpu::BlendState blendState {

View File

@@ -161,7 +161,7 @@ private:
// render targets... //
uint8_t multisampleCount{ 0 }; // 1 : 313
uint8_t colorFormatCount{ 0 }; // 1 : 314
uint8_t padding[5]{ 0 }; // 5 : 319
uint8_t padding[5]{ 0 }; // 5 : 315
TargetBufferFlags targetRenderFlags{ TargetBufferFlags::NONE }; // 4 : 320
wgpu::TextureFormat depthStencilFormat { wgpu::TextureFormat::Undefined }; // 4 : 324
wgpu::TextureFormat colorFormats[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]{ //

View File

@@ -173,9 +173,7 @@ utils::io::sstream& CodeGenerator::generateCommonProlog(utils::io::sstream& out,
case TargetApi::METAL:
out << "#define TARGET_METAL_ENVIRONMENT\n";
break;
// TODO: Handle webgpu here
case TargetApi::WEBGPU:
//For now, no differences so inherit the same changes.
out << "#define TARGET_WEBGPU_ENVIRONMENT\n";
break;
case TargetApi::ALL:
@@ -732,7 +730,6 @@ io::sstream& CodeGenerator::generateBufferInterfaceBlock(io::sstream& out, Shade
// in the GLSL 4.5 / ESSL 3.1 case, the set is not used and binding is unique
out << "binding = " << +binding << ", ";
break;
// TODO: Handle webgpu here
case TargetApi::WEBGPU:
out << "set = " << +set << ", binding = " << +binding << ", ";
break;
@@ -822,7 +819,6 @@ io::sstream& CodeGenerator::generateCommonSamplers(utils::io::sstream& out,
// GLSL 4.5 / ESSL 3.1 require the 'binding' layout qualifier
out << "layout(binding = " << getUniqueSamplerBindingPoint() << ") ";
break;
// TODO: Handle webgpu here
case TargetApi::WEBGPU:
out << "layout(binding = " << +info.binding << ", set = " << +set << ") ";
break;
@@ -942,7 +938,7 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
static const char* types[] = { "int", "float", "bool" };
// Spec constants aren't fully supported in Tint,
// workaround until https://issues.chromium.org/issues/42250586 is resolved
// workaround until https://issues.chromium.org/issues/42250586 is resolved
if (mTargetApi == TargetApi::WEBGPU) {
std::string const variableName = "FILAMENT_SPEC_CONST_" + std::to_string(id) + "_" + name;
out << " const " << types[value.index()] << " " << variableName << " = " << constantString << ";\n";
@@ -964,10 +960,11 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
utils::io::sstream& CodeGenerator::generatePushConstants(utils::io::sstream& out,
MaterialBuilder::PushConstantList const& pushConstants, size_t const layoutLocation) const {
if (UTILS_UNLIKELY(pushConstants.empty())) {
return out;
}
static constexpr char const* STRUCT_NAME = "Constants";
bool const outputSpirv =
mTargetLanguage == TargetLanguage::SPIRV && mTargetApi != TargetApi::OPENGL;
auto const getType = [](ConstantType const& type) {
switch (type) {
case ConstantType::BOOL:
@@ -978,6 +975,30 @@ utils::io::sstream& CodeGenerator::generatePushConstants(utils::io::sstream& out
return "float";
}
};
// This is a workaround for WebGPU not supporting push constants for skinning.
// We replace the push constant with a regular constant struct initialized to 0.
if (mTargetApi == TargetApi::WEBGPU) {
assert_invariant(
pushConstants.size() == 1 &&
"The current workaround for WebGPU push constants assumes for now that only 1");
assert_invariant(pushConstants[0].name == CString("morphingBufferOffset") &&
"The current workaround for WebGPU push constants assumes only the "
"morphingBufferOffset constant is present.");
assert_invariant(pushConstants[0].type == ConstantType::INT &&
"The current workaround for WebGPU push constants assumes "
"morphingBufferOffset is an integer type.");
out << "struct " << STRUCT_NAME << " {\n";
for (auto const& constant: pushConstants) {
out << " " << getType(constant.type) << " " << constant.name.c_str() << ";\n";
}
out << "};\n";
out << "const " << STRUCT_NAME << " " << PUSH_CONSTANT_STRUCT_VAR_NAME << " = "
<< STRUCT_NAME << "(0);\n";
return out;
}
bool const outputSpirv =
mTargetLanguage == TargetLanguage::SPIRV && mTargetApi != TargetApi::OPENGL;
if (outputSpirv) {
out << "layout(push_constant) uniform " << STRUCT_NAME << " {\n ";
} else {

View File

@@ -174,6 +174,7 @@ int main(int argc, char** argv) {
.attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12)
.attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12)
.normalized(VertexAttribute::COLOR)
.advancedSkinning(true)
.build(*engine);
app.vb2->setBufferAt(*engine, 0,
VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));