Compare commits
5 Commits
exv/mi-sc
...
ma/entitym
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
547f1f7e32 | ||
|
|
fd0a0c4781 | ||
|
|
8812a35cbd | ||
|
|
35d7b819e1 | ||
|
|
9b2959d3a4 |
@@ -691,11 +691,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a webgpu --variant-filter=stereo)
|
||||
endif()
|
||||
|
||||
# Disable ESSL 1.0 code generation.
|
||||
if (NOT FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
set(MATC_API_FLAGS ${MATC_API_FLAGS} -1)
|
||||
endif()
|
||||
|
||||
# Enable debug info (preserves names in SPIR-V)
|
||||
if (FILAMENT_ENABLE_MATDBG)
|
||||
set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d)
|
||||
|
||||
59
build/common/upload-release-assets/package-lock.json
generated
59
build/common/upload-release-assets/package-lock.json
generated
@@ -13,12 +13,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/http-client": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/glob": {
|
||||
@@ -38,6 +46,11 @@
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
|
||||
@@ -257,24 +270,24 @@
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/http-client": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"@actions/exec": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"requires": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@actions/glob": {
|
||||
@@ -294,6 +307,11 @@
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
|
||||
@@ -444,11 +462,6 @@
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -43,101 +43,6 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Given the source code of a [WGSL] WebGPU shader in text (string view to it) and the constants
|
||||
* to be overwritten now (at runtime), returns updated [WGSL] shader code text, where the constants
|
||||
* have been replaced.
|
||||
* @param shaderLabel Something to call this shader for troubleshooting, error messaging, etc.
|
||||
* @param shaderSource The original WGSL WebGPU shader code as text to be processed. This is a
|
||||
* view to that text and this function does not change it; it is immutable.
|
||||
* Instead, the processed version of this source is returned by the function.
|
||||
* @param specConstants The constants to replace in the shader code, indexed by constant id.
|
||||
* @return Processed version of the WGSL WebGPU shader code provided, where the constants have
|
||||
* been replaced with the values provided by the specConstants parameter.
|
||||
*/
|
||||
[[nodiscard]] std::string replaceSpecConstants(std::string_view shaderLabel,
|
||||
std::string_view shaderSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants) {
|
||||
// this function is not expected to be called at all if no spec constants are to be replaced
|
||||
assert_invariant(!specConstants.empty());
|
||||
static constexpr std::string_view specConstantPrefix = "FILAMENT_SPEC_CONST_";
|
||||
static constexpr size_t specConstantPrefixSize = specConstantPrefix.size();
|
||||
const char* const sourceData = shaderSource.data();
|
||||
std::stringstream processedShaderSource{};
|
||||
size_t pos = 0;
|
||||
while (pos < shaderSource.size()) {
|
||||
const size_t posOfNextSpecConstant = shaderSource.find(specConstantPrefix, pos);
|
||||
if (posOfNextSpecConstant == std::string::npos) {
|
||||
// no more spec constants, so just stream the rest of the source code string
|
||||
processedShaderSource << std::string_view(sourceData + pos, shaderSource.size() - pos);
|
||||
break;
|
||||
}
|
||||
const size_t posOfId = posOfNextSpecConstant + specConstantPrefixSize;
|
||||
const size_t posAfterId = shaderSource.find('_', posOfId);
|
||||
FILAMENT_CHECK_POSTCONDITION(posAfterId != std::string::npos)
|
||||
<< "malformed " << shaderLabel << ". Found spec constant prefix '"
|
||||
<< specConstantPrefix << "' without an id or '_' after it.";
|
||||
const std::string_view idStr =
|
||||
std::string_view(sourceData + posOfId, posAfterId - posOfId);
|
||||
const size_t posEndOfStatement = shaderSource.find(';', posAfterId);
|
||||
FILAMENT_CHECK_POSTCONDITION(posEndOfStatement != std::string::npos)
|
||||
<< "malformed " << shaderLabel << ". Found spec constant assignment with id "
|
||||
<< idStr << " without a terminating ';' character?";
|
||||
// this is a view into part of the statement, from after the id to the ';'
|
||||
const std::string_view statementSegment =
|
||||
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
|
||||
size_t posOfEqual = statementSegment.find('=');
|
||||
if (posOfEqual == std::string::npos) {
|
||||
// Not an assignment, so we don't need to replace it.
|
||||
processedShaderSource << std::string_view(sourceData + pos,
|
||||
posEndOfStatement + 1 - pos);
|
||||
pos = posEndOfStatement + 1;
|
||||
continue;
|
||||
}
|
||||
posOfEqual += posAfterId; // position in original source overall, not just the segment
|
||||
int constantId = 0;
|
||||
char* endPtr;
|
||||
errno = 0; // Clear errno before the call
|
||||
|
||||
long tempConstantId = std::strtol(idStr.data(), &endPtr, 10);
|
||||
|
||||
// Check for conversion errors
|
||||
if (endPtr == idStr.data() || *endPtr != '\0' || errno == ERANGE) {
|
||||
// Parsing failed, or conversion was out of range for 'long'
|
||||
// or the string contained non-numeric characters after the number.
|
||||
PANIC_POSTCONDITION("Invalid spec constant id '%s' in %s (not a valid integer or out "
|
||||
"of range for 'long'?)",
|
||||
idStr.data(), shaderLabel.data());
|
||||
} else {
|
||||
// Check if the parsed long value fits into an int
|
||||
if (tempConstantId > std::numeric_limits<int>::max() ||
|
||||
tempConstantId < std::numeric_limits<int>::min()) {
|
||||
PANIC_POSTCONDITION(
|
||||
"Invalid spec constant id '%s' in %s (value out of range for 'int')",
|
||||
idStr.data(), shaderLabel.data());
|
||||
} else {
|
||||
constantId = static_cast<int>(tempConstantId);
|
||||
}
|
||||
}
|
||||
const std::variant<int32_t, float, bool> newValue = specConstants[constantId];
|
||||
// stream up to the equal sign...
|
||||
processedShaderSource << std::string_view(sourceData + pos, posOfEqual + 1 - pos);
|
||||
// stream the new value...
|
||||
if (auto* v = std::get_if<int32_t>(&newValue)) {
|
||||
processedShaderSource << " " << *v << "i";
|
||||
} else if (auto* f = std::get_if<float>(&newValue)) {
|
||||
processedShaderSource << " " << *f << "f";
|
||||
} else if (auto* b = std::get_if<bool>(&newValue)) {
|
||||
processedShaderSource << " " << ((*b) ? "true" : "false");
|
||||
}
|
||||
// end the statement...
|
||||
processedShaderSource << ";";
|
||||
// and skip to after the end of the statement in the original source and continue...
|
||||
pos = posEndOfStatement + 1;
|
||||
}
|
||||
return processedShaderSource.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WebGPU shader module for a given "program" "stage", accounting for override constants.
|
||||
* Effectively, this function is responsible for preprocessing the shader source and compiling it.
|
||||
@@ -145,14 +50,12 @@ namespace {
|
||||
* a shader module
|
||||
* @param program The "program" to compile/create the shader, which includes the shader source
|
||||
* @param stage The stage (e.g. vertex, fragment, etc.) to create the shader module
|
||||
* @param specConstants Override constants to apply when creating/compiling the shader module.
|
||||
* @return the proper WebGPU shader module compiled/created from the input parameters. This might
|
||||
* wrap a null handle if the shader is not present (if the shader source is empty), such as
|
||||
* a missing fragment or compute shader.
|
||||
*/
|
||||
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device const& device,
|
||||
Program const& program, const ShaderStage stage,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants) {
|
||||
Program const& program, const ShaderStage stage) {
|
||||
const char* const programName = program.getName().c_str_safe();
|
||||
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
|
||||
shaderSource = program.getShadersSource();
|
||||
@@ -164,13 +67,9 @@ namespace {
|
||||
std::stringstream labelStream;
|
||||
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
|
||||
const auto label = labelStream.str();
|
||||
const std::string processedShaderSource =
|
||||
specConstants.empty()
|
||||
? reinterpret_cast<const char*>(sourceBytes.data())
|
||||
: replaceSpecConstants(label, reinterpret_cast<const char*>(sourceBytes.data()),
|
||||
specConstants);
|
||||
|
||||
wgpu::ShaderSourceWGSL wgslDescriptor{};
|
||||
wgslDescriptor.code = wgpu::StringView(processedShaderSource);
|
||||
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
|
||||
const wgpu::ShaderModuleDescriptor descriptor{
|
||||
.nextInChain = &wgslDescriptor,
|
||||
.label = label.data()
|
||||
@@ -251,14 +150,11 @@ namespace {
|
||||
}// namespace
|
||||
|
||||
WebGPUProgram::WebGPUProgram(wgpu::Device const& device, Program const& program)
|
||||
: HwProgram{ program.getName() } {
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants =
|
||||
program.getSpecializationConstants();
|
||||
: HwProgram{ program.getName() } {
|
||||
// TODO: Consider creating/compiling these shaders in parallel.
|
||||
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
|
||||
fragmentShaderModule =
|
||||
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
|
||||
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE, specConstants);
|
||||
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX);
|
||||
fragmentShaderModule = createShaderModule(device, program, ShaderStage::FRAGMENT);
|
||||
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE);
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -225,42 +225,73 @@ struct AttributeInfo final {
|
||||
* associated with a vertex buffer, and the slot each one belongs
|
||||
*/
|
||||
void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attributes,
|
||||
const uint8_t attributeCount, const uint32_t deviceMaxVertexBuffers,
|
||||
const uint32_t deviceMaxVertexBuffers,
|
||||
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo>& outWebGPUSlotBindingInfos,
|
||||
std::vector<wgpu::VertexBufferLayout>& outVertexBufferLayouts,
|
||||
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos) {
|
||||
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT>& outAttributeInfos,
|
||||
uint8_t& outActualAttributeCount) {
|
||||
uint8_t currentWebGPUSlotIndex = 0;
|
||||
uint8_t currentAttributeIndex = 0;
|
||||
outVertexBufferLayouts.reserve(MAX_VERTEX_BUFFER_COUNT);
|
||||
outWebGPUSlotBindingInfos.reserve(outVertexBufferLayouts.capacity());
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < attributes.size(); ++attributeIndex) {
|
||||
const auto& attribute = attributes[attributeIndex];
|
||||
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
|
||||
// We ignore "unused" attributes, ones not associated with a buffer. If a shader
|
||||
// references such an attribute we have a bug one way or another. Either the API/CPU
|
||||
// will produce an error (best case scenario), where an attribute is referenced in the
|
||||
// shader but not provided by the backend API (CPU-side), or the shader would be getting
|
||||
// junk/undefined data from the GPU, since we do not have a valid buffer of data to
|
||||
// provide to the shader/GPU.
|
||||
continue;
|
||||
|
||||
// Find a valid attribute to use as a dummy for unused slots (typically POSITION at index 0)
|
||||
Attribute dummyAttribute{};
|
||||
bool hasDummy = false;
|
||||
for (uint32_t i = 0; i < attributes.size(); ++i) {
|
||||
if (attributes[i].buffer != Attribute::BUFFER_UNUSED) {
|
||||
dummyAttribute = attributes[i];
|
||||
hasDummy = true;
|
||||
break;
|
||||
}
|
||||
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
const bool isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
|
||||
const wgpu::VertexFormat vertexFormat =
|
||||
getVertexFormat(attribute.type, isNormalized, isInteger);
|
||||
}
|
||||
|
||||
/*
|
||||
* WebGPU Strict Validation Workaround:
|
||||
*
|
||||
* 1. Completeness: WebGPU requires ALL shader-declared attributes to exist in the Pipeline's
|
||||
* VertexState, even if unused by a specific mesh variant (e.g., BONE_WEIGHTS).
|
||||
* 2. Stride Boundary: Offset + Format_Size <= arrayStride.
|
||||
*
|
||||
* If we omit an unused attribute, WebGPU crashes (violates 1).
|
||||
* If we pad it as Float32x4 (16 bytes) into Slot 0 (e.g., Position, stride 12), WebGPU crashes
|
||||
* (violates 2):
|
||||
*
|
||||
* [ Slot 0 Stride: 12 bytes ]
|
||||
* |-------POSITION--------|
|
||||
* |------ BONE_WEIGHTS (Float32x4 = 16b) -----| <-- OVERFLOWS STRIDE!
|
||||
*
|
||||
* FIX: We alias unused attributes into Slot 0 as a 4-byte format (Float32 or Uint32).
|
||||
*
|
||||
* [ Slot 0 Stride: 12 bytes ]
|
||||
* |-------POSITION--------|
|
||||
* |-BONE-| <-- (Float32 = 4b) Safely fits inside the 12-byte stride!
|
||||
*
|
||||
* WebGPU safely promotes the 4-byte Float32 into a WGSL vec4<f32> as (x, 0.0, 0.0, 1.0).
|
||||
*/
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < attributes.size(); ++attributeIndex) {
|
||||
auto attribute = attributes[attributeIndex];
|
||||
wgpu::VertexFormat vertexFormat;
|
||||
|
||||
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
|
||||
if (!hasDummy) {
|
||||
continue;
|
||||
}
|
||||
// HACK: Re-use the dummy buffer for disabled attributes to satisfy WebGPU validation.
|
||||
// Filament's shaders expect vec4 or uvec4. We provide a dummy format.
|
||||
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
vertexFormat = isInteger ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Unorm8x4;
|
||||
attribute = dummyAttribute;
|
||||
} else {
|
||||
const bool isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
const bool isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
|
||||
vertexFormat = getVertexFormat(attribute.type, isNormalized, isInteger);
|
||||
}
|
||||
|
||||
uint8_t existingSlot = INVALID_SLOT_INDEX;
|
||||
for (uint32_t slot = 0; slot < currentWebGPUSlotIndex; slot++) {
|
||||
WebGPUVertexBufferInfo::WebGPUSlotBindingInfo const& info =
|
||||
outWebGPUSlotBindingInfos[slot];
|
||||
// We consider attributes to be in the same slot/layout only if they belong to the
|
||||
// same buffer and are interleaved; they cannot belong to separate partitions in the
|
||||
// same buffer, for example.
|
||||
// For the attributes to be interleaved, the stride must match (among other things).
|
||||
// The attribute offset being less than the slot's/layout's buffer offset indicates
|
||||
// that it is in a separate partition before this slot/layout, thus not part of it.
|
||||
// The difference from the attribute's offset and this slot's/layout's buffer offset
|
||||
// being greater than the stride indicates it is in a separate partition after this
|
||||
// slot/layout, thus not part of it.
|
||||
if (info.sourceBufferIndex == attribute.buffer && info.stride == attribute.stride &&
|
||||
attribute.offset >= info.bufferOffset &&
|
||||
((attribute.offset - info.bufferOffset) < attribute.stride)) {
|
||||
@@ -269,26 +300,18 @@ void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attr
|
||||
}
|
||||
}
|
||||
if (existingSlot == INVALID_SLOT_INDEX) {
|
||||
// New combination, use a new WebGPU slot
|
||||
FILAMENT_CHECK_PRECONDITION(currentWebGPUSlotIndex < MAX_VERTEX_BUFFER_COUNT &&
|
||||
currentWebGPUSlotIndex < deviceMaxVertexBuffers)
|
||||
<< "Number of vertex buffer layouts must not exceed MAX_VERTEX_BUFFER_COUNT ("
|
||||
<< MAX_VERTEX_BUFFER_COUNT << ") or the device limit ("
|
||||
<< deviceMaxVertexBuffers << ")";
|
||||
existingSlot = currentWebGPUSlotIndex++;
|
||||
outWebGPUSlotBindingInfos.push_back({
|
||||
.sourceBufferIndex = attribute.buffer,
|
||||
outWebGPUSlotBindingInfos.push_back({ .sourceBufferIndex = attribute.buffer,
|
||||
.bufferOffset = attribute.offset,
|
||||
.stride = attribute.stride });
|
||||
// we need to have a vertex buffer layout for each slot
|
||||
outVertexBufferLayouts.push_back({
|
||||
.stepMode = wgpu::VertexStepMode::Vertex,
|
||||
.arrayStride = attribute.stride
|
||||
// we do not know attributeCount or attributes at this time. We get those
|
||||
// in a subsequent pass over the attributeInfos we collect in this loop.
|
||||
});
|
||||
outVertexBufferLayouts.push_back(
|
||||
{ .stepMode = wgpu::VertexStepMode::Vertex, .arrayStride = attribute.stride });
|
||||
}
|
||||
// we don't use a designated initializer here because certain compilers can't handle it
|
||||
outAttributeInfos[currentAttributeIndex++] = AttributeInfo(
|
||||
existingSlot,
|
||||
{
|
||||
@@ -297,9 +320,8 @@ void createBufferLayoutsBindingSlotsAndAttributeInfos(AttributeArray const& attr
|
||||
attribute.offset - outWebGPUSlotBindingInfos[existingSlot].bufferOffset,
|
||||
.shaderLocation = attributeIndex });
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(currentAttributeIndex == attributeCount)
|
||||
<< "Using " << currentAttributeIndex << " attributes, but " << attributeCount
|
||||
<< " where indicated.";
|
||||
|
||||
outActualAttributeCount = currentAttributeIndex;
|
||||
outVertexBufferLayouts.shrink_to_fit();
|
||||
outWebGPUSlotBindingInfos.shrink_to_fit();
|
||||
}
|
||||
@@ -316,21 +338,21 @@ WebGPUVertexBufferInfo::WebGPUVertexBufferInfo(const uint8_t bufferCount,
|
||||
<< ") exceeds Filament's MAX_VERTEX_ATTRIBUTE_COUNT limit ("
|
||||
<< MAX_VERTEX_ATTRIBUTE_COUNT << ") and/or the device's limit ("
|
||||
<< deviceLimits.maxVertexAttributes << ")";
|
||||
mVertexAttributes.reserve(attributeCount);
|
||||
mVertexAttributes.reserve(MAX_VERTEX_ATTRIBUTE_COUNT); // Reserve max as we might add dummies
|
||||
if (attributeCount == 0) {
|
||||
mVertexBufferLayouts.resize(0);
|
||||
mWebGPUSlotBindingInfos.resize(0);
|
||||
return; // should not be possible, but being defensive. nothing to do otherwise
|
||||
}
|
||||
std::array<AttributeInfo, MAX_VERTEX_ATTRIBUTE_COUNT> attributeInfos{};
|
||||
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, attributeCount,
|
||||
deviceLimits.maxVertexBuffers, mWebGPUSlotBindingInfos, mVertexBufferLayouts,
|
||||
attributeInfos);
|
||||
uint8_t actualAttributeCount = 0;
|
||||
createBufferLayoutsBindingSlotsAndAttributeInfos(attributes, deviceLimits.maxVertexBuffers,
|
||||
mWebGPUSlotBindingInfos, mVertexBufferLayouts, attributeInfos, actualAttributeCount);
|
||||
// sort attribute infos by increasing slot (by increasing offset within each slot).
|
||||
// We do this to ensure that attributes for the same slot/layout are contiguous in
|
||||
// the vector, so the vertex buffer layout associated with these contiguous attributes
|
||||
// can directly reference them in the mVertexAttributes vector below.
|
||||
std::sort(attributeInfos.data(), attributeInfos.data() + attributeCount,
|
||||
std::sort(attributeInfos.data(), attributeInfos.data() + actualAttributeCount,
|
||||
[](AttributeInfo const& first, AttributeInfo const& second) {
|
||||
if (first.slot < second.slot) {
|
||||
return true;
|
||||
@@ -344,7 +366,7 @@ WebGPUVertexBufferInfo::WebGPUVertexBufferInfo(const uint8_t bufferCount,
|
||||
});
|
||||
// populate mVertexAttributes and update mVertexBufferLayouts to reference the correct
|
||||
// attributes in it (which will be contiguous in memory as ensured by the sorting above)...
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; ++attributeIndex) {
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < actualAttributeCount; ++attributeIndex) {
|
||||
AttributeInfo const& info = attributeInfos[attributeIndex];
|
||||
mVertexAttributes.push_back(info.attribute);
|
||||
if (mVertexBufferLayouts[info.slot].attributeCount == 0) {
|
||||
|
||||
@@ -881,14 +881,6 @@ 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
|
||||
if (mTargetApi == TargetApi::WEBGPU) {
|
||||
std::string const variableName = "FILAMENT_SPEC_CONST_" + std::to_string(id) + "_" + name;
|
||||
out << " const " << types[value.index()] << " " << variableName << " = " << constantString << ";\n";
|
||||
out << types[value.index()] << " " << name << " = " << variableName << ";\n";
|
||||
return out;
|
||||
}
|
||||
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
|
||||
out << "layout (constant_id = " << id << ") const "
|
||||
<< types[value.index()] << " " << name << " = " << constantString << ";\n";
|
||||
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
virtual ~Listener() noexcept;
|
||||
};
|
||||
|
||||
using ChangeCallback = std::function<void(utils::Slice<const Entity>)>;
|
||||
using ChangeCallback = std::function<void(Slice<const Entity>)>;
|
||||
|
||||
/**
|
||||
* Registers a callback to be triggered when entities are destroyed.
|
||||
@@ -105,10 +105,7 @@ public:
|
||||
|
||||
// Return whether the given Entity has been destroyed (false) or not (true).
|
||||
// Thread safe.
|
||||
bool isAlive(Entity const e) const noexcept {
|
||||
assert(getIndex(e) < RAW_INDEX_COUNT);
|
||||
return (!e.isNull()) && (getGeneration(e) == mGens[getIndex(e)]);
|
||||
}
|
||||
bool isAlive(Entity e) const noexcept;
|
||||
|
||||
// Registers a listener to be called when an entity is destroyed. Thread safe.
|
||||
// If the listener is already registered, this method has no effect.
|
||||
@@ -118,12 +115,8 @@ public:
|
||||
void unregisterListener(Listener* l) noexcept;
|
||||
|
||||
|
||||
/* no user serviceable parts below */
|
||||
|
||||
// current generation of the given index. Use for debugging and testing.
|
||||
uint8_t getGenerationForIndex(size_t const index) const noexcept {
|
||||
return mGens[index];
|
||||
}
|
||||
/* no user serviceable parts below */
|
||||
|
||||
// singleton, can't be copied
|
||||
EntityManager(const EntityManager& rhs) = delete;
|
||||
@@ -157,9 +150,6 @@ private:
|
||||
static Entity::Type makeIdentity(Entity::Type const g, Entity::Type const i) noexcept {
|
||||
return (g << GENERATION_SHIFT) | (i & INDEX_MASK);
|
||||
}
|
||||
|
||||
// stores the generation of each index.
|
||||
uint8_t* const mGens;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
|
||||
@@ -18,21 +18,21 @@
|
||||
|
||||
#include "EntityManagerImpl.h"
|
||||
|
||||
#include <utils/Entity.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
namespace utils {
|
||||
|
||||
EntityManager::Listener::~Listener() noexcept = default;
|
||||
|
||||
EntityManager::EntityManager()
|
||||
: mGens(new uint8_t[RAW_INDEX_COUNT]) {
|
||||
// initialize all the generations to 0
|
||||
std::fill_n(mGens, RAW_INDEX_COUNT, 0);
|
||||
}
|
||||
EntityManager::EntityManager() = default;
|
||||
|
||||
EntityManager::~EntityManager() {
|
||||
delete [] mGens;
|
||||
}
|
||||
EntityManager::~EntityManager() = default;
|
||||
|
||||
EntityManager& EntityManager::get() noexcept {
|
||||
// note: we leak the EntityManager because it's more important that it survives everything else
|
||||
@@ -41,11 +41,11 @@ EntityManager& EntityManager::get() noexcept {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
void EntityManager::create(size_t n, Entity* entities) {
|
||||
void EntityManager::create(size_t const n, Entity* entities) {
|
||||
static_cast<EntityManagerImpl *>(this)->create(n, entities);
|
||||
}
|
||||
|
||||
void EntityManager::destroy(size_t n, Entity* entities) noexcept {
|
||||
void EntityManager::destroy(size_t const n, Entity* entities) noexcept {
|
||||
static_cast<EntityManagerImpl *>(this)->destroy(n, entities);
|
||||
}
|
||||
|
||||
@@ -84,4 +84,8 @@ void EntityManager::dumpActiveEntities(utils::io::ostream& out) const {
|
||||
|
||||
#endif
|
||||
|
||||
bool EntityManager::isAlive(Entity const e) const noexcept {
|
||||
return static_cast<EntityManagerImpl const *>(this)->isAlive(e);
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex> // for std::lock_guard
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -49,15 +50,38 @@ static constexpr size_t MIN_FREE_INDICES = 1024;
|
||||
|
||||
class UTILS_PRIVATE EntityManagerImpl : public EntityManager {
|
||||
public:
|
||||
friend class EntityManager;
|
||||
|
||||
using EntityManager::getGeneration;
|
||||
using EntityManager::getIndex;
|
||||
using EntityManager::makeIdentity;
|
||||
using EntityManager::create;
|
||||
using EntityManager::destroy;
|
||||
|
||||
EntityManagerImpl() noexcept
|
||||
: mGens(new(std::nothrow) uint8_t[RAW_INDEX_COUNT]) {
|
||||
// initialize all the generations to 0
|
||||
std::fill_n(mGens, RAW_INDEX_COUNT, 0);
|
||||
}
|
||||
|
||||
~EntityManagerImpl() noexcept {
|
||||
delete [] mGens;
|
||||
}
|
||||
|
||||
bool isAlive(Entity const e) const noexcept {
|
||||
assert(getIndex(e) < RAW_INDEX_COUNT);
|
||||
if (e.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard const lock(mFreeListLock);
|
||||
bool const alive = (getGeneration(e) == mGens[getIndex(e)]);
|
||||
return alive;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
size_t getEntityCount() const noexcept {
|
||||
std::lock_guard<Mutex> const lock(mFreeListLock);
|
||||
std::lock_guard const lock(mFreeListLock);
|
||||
if (mCurrentIndex < RAW_INDEX_COUNT) {
|
||||
return (mCurrentIndex - 1) - mFreeList.size();
|
||||
} else {
|
||||
@@ -66,13 +90,13 @@ public:
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void create(size_t n, Entity* entities) {
|
||||
void create(size_t const n, Entity* entities) {
|
||||
Entity::Type index{};
|
||||
auto& freeList = mFreeList;
|
||||
uint8_t const* const gens = mGens;
|
||||
|
||||
// this must be thread-safe, acquire the free-list mutex
|
||||
std::lock_guard<Mutex> const lock(mFreeListLock);
|
||||
std::lock_guard const lock(mFreeListLock);
|
||||
Entity::Type currentIndex = mCurrentIndex;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (UTILS_UNLIKELY(currentIndex >= RAW_INDEX_COUNT || freeList.size() >= MIN_FREE_INDICES)) {
|
||||
@@ -98,11 +122,11 @@ public:
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void destroy(size_t n, Entity* entities) noexcept {
|
||||
void destroy(size_t const n, Entity* entities) noexcept {
|
||||
auto& freeList = mFreeList;
|
||||
uint8_t* const gens = mGens;
|
||||
|
||||
std::unique_lock<Mutex> lock(mFreeListLock);
|
||||
std::unique_lock lock(mFreeListLock);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (!entities[i]) {
|
||||
// behave like free(), ok to free null Entity.
|
||||
@@ -110,19 +134,15 @@ public:
|
||||
}
|
||||
|
||||
// it's an error to delete an Entity twice...
|
||||
assert(isAlive(entities[i]));
|
||||
bool const isAlive = getGeneration(entities[i]) == mGens[getIndex(entities[i])];
|
||||
assert(isAlive);
|
||||
|
||||
// ... deleting a dead Entity will corrupt the internal state, so we protect ourselves
|
||||
// against it. We don't guarantee anything about external state -- e.g. the listeners
|
||||
// will be called.
|
||||
if (isAlive(entities[i])) {
|
||||
if (UTILS_LIKELY(isAlive)) {
|
||||
Entity::Type const index = getIndex(entities[i]);
|
||||
freeList.push_back(index);
|
||||
|
||||
// The generation update doesn't require the lock because it's only used for isAlive()
|
||||
// and entities work as weak references -- it just means that isAlive() could return
|
||||
// true a little longer than expected in some other threads.
|
||||
// We do need a memory fence though, it is provided by the mFreeListLock.unlock() below.
|
||||
gens[index]++;
|
||||
|
||||
#if FILAMENT_UTILS_TRACK_ENTITIES
|
||||
@@ -151,22 +171,22 @@ public:
|
||||
}
|
||||
|
||||
void registerListener(Listener* l) noexcept {
|
||||
std::lock_guard<Mutex> const lock(mListenerLock);
|
||||
std::lock_guard const lock(mListenerLock);
|
||||
mListeners.insert(l);
|
||||
}
|
||||
|
||||
void unregisterListener(Listener* l) noexcept {
|
||||
std::lock_guard<Mutex> const lock(mListenerLock);
|
||||
std::lock_guard const lock(mListenerLock);
|
||||
mListeners.erase(l);
|
||||
}
|
||||
|
||||
void registerChangeCallback(void const* token, ChangeCallback callback) noexcept {
|
||||
std::lock_guard<Mutex> const lock(mListenerLock);
|
||||
std::lock_guard const lock(mListenerLock);
|
||||
mChangeCallbacks.push_back({token, std::move(callback)});
|
||||
}
|
||||
|
||||
void unregisterChangeCallback(void const* token) noexcept {
|
||||
std::lock_guard<Mutex> const lock(mListenerLock);
|
||||
std::lock_guard const lock(mListenerLock);
|
||||
mChangeCallbacks.erase(
|
||||
std::remove_if(mChangeCallbacks.begin(), mChangeCallbacks.end(),
|
||||
[token](auto const& info) { return info.token == token; }),
|
||||
@@ -174,7 +194,7 @@ public:
|
||||
}
|
||||
|
||||
void flushNotifications() noexcept {
|
||||
std::unique_lock<Mutex> lock(mFreeListLock);
|
||||
std::unique_lock lock(mFreeListLock);
|
||||
if (mDirtyCount > 0) {
|
||||
Entity localBuffer[MAX_DIRTY_COUNT];
|
||||
assert_invariant(mDirtyCount <= MAX_DIRTY_COUNT);
|
||||
@@ -208,7 +228,7 @@ public:
|
||||
|
||||
private:
|
||||
std::vector<ChangeCallback> getChangeCallbacks() const noexcept {
|
||||
std::lock_guard<Mutex> const lock(mListenerLock);
|
||||
std::lock_guard const lock(mListenerLock);
|
||||
std::vector<ChangeCallback> result;
|
||||
result.reserve(mChangeCallbacks.size());
|
||||
for (auto const& info : mChangeCallbacks) {
|
||||
@@ -217,7 +237,7 @@ private:
|
||||
return result;
|
||||
}
|
||||
|
||||
void triggerChangeCallbacks(Entity const* entities, size_t n) const noexcept {
|
||||
void triggerChangeCallbacks(Entity const* entities, size_t const n) const noexcept {
|
||||
auto const callbacks = getChangeCallbacks();
|
||||
Slice const slice(entities, n);
|
||||
for (auto const& callback : callbacks) {
|
||||
@@ -226,7 +246,7 @@ private:
|
||||
}
|
||||
|
||||
FixedCapacityVector<Listener*> getListeners() const noexcept {
|
||||
std::lock_guard<Mutex> const lock(mListenerLock);
|
||||
std::lock_guard const lock(mListenerLock);
|
||||
tsl::robin_set<Listener*> const& listeners = mListeners;
|
||||
FixedCapacityVector<Listener*> result(listeners.size());
|
||||
result.resize(result.capacity()); // unfortunately this memset()
|
||||
@@ -234,22 +254,20 @@ private:
|
||||
return result; // the c++ standard guarantees a move
|
||||
}
|
||||
|
||||
uint32_t mCurrentIndex = 1;
|
||||
|
||||
// stores indices that got freed
|
||||
mutable Mutex mFreeListLock;
|
||||
std::deque<Entity::Type> mFreeList;
|
||||
|
||||
mutable Mutex mListenerLock;
|
||||
tsl::robin_set<Listener*> mListeners;
|
||||
|
||||
static constexpr size_t MAX_DIRTY_COUNT = 16;
|
||||
struct CallbackInfo {
|
||||
void const* token;
|
||||
ChangeCallback callback;
|
||||
};
|
||||
std::vector<CallbackInfo> mChangeCallbacks;
|
||||
|
||||
static constexpr size_t MAX_DIRTY_COUNT = 16;
|
||||
mutable Mutex mFreeListLock;
|
||||
uint32_t mCurrentIndex = 1;
|
||||
std::deque<Entity::Type> mFreeList; // stores indices that got freed
|
||||
uint8_t* const mGens; // stores the generation of each index.
|
||||
|
||||
mutable Mutex mListenerLock;
|
||||
tsl::robin_set<Listener*> mListeners;
|
||||
std::vector<CallbackInfo> mChangeCallbacks;
|
||||
Entity mDirtyEntities[MAX_DIRTY_COUNT];
|
||||
size_t mDirtyCount = 0;
|
||||
|
||||
|
||||
3
third_party/dawn/tnt/CMakeLists.txt
vendored
3
third_party/dawn/tnt/CMakeLists.txt
vendored
@@ -52,8 +52,6 @@ if (ANDROID AND FILAMENT_BUILD_FILAMAT)
|
||||
tint_lang_spirv_intrinsic
|
||||
tint_lang_spirv_ir
|
||||
tint_lang_spirv_reader
|
||||
tint_lang_spirv_reader_ast_lower
|
||||
tint_lang_spirv_reader_ast_parser
|
||||
tint_lang_spirv_reader_common
|
||||
tint_lang_spirv_reader_lower
|
||||
tint_lang_spirv_reader_parser
|
||||
@@ -77,6 +75,7 @@ if (ANDROID AND FILAMENT_BUILD_FILAMAT)
|
||||
tint_utils_rtti
|
||||
tint_utils_strconv
|
||||
tint_utils_symbol
|
||||
tint_utils_system
|
||||
tint_utils_text
|
||||
tint_utils_text_generator
|
||||
)
|
||||
|
||||
@@ -4,7 +4,8 @@ material {
|
||||
color
|
||||
],
|
||||
shadingModel : unlit,
|
||||
culling : none
|
||||
culling : none,
|
||||
featureLevel: 0
|
||||
}
|
||||
|
||||
fragment {
|
||||
|
||||
@@ -31,6 +31,7 @@ set(COPTS "${COPTS} -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0")
|
||||
set(LOPTS "${LOPTS} --bind")
|
||||
set(LOPTS "${LOPTS} -s ALLOW_MEMORY_GROWTH=1")
|
||||
set(LOPTS "${LOPTS} -s EXPORTED_FUNCTIONS=['_malloc','_free']")
|
||||
set(LOPTS "${LOPTS} -s EXPORTED_RUNTIME_METHODS=['HEAP8','HEAP16','HEAP32','HEAPU8','HEAPU16','HEAPU32','HEAPF32','HEAPF64']")
|
||||
set(LOPTS "${LOPTS} -s MODULARIZE=1")
|
||||
set(LOPTS "${LOPTS} -s EXPORT_NAME=Filament")
|
||||
set(LOPTS "${LOPTS} -s USE_WEBGL2=1")
|
||||
|
||||
Reference in New Issue
Block a user