Compare commits
5 Commits
kpiascik/s
...
zm/skip-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55dbafe0b0 | ||
|
|
4879a0a124 | ||
|
|
637affefeb | ||
|
|
8d219d3bc0 | ||
|
|
a7289da05f |
@@ -275,8 +275,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPUIndexBuffer.h
|
||||
src/webgpu/WebGPUMsaaTextureResolver.cpp
|
||||
src/webgpu/WebGPUMsaaTextureResolver.h
|
||||
src/webgpu/WebGPUPipelineCreation.cpp
|
||||
src/webgpu/WebGPUPipelineCreation.h
|
||||
src/webgpu/WebGPUPipelineCache.cpp
|
||||
src/webgpu/WebGPUPipelineCache.h
|
||||
src/webgpu/WebGPUPipelineLayoutCache.cpp
|
||||
src/webgpu/WebGPUPipelineLayoutCache.h
|
||||
src/webgpu/WebGPUProgram.cpp
|
||||
src/webgpu/WebGPUProgram.h
|
||||
src/webgpu/WebGPURenderPassMipmapGenerator.cpp
|
||||
|
||||
@@ -121,6 +121,12 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks the token's completion status. The token is considered ready if the program was
|
||||
// created successfully or if a shader compilation error occurred.
|
||||
bool isReady() const noexcept {
|
||||
return gl.program || compilationFailed;
|
||||
}
|
||||
|
||||
std::optional<CallbackManager::Handle> handle{};
|
||||
|
||||
// Only valid when the blob functions are provided by users. The validity of this variable
|
||||
@@ -134,6 +140,9 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
|
||||
// Indicate this program was created from the cache blob.
|
||||
bool retrievedFromBlobCache = false;
|
||||
|
||||
// Indicates that shader compilation failed.
|
||||
bool compilationFailed = false;
|
||||
};
|
||||
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
|
||||
@@ -297,22 +306,33 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// Check link completion if link was initiated.
|
||||
if (token->gl.program) {
|
||||
|
||||
if (token->gl.program) {
|
||||
// Program linking has been initiated.
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
return isLinkCompleted(token);
|
||||
}
|
||||
// Link hasn't been initiated, then check compile completion.
|
||||
return true; // In sync mode, if program exists, we're done with this job.
|
||||
}
|
||||
|
||||
// Program not linked yet. Check if shaders are compiled.
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
if (!isCompileCompleted(token)) {
|
||||
return false;
|
||||
return false; // Wait for compilation to finish.
|
||||
}
|
||||
}
|
||||
if (!token->gl.program) {
|
||||
linkProgram(mDriver.getContext(), token);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
return false;// Wait until the link finishes.
|
||||
}
|
||||
|
||||
// Shaders are compiled (or we're in sync mode). Let's link.
|
||||
if (!linkProgram(mDriver.getContext(), token)) {
|
||||
// Shader compilation failed. Stop processing this program.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Program is now linking.
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
return false; // Wait for linking to finish.
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
@@ -394,6 +414,14 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
assert_invariant(token);// This function should be called when the token is still alive.
|
||||
|
||||
ensureTokenIsReady(token);
|
||||
|
||||
if (token->compilationFailed) {
|
||||
// Cleanup the token.
|
||||
token->compiler.cancelTickOp(token);
|
||||
token = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
// Check status of program linking. If it failed, errors will be logged.
|
||||
@@ -419,7 +447,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
}
|
||||
|
||||
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
||||
if (token->gl.program) {
|
||||
if (token->isReady()) {
|
||||
return;// It's ready.
|
||||
}
|
||||
|
||||
@@ -638,9 +666,10 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
|
||||
/* static */ bool ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
bool success = true;
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const GLuint shader = token->gl.shaders[i];
|
||||
@@ -656,15 +685,21 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
// Something went wrong. Log the error message.
|
||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||
logCompilationError(type, token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
|
||||
success = false;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::linkProgram(OpenGLContext const& context,
|
||||
/* static */ bool ShaderCompilerService::linkProgram(OpenGLContext const& context,
|
||||
program_token_t const& token) noexcept {
|
||||
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
|
||||
|
||||
// Shader compilation should be completed by now. Check the status and log errors on failure.
|
||||
checkCompileStatus(token);
|
||||
if (!checkCompileStatus(token)) {
|
||||
token->compilationFailed = true;
|
||||
token->trySubmittingCallback();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Link program
|
||||
GLuint const program = glCreateProgram();
|
||||
@@ -681,6 +716,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
glLinkProgram(program);
|
||||
token->gl.program = program;
|
||||
token->trySubmittingCallback();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
|
||||
|
||||
@@ -155,13 +155,14 @@ private:
|
||||
static bool isCompileCompleted(program_token_t const& token) noexcept;
|
||||
|
||||
// Check compilation status of the shaders and log errors on failure.
|
||||
static void checkCompileStatus(program_token_t const& token) noexcept;
|
||||
static bool checkCompileStatus(program_token_t const& token) noexcept;
|
||||
|
||||
// Create a program by linking the compiled shaders. `gl.program` is always populated with a
|
||||
// valid program ID after this method. But this doesn't necessarily mean the program is
|
||||
// successfully linked. Errors can be checked by calling `checkLinkStatusAndCleanupShaders`
|
||||
// later.
|
||||
static void linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
|
||||
// Create a program by linking the compiled shaders. If the previous shader compilation was
|
||||
// failed, `compilationFailed` is set, then this function returns false. Otherwise `gl.program`
|
||||
// is populated with a valid program ID, then returns true. However this doesn't necessarily
|
||||
// mean the program is successfully linked. The link error can be checked by calling
|
||||
// `checkLinkStatusAndCleanupShaders` later.
|
||||
static bool linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
|
||||
|
||||
// Check if the program link is completed. You may want to call this when the extension
|
||||
// `KHR_parallel_shader_compile` is enabled.
|
||||
|
||||
@@ -255,6 +255,21 @@ void adjustedMemcpy(void* mapped, PixelBufferDescriptor const& p, size_t width,
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getAlignmentForBufferToImageCopy(VkFormat format) {
|
||||
// VUID-vkCmdCopyBufferToImage-dstImage-07978
|
||||
if(fvkutils::isVkDepthFormat(format) || fvkutils::isVkStencilFormat(format)) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (fvkutils::isVKYcbcrConversionFormat(format)) {
|
||||
assert_invariant(false && "Multi planar format is not supported");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// VUID-vkCmdCopyBufferToImage-dstImage-07975
|
||||
return fvkutils::getTexelBlockSize(format);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
|
||||
@@ -526,8 +541,7 @@ void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t widt
|
||||
// Note: the following stageSegment must be stored within the command buffer
|
||||
// before going out of scope, to ensure proper bookkeeping within the
|
||||
// staging buffer pool.
|
||||
uint8_t alignment =
|
||||
fvkutils::getTexelBlockSize(fvkutils::getVkFormat(hostData->format, hostData->type));
|
||||
uint8_t alignment = getAlignmentForBufferToImageCopy(mState->mVkFormat);
|
||||
fvkmemory::resource_ptr<VulkanStage::Segment> stageSegment =
|
||||
mState->mStagePool.acquireStage(writeSize, alignment);
|
||||
assert_invariant(stageSegment->memory());
|
||||
|
||||
@@ -233,15 +233,12 @@ VkFormat getVkFormat(TextureFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
// As per VUID-vkCmdCopyBufferToImage-dstImage-07975 and
|
||||
// VUID-vkCmdCopyBufferToImage-dstImage-07978, these provide the texel block
|
||||
// sizes for each format (as confirmed using the table at
|
||||
// As per
|
||||
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#formats-compatibility-classes).
|
||||
// We have not listed values for multi-plane formats, as we do not support them
|
||||
// unless they're externally provided.
|
||||
uint8_t getTexelBlockSize(VkFormat format) {
|
||||
switch (format) {
|
||||
// 8-bit formats.
|
||||
case VK_FORMAT_R4G4_UNORM_PACK8:
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
case VK_FORMAT_R8_SNORM:
|
||||
case VK_FORMAT_R8_USCALED:
|
||||
@@ -249,16 +246,21 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_R8_UINT:
|
||||
case VK_FORMAT_R8_SINT:
|
||||
case VK_FORMAT_R8_SRGB:
|
||||
case VK_FORMAT_S8_UINT:
|
||||
return 1;
|
||||
|
||||
// 16-bit formats.
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16_SNORM:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16_SINT:
|
||||
case VK_FORMAT_R16_SFLOAT:
|
||||
case VK_FORMAT_R10X6_UNORM_PACK16:
|
||||
case VK_FORMAT_R12X4_UNORM_PACK16:
|
||||
case VK_FORMAT_A4R4G4B4_UNORM_PACK16:
|
||||
case VK_FORMAT_A4B4G4R4_UNORM_PACK16:
|
||||
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
|
||||
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
|
||||
case VK_FORMAT_R5G6B5_UNORM_PACK16:
|
||||
case VK_FORMAT_B5G6R5_UNORM_PACK16:
|
||||
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
|
||||
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
|
||||
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
|
||||
case VK_FORMAT_R8G8_UNORM:
|
||||
case VK_FORMAT_R8G8_SNORM:
|
||||
case VK_FORMAT_R8G8_USCALED:
|
||||
@@ -266,9 +268,14 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
case VK_FORMAT_R8G8_SINT:
|
||||
case VK_FORMAT_R8G8_SRGB:
|
||||
case VK_FORMAT_R5G6B5_UNORM_PACK16:
|
||||
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
|
||||
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16_SNORM:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16_SINT:
|
||||
case VK_FORMAT_R16_SFLOAT:
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
return 2;
|
||||
|
||||
// 24-bit formats.
|
||||
@@ -286,19 +293,18 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_B8G8R8_UINT:
|
||||
case VK_FORMAT_B8G8R8_SINT:
|
||||
case VK_FORMAT_B8G8R8_SRGB:
|
||||
case VK_FORMAT_D16_UNORM_S8_UINT:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
|
||||
return 3;
|
||||
|
||||
// 32-bit formats.
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32_SINT:
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16_SNORM:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
case VK_FORMAT_R16G16_SFLOAT:
|
||||
case VK_FORMAT_R10X6G10X6_UNORM_2PACK16:
|
||||
case VK_FORMAT_R12X4G12X4_UNORM_2PACK16:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SNORM:
|
||||
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||
@@ -320,18 +326,41 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
|
||||
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
|
||||
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||||
// Depth and stencil formats.
|
||||
case VK_FORMAT_S8_UINT:
|
||||
case VK_FORMAT_D16_UNORM:
|
||||
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
|
||||
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16_SNORM:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
case VK_FORMAT_R16G16_SFLOAT:
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32_SINT:
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||||
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
|
||||
case VK_FORMAT_X8_D24_UNORM_PACK32:
|
||||
case VK_FORMAT_D32_SFLOAT:
|
||||
case VK_FORMAT_D24_UNORM_S8_UINT:
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||||
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
|
||||
case VK_FORMAT_G8B8G8R8_422_UNORM:
|
||||
case VK_FORMAT_B8G8R8G8_422_UNORM:
|
||||
return 4;
|
||||
|
||||
// 40-bit formats.
|
||||
case VK_FORMAT_D32_SFLOAT_S8_UINT:
|
||||
return 5;
|
||||
|
||||
// 48-bit formats.
|
||||
case VK_FORMAT_R16G16B16_UNORM:
|
||||
case VK_FORMAT_R16G16B16_SNORM:
|
||||
@@ -340,12 +369,27 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_R16G16B16_UINT:
|
||||
case VK_FORMAT_R16G16B16_SINT:
|
||||
case VK_FORMAT_R16G16B16_SFLOAT:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
|
||||
return 6;
|
||||
|
||||
// 64-bit formats.
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||||
case VK_FORMAT_R16G16B16A16_SNORM:
|
||||
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||
@@ -353,7 +397,12 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
case VK_FORMAT_R16G16B16A16_SINT:
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
// Compressed formats.
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
case VK_FORMAT_R64_UINT:
|
||||
case VK_FORMAT_R64_SINT:
|
||||
case VK_FORMAT_R64_SFLOAT:
|
||||
case VK_FORMAT_BC1_RGB_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC1_RGB_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC1_RGBA_UNORM_BLOCK:
|
||||
@@ -366,6 +415,22 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK:
|
||||
case VK_FORMAT_EAC_R11_UNORM_BLOCK:
|
||||
case VK_FORMAT_EAC_R11_SNORM_BLOCK:
|
||||
case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
|
||||
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16:
|
||||
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_G16B16G16R16_422_UNORM:
|
||||
case VK_FORMAT_B16G16R16G16_422_UNORM:
|
||||
case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG:
|
||||
case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG:
|
||||
return 8;
|
||||
|
||||
// 96-bit formats.
|
||||
@@ -375,54 +440,82 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
return 12;
|
||||
|
||||
// 128-bit formats.
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
// Compressed formats.
|
||||
case VK_FORMAT_BC2_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC2_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC3_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC3_SRGB_BLOCK:
|
||||
case VK_FORMAT_BC5_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC5_SNORM_BLOCK:
|
||||
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC6H_UFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC6H_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_BC7_UNORM_BLOCK:
|
||||
case VK_FORMAT_BC7_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK:
|
||||
case VK_FORMAT_EAC_R11G11_UNORM_BLOCK:
|
||||
case VK_FORMAT_EAC_R11G11_SNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_4x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x4_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_5x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_6x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_8x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x5_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x6_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x8_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_10x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x10_SRGB_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
|
||||
case VK_FORMAT_ASTC_12x12_SRGB_BLOCK:
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
case VK_FORMAT_R64G64_UINT:
|
||||
case VK_FORMAT_R64G64_SINT:
|
||||
case VK_FORMAT_R64G64_SFLOAT:
|
||||
return 16;
|
||||
|
||||
// 192-bit formats.
|
||||
case VK_FORMAT_R64G64B64_UINT:
|
||||
case VK_FORMAT_R64G64B64_SINT:
|
||||
case VK_FORMAT_R64G64B64_SFLOAT:
|
||||
return 24;
|
||||
|
||||
// 256-bit formats.
|
||||
case VK_FORMAT_R64G64B64A64_UINT:
|
||||
case VK_FORMAT_R64G64B64A64_SINT:
|
||||
case VK_FORMAT_R64G64B64A64_SFLOAT:
|
||||
return 32;
|
||||
|
||||
case VK_FORMAT_UNDEFINED:
|
||||
// In cases where we've explicitly already determined that the
|
||||
// format is not supported, let the rest of the system handle
|
||||
@@ -438,7 +531,6 @@ uint8_t getTexelBlockSize(VkFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VkFormat getVkFormat(PixelDataFormat format, PixelDataType type) {
|
||||
if (type == PixelDataType::USHORT_565) return VK_FORMAT_R5G6B5_UNORM_PACK16;
|
||||
if (type == PixelDataType::UINT_2_10_10_10_REV) return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
|
||||
|
||||
@@ -37,8 +37,8 @@ namespace {
|
||||
uint32_t size, const char* const label) {
|
||||
// Write size must be divisible by WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4).
|
||||
// If the whole buffer is written to as is common, so must the buffer size.
|
||||
size += (WEBGPU_BUFFER_SIZE_MODULUS - (size % WEBGPU_BUFFER_SIZE_MODULUS)) %
|
||||
WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
size += (FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - (size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS)) %
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
wgpu::BufferDescriptor descriptor{
|
||||
.label = label,
|
||||
.usage = usage,
|
||||
@@ -62,15 +62,15 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
||||
<< "Attempting to copy " << bufferDescriptor.size << " bytes into a buffer of size "
|
||||
<< mBuffer.GetSize() << " at offset " << byteOffset;
|
||||
FILAMENT_CHECK_PRECONDITION(byteOffset % WEBGPU_BUFFER_SIZE_MODULUS == 0)
|
||||
<< "Byte offset must be a multiple of " << WEBGPU_BUFFER_SIZE_MODULUS << " but is "
|
||||
<< byteOffset;
|
||||
FILAMENT_CHECK_PRECONDITION(byteOffset % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS == 0)
|
||||
<< "Byte offset must be a multiple of " << FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS
|
||||
<< " but is " << byteOffset;
|
||||
|
||||
// TODO: All buffer objects are created with CopyDst usage.
|
||||
// This may have some performance implications. That should be investigated later.
|
||||
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
|
||||
|
||||
const size_t remainder = bufferDescriptor.size % WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
const size_t remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
|
||||
// WriteBuffer is an async call. But cpu buffer data is already written to the staging
|
||||
// buffer on return from the WriteBuffer.
|
||||
@@ -82,10 +82,11 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
memcpy(mRemainderChunk.data(), remainderStart, remainder);
|
||||
// Pad the remainder with zeros to ensure deterministic behavior, though GPU shouldn't
|
||||
// access this
|
||||
std::memset(mRemainderChunk.data() + remainder, 0, WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
||||
std::memset(mRemainderChunk.data() + remainder, 0,
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
||||
|
||||
queue.WriteBuffer(mBuffer, byteOffset + legalSize, &mRemainderChunk,
|
||||
WEBGPU_BUFFER_SIZE_MODULUS);
|
||||
FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ protected:
|
||||
|
||||
private:
|
||||
const wgpu::Buffer mBuffer;
|
||||
// WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
std::array<uint8_t, WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
// FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS (e.g. 4) bytes to hold any extra chunk we need.
|
||||
std::array<uint8_t, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS> mRemainderChunk{};
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr size_t WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
constexpr size_t FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
|
||||
// FWGPU is short for Filament WebGPU
|
||||
|
||||
@@ -65,13 +65,25 @@ constexpr size_t WEBGPU_BUFFER_SIZE_MODULUS = 4;
|
||||
#define FWGPU_LOGI LOG(INFO)
|
||||
#endif
|
||||
|
||||
constexpr uint64_t REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
|
||||
constexpr uint64_t FILAMENT_WEBGPU_REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
|
||||
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
|
||||
|
||||
constexpr uint64_t REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
|
||||
constexpr uint64_t FILAMENT_WEBGPU_REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
|
||||
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
|
||||
|
||||
constexpr uint64_t SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
|
||||
constexpr uint64_t FILAMENT_WEBGPU_SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
|
||||
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
|
||||
|
||||
// if a render pipeline is not used in this number of consecutive frames,
|
||||
// then expire/release it from the cache.
|
||||
// A smaller number means more frequent pipeline creation events, taking more time.
|
||||
// A larger number means more pipelines stored, taking more memory.
|
||||
constexpr uint64_t FILAMENT_WEBGPU_RENDER_PIPELINE_EXPIRATION_IN_FRAME_COUNT = 45;
|
||||
|
||||
// if a pipeline layout is not used in this number of consecutive frames,
|
||||
// then expire/release it from the cache.
|
||||
// A smaller number means more frequent pipeline layout creation events, taking more time.
|
||||
// A larger number means more layouts stored, taking more memory.
|
||||
constexpr uint64_t FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT = 90;
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#include "WebGPUDescriptorSetLayout.h"
|
||||
#include "WebGPUFence.h"
|
||||
#include "WebGPUIndexBuffer.h"
|
||||
#include "WebGPUPipelineCreation.h"
|
||||
#include "WebGPUPipelineCache.h"
|
||||
#include "WebGPUPipelineLayoutCache.h"
|
||||
#include "WebGPUProgram.h"
|
||||
#include "WebGPURenderPrimitive.h"
|
||||
#include "WebGPURenderTarget.h"
|
||||
@@ -72,6 +73,8 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||
mQueue{ mDevice.GetQueue() },
|
||||
mPipelineLayoutCache{ mDevice },
|
||||
mPipelineCache{ mDevice },
|
||||
mRenderPassMipmapGenerator{ mDevice },
|
||||
mSpdComputePassMipmapGenerator{ mDevice },
|
||||
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
|
||||
@@ -126,7 +129,9 @@ void WebGPUDriver::setFrameCompletedCallback(Handle<HwSwapChain> sch,
|
||||
void WebGPUDriver::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::endFrame(uint32_t frameId) {
|
||||
void WebGPUDriver::endFrame(const uint32_t /* frameId */) {
|
||||
mPipelineLayoutCache.onFrameEnd();
|
||||
mPipelineCache.onFrameEnd();
|
||||
}
|
||||
|
||||
void WebGPUDriver::flush(int) {
|
||||
@@ -1123,110 +1128,94 @@ void WebGPUDriver::blit(Handle<HwTexture> destinationTextureHandle, const uint8_
|
||||
// todo
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::computePipelineKey(PipelineState const& pipelineState,
|
||||
WebGPURenderTarget const* const renderTarget) const {
|
||||
// TODO Investigate implications of this hash more closely. Vulkan has a whole class
|
||||
// VulkanPipelineCache to handle this, may be missing nuance
|
||||
static const auto pipelineStateHasher{
|
||||
utils::hash::MurmurHashFn<filament::backend::PipelineState>()
|
||||
};
|
||||
const std::hash<uint32_t> intHasher{};
|
||||
const std::hash<WebGPURenderTarget const*> addressHasher{};
|
||||
const size_t pipelineStateHash{ intHasher(pipelineStateHasher(pipelineState)) };
|
||||
const size_t renderTargetHash{ addressHasher(renderTarget) };
|
||||
return utils::hash::combine(pipelineStateHash, renderTargetHash);
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
auto pipelineKey{ computePipelineKey(pipelineState, mCurrentRenderTarget) };
|
||||
if (mPipelineMap.find(pipelineKey) != mPipelineMap.end()) {
|
||||
mRenderPassEncoder.SetPipeline(mPipelineMap[pipelineKey]);
|
||||
return;
|
||||
}
|
||||
const auto program = handleCast<WebGPUProgram>(pipelineState.program);
|
||||
assert_invariant(mRenderPassEncoder);
|
||||
const auto program{ handleCast<WebGPUProgram>(pipelineState.program) };
|
||||
assert_invariant(program);
|
||||
WebGPURenderTarget const* renderTarget{ mCurrentRenderTarget };
|
||||
assert_invariant(renderTarget);
|
||||
assert_invariant(program->computeShaderModule == nullptr &&
|
||||
"WebGPU backend does not (yet) support compute pipelines.");
|
||||
FILAMENT_CHECK_POSTCONDITION(program->vertexShaderModule)
|
||||
<< "WebGPU backend requires a vertex shader module for a render pipeline";
|
||||
const auto vertexBufferInfo{ handleCast<WebGPUVertexBufferInfo>(
|
||||
pipelineState.vertexBufferInfo) };
|
||||
assert_invariant(vertexBufferInfo);
|
||||
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> bindGroupLayouts{};
|
||||
assert_invariant(bindGroupLayouts.size() >= pipelineState.pipelineLayout.setLayout.size());
|
||||
size_t bindGroupLayoutCount = 0;
|
||||
for (size_t i = 0; i < bindGroupLayouts.size(); i++) {
|
||||
const auto handle = pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount];
|
||||
size_t bindGroupLayoutCount{ 0 };
|
||||
for (size_t i{ 0 }; i < bindGroupLayouts.size(); i++) {
|
||||
const auto handle{ pipelineState.pipelineLayout.setLayout[bindGroupLayoutCount] };
|
||||
if (handle.getId() == HandleBase::nullid) {
|
||||
continue;
|
||||
}
|
||||
bindGroupLayouts[bindGroupLayoutCount++] =
|
||||
handleCast<WebGPUDescriptorSetLayout>(handle)->getLayout();
|
||||
}
|
||||
std::stringstream layoutLabelStream;
|
||||
layoutLabelStream << program->name.c_str() << " layout";
|
||||
const auto layoutLabel = layoutLabelStream.str();
|
||||
const wgpu::PipelineLayoutDescriptor layoutDescriptor{
|
||||
.label = wgpu::StringView(layoutLabel),
|
||||
const WebGPUPipelineLayoutCache::PipelineLayoutRequest pipelineLayoutRequest{
|
||||
.label = program->name,
|
||||
.bindGroupLayouts = bindGroupLayouts,
|
||||
.bindGroupLayoutCount = bindGroupLayoutCount,
|
||||
.bindGroupLayouts = bindGroupLayouts.data()
|
||||
// TODO investigate immediateDataRangeByteSize
|
||||
};
|
||||
const wgpu::PipelineLayout layout = mDevice.CreatePipelineLayout(&layoutDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(layout)
|
||||
<< "Failed to create wgpu::PipelineLayout for render pipeline for "
|
||||
<< layoutDescriptor.label;
|
||||
const auto vertexBufferInfo =
|
||||
handleCast<WebGPUVertexBufferInfo>(pipelineState.vertexBufferInfo);
|
||||
assert_invariant(vertexBufferInfo);
|
||||
|
||||
std::vector<wgpu::TextureFormat> pipelineColorFormats;
|
||||
wgpu::TextureFormat pipelineDepthStencilFormat = wgpu::TextureFormat::Undefined;
|
||||
uint8_t pipelineSamples = 1;
|
||||
bool const requestedDepth = any(mCurrentRenderTarget->getTargetFlags() & TargetBufferFlags::DEPTH);
|
||||
bool const requestedStencil = any(mCurrentRenderTarget->getTargetFlags() & TargetBufferFlags::STENCIL);
|
||||
pipelineSamples = mCurrentRenderTarget->getSamples();
|
||||
|
||||
if (mCurrentRenderTarget->isDefaultRenderTarget()) {
|
||||
pipelineColorFormats.push_back(mSwapChain->getColorFormat());
|
||||
pipelineDepthStencilFormat = mSwapChain->getDepthFormat();
|
||||
wgpu::PipelineLayout const& layout{ mPipelineLayoutCache.getOrCreatePipelineLayout(
|
||||
pipelineLayoutRequest) };
|
||||
uint8_t colorFormatCount{ 0 };
|
||||
std::array<wgpu::TextureFormat, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorFormats{
|
||||
wgpu::TextureFormat::Undefined
|
||||
};
|
||||
wgpu::TextureFormat depthStencilFormat{ wgpu::TextureFormat::Undefined };
|
||||
if (renderTarget->isDefaultRenderTarget()) {
|
||||
// default render target color(s) (one)...
|
||||
colorFormatCount = 1;
|
||||
colorFormats[0] = mSwapChain->getColorFormat();
|
||||
// default render target depth/stencil...
|
||||
depthStencilFormat = mSwapChain->getDepthFormat();
|
||||
} else {
|
||||
const auto& mrtColorAttachments = mCurrentRenderTarget->getColorAttachmentInfos();
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
// custom render target color(s)...
|
||||
MRT const& mrtColorAttachments{ mCurrentRenderTarget->getColorAttachmentInfos() };
|
||||
for (size_t i{ 0 }; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
if (mrtColorAttachments[i].handle) {
|
||||
const auto colorTexture = handleCast<WebGPUTexture>(mrtColorAttachments[i].handle);
|
||||
const auto colorTexture{ handleCast<WebGPUTexture>(mrtColorAttachments[i].handle) };
|
||||
if (colorTexture) {
|
||||
pipelineColorFormats.push_back(colorTexture->getTexture().GetFormat());
|
||||
colorFormats[colorFormatCount++] = colorTexture->getTexture().GetFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// custom render target depth/stencil...
|
||||
const auto& depthInfo = mCurrentRenderTarget->getDepthAttachmentInfo();
|
||||
const auto& stencilInfo = mCurrentRenderTarget->getStencilAttachmentInfo();
|
||||
Handle<HwTexture> depthStencilHandle = {};
|
||||
Handle<HwTexture> depthStencilHandle{};
|
||||
if (depthInfo.handle) {
|
||||
depthStencilHandle = depthInfo.handle;
|
||||
} else if (stencilInfo.handle) {
|
||||
depthStencilHandle = stencilInfo.handle;
|
||||
depthStencilHandle = stencilInfo.handle;
|
||||
}
|
||||
|
||||
if (depthStencilHandle) {
|
||||
const auto dsTexture = handleCast<WebGPUTexture>(depthStencilHandle);
|
||||
if (dsTexture) {
|
||||
pipelineDepthStencilFormat = dsTexture->getTexture().GetFormat();
|
||||
const auto depthStencilTexture{ handleCast<WebGPUTexture>(depthStencilHandle) };
|
||||
if (depthStencilTexture) {
|
||||
depthStencilFormat = depthStencilTexture->getTexture().GetFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: We expected this to be a sane check, however it complains when running shadowtest.
|
||||
//if (program->fragmentShaderModule != nullptr) {
|
||||
// FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
|
||||
// << "Render pipeline with fragment shader must have at least one color target "
|
||||
// "format.";
|
||||
//}
|
||||
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
|
||||
layout, pipelineState.rasterState, pipelineState.stencilState,
|
||||
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
|
||||
pipelineDepthStencilFormat, pipelineSamples, requestedDepth, requestedStencil);
|
||||
assert_invariant(pipeline);
|
||||
mPipelineMap[pipelineKey] = pipeline;
|
||||
const WebGPUPipelineCache::RenderPipelineRequest pipelineRequest{
|
||||
.label = program->name,
|
||||
.vertexShaderModule = program->vertexShaderModule,
|
||||
.fragmentShaderModule = program->fragmentShaderModule,
|
||||
.vertexBufferSlots = vertexBufferInfo->getWebGPUSlotBindingInfos(),
|
||||
.vertexBufferLayouts = vertexBufferInfo->getVertexBufferLayouts(),
|
||||
.pipelineLayout = layout,
|
||||
.primitiveType = pipelineState.primitiveType,
|
||||
.rasterState = pipelineState.rasterState,
|
||||
.stencilState = pipelineState.stencilState,
|
||||
.polygonOffset = pipelineState.polygonOffset,
|
||||
.targetRenderFlags = renderTarget->getTargetFlags(),
|
||||
.multisampleCount = renderTarget->getSamples(),
|
||||
.depthStencilFormat = depthStencilFormat,
|
||||
.colorFormatCount = colorFormatCount,
|
||||
.colorFormats = colorFormats.data(),
|
||||
};
|
||||
wgpu::RenderPipeline const& pipeline{ mPipelineCache.getOrCreateRenderPipeline(
|
||||
pipelineRequest) };
|
||||
mRenderPassEncoder.SetPipeline(pipeline);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include "webgpu/WebGPUMsaaTextureResolver.h"
|
||||
#include "webgpu/WebGPUPipelineCache.h"
|
||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
@@ -32,7 +34,6 @@
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -79,12 +80,12 @@ private:
|
||||
wgpu::CommandBuffer mCommandBuffer = nullptr;
|
||||
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
|
||||
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
|
||||
WebGPUPipelineLayoutCache mPipelineLayoutCache;
|
||||
WebGPUPipelineCache mPipelineCache;
|
||||
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
|
||||
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
|
||||
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
|
||||
|
||||
tsl::robin_map<size_t, wgpu::RenderPipeline> mPipelineMap;
|
||||
|
||||
struct DescriptorSetBindingInfo{
|
||||
wgpu::BindGroup bindGroup;
|
||||
size_t offsetCount;
|
||||
@@ -92,8 +93,6 @@ private:
|
||||
};
|
||||
std::array<DescriptorSetBindingInfo,MAX_DESCRIPTOR_SET_COUNT> mCurrentDescriptorSets;
|
||||
|
||||
[[nodiscard]] size_t computePipelineKey(PipelineState const&, WebGPURenderTarget const*) const;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
|
||||
382
filament/backend/src/webgpu/WebGPUPipelineCache.cpp
Normal file
382
filament/backend/src/webgpu/WebGPUPipelineCache.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUPipelineCache.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
#include "WebGPUTexture.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr uint8_t toUint8(const bool value) { return value ? 1 : 0; }
|
||||
|
||||
[[nodiscard]] constexpr wgpu::PrimitiveTopology toWebGPU(const PrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case PrimitiveType::POINTS: return wgpu::PrimitiveTopology::PointList;
|
||||
case PrimitiveType::LINES: return wgpu::PrimitiveTopology::LineList;
|
||||
case PrimitiveType::LINE_STRIP: return wgpu::PrimitiveTopology::LineStrip;
|
||||
case PrimitiveType::TRIANGLES: return wgpu::PrimitiveTopology::TriangleList;
|
||||
case PrimitiveType::TRIANGLE_STRIP: return wgpu::PrimitiveTopology::TriangleStrip;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::CullMode toWebGPU(const CullingMode cullMode) {
|
||||
switch (cullMode) {
|
||||
case CullingMode::NONE: return wgpu::CullMode::None;
|
||||
case CullingMode::FRONT: return wgpu::CullMode::Front;
|
||||
case CullingMode::BACK: return wgpu::CullMode::Back;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
// no WegGPU equivalent of front and back
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::CompareFunction toWebGPU(const SamplerCompareFunc compareFunction) {
|
||||
switch (compareFunction) {
|
||||
case SamplerCompareFunc::LE: return wgpu::CompareFunction::LessEqual;
|
||||
case SamplerCompareFunc::GE: return wgpu::CompareFunction::GreaterEqual;
|
||||
case SamplerCompareFunc::L: return wgpu::CompareFunction::Less;
|
||||
case SamplerCompareFunc::G: return wgpu::CompareFunction::Greater;
|
||||
case SamplerCompareFunc::E: return wgpu::CompareFunction::Equal;
|
||||
case SamplerCompareFunc::NE: return wgpu::CompareFunction::NotEqual;
|
||||
case SamplerCompareFunc::A: return wgpu::CompareFunction::Always;
|
||||
case SamplerCompareFunc::N: return wgpu::CompareFunction::Never;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::StencilOperation toWebGPU(const StencilOperation stencilOp) {
|
||||
switch (stencilOp) {
|
||||
case StencilOperation::KEEP: return wgpu::StencilOperation::Keep;
|
||||
case StencilOperation::ZERO: return wgpu::StencilOperation::Zero;
|
||||
case StencilOperation::REPLACE: return wgpu::StencilOperation::Replace;
|
||||
case StencilOperation::INCR: return wgpu::StencilOperation::IncrementClamp;
|
||||
case StencilOperation::INCR_WRAP: return wgpu::StencilOperation::IncrementWrap;
|
||||
case StencilOperation::DECR: return wgpu::StencilOperation::DecrementClamp;
|
||||
case StencilOperation::DECR_WRAP: return wgpu::StencilOperation::DecrementWrap;
|
||||
case StencilOperation::INVERT: return wgpu::StencilOperation::Invert;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::BlendOperation toWebGPU(const BlendEquation blendOp) {
|
||||
switch (blendOp) {
|
||||
case BlendEquation::ADD: return wgpu::BlendOperation::Add;
|
||||
case BlendEquation::SUBTRACT: return wgpu::BlendOperation::Subtract;
|
||||
case BlendEquation::REVERSE_SUBTRACT: return wgpu::BlendOperation::ReverseSubtract;
|
||||
case BlendEquation::MIN: return wgpu::BlendOperation::Min;
|
||||
case BlendEquation::MAX: return wgpu::BlendOperation::Max;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr wgpu::BlendFactor toWebGPU(const BlendFunction blendFunction) {
|
||||
switch (blendFunction) {
|
||||
case BlendFunction::ZERO: return wgpu::BlendFactor::Zero;
|
||||
case BlendFunction::ONE: return wgpu::BlendFactor::One;
|
||||
case BlendFunction::SRC_COLOR: return wgpu::BlendFactor::Src;
|
||||
case BlendFunction::ONE_MINUS_SRC_COLOR: return wgpu::BlendFactor::OneMinusSrc;
|
||||
case BlendFunction::DST_COLOR: return wgpu::BlendFactor::Dst;
|
||||
case BlendFunction::ONE_MINUS_DST_COLOR: return wgpu::BlendFactor::OneMinusDst;
|
||||
case BlendFunction::SRC_ALPHA: return wgpu::BlendFactor::SrcAlpha;
|
||||
case BlendFunction::ONE_MINUS_SRC_ALPHA: return wgpu::BlendFactor::OneMinusSrcAlpha;
|
||||
case BlendFunction::DST_ALPHA: return wgpu::BlendFactor::DstAlpha;
|
||||
case BlendFunction::ONE_MINUS_DST_ALPHA: return wgpu::BlendFactor::OneMinusDstAlpha;
|
||||
case BlendFunction::SRC_ALPHA_SATURATE: return wgpu::BlendFactor::SrcAlphaSaturated;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WebGPUPipelineCache::WebGPUPipelineCache(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
wgpu::RenderPipeline const& WebGPUPipelineCache::getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const& request) {
|
||||
RenderPipelineKey key{};
|
||||
populateKey(request, key);
|
||||
if (auto iterator{ mRenderPipelines.find(key) }; iterator != mRenderPipelines.end()) {
|
||||
RenderPipelineCacheEntry& entry{ iterator.value() };
|
||||
entry.lastUsedFrameCount = mFrameCount;
|
||||
return entry.pipeline;
|
||||
}
|
||||
const wgpu::RenderPipeline pipeline{ createRenderPipeline(request) };
|
||||
mRenderPipelines.emplace(key, RenderPipelineCacheEntry{
|
||||
.pipeline = pipeline,
|
||||
.lastUsedFrameCount = mFrameCount,
|
||||
});
|
||||
return mRenderPipelines[key].pipeline;
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::onFrameEnd() {
|
||||
++mFrameCount;
|
||||
removeExpiredPipelines();
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::populateKey(RenderPipelineRequest const& request,
|
||||
RenderPipelineKey& outKey) {
|
||||
outKey.vertexShaderModuleHandle =
|
||||
request.vertexShaderModule ? request.vertexShaderModule.Get() : nullptr;
|
||||
outKey.fragmentShaderModuleHandle =
|
||||
request.fragmentShaderModule ? request.fragmentShaderModule.Get() : nullptr;
|
||||
outKey.pipelineLayoutHandle = request.pipelineLayout ? request.pipelineLayout.Get() : nullptr;
|
||||
outKey.depthBias = static_cast<int32_t>(request.polygonOffset.constant);
|
||||
outKey.depthBiasSlopeScale = request.polygonOffset.slope;
|
||||
outKey.primitiveType = request.primitiveType;
|
||||
outKey.stencilFrontCompare = request.stencilState.front.stencilFunc;
|
||||
outKey.stencilFrontFailOperation = request.stencilState.front.stencilOpStencilFail;
|
||||
outKey.stencilFrontDepthFailOperation = request.stencilState.front.stencilOpDepthFail;
|
||||
outKey.stencilFrontPassOperation = request.stencilState.front.stencilOpDepthStencilPass;
|
||||
outKey.stencilWrite = toUint8(request.stencilState.stencilWrite);
|
||||
outKey.stencilFrontReadMask = request.stencilState.front.readMask;
|
||||
outKey.stencilFrontWriteMask = request.stencilState.front.writeMask;
|
||||
outKey.stencilBackCompare = request.stencilState.back.stencilFunc;
|
||||
outKey.stencilBackFailOperation = request.stencilState.back.stencilOpStencilFail;
|
||||
outKey.stencilBackDepthFailOperation = request.stencilState.back.stencilOpDepthFail;
|
||||
outKey.stencilBackPassOperation = request.stencilState.back.stencilOpDepthStencilPass;
|
||||
outKey.cullingMode = request.rasterState.culling;
|
||||
outKey.inverseFrontFaces = toUint8(request.rasterState.inverseFrontFaces);
|
||||
outKey.depthWriteEnabled = toUint8(request.rasterState.depthWrite);
|
||||
outKey.depthCompare = request.rasterState.depthFunc;
|
||||
outKey.depthClamp = toUint8(request.rasterState.depthClamp);
|
||||
outKey.colorWrite = toUint8(request.rasterState.colorWrite);
|
||||
outKey.alphaToCoverageEnabled = toUint8(request.rasterState.alphaToCoverage);
|
||||
outKey.colorBlendOperation = request.rasterState.blendEquationRGB;
|
||||
outKey.colorBlendSourceFactor = request.rasterState.blendFunctionSrcRGB;
|
||||
outKey.colorBlendDestinationFactor = request.rasterState.blendFunctionDstRGB;
|
||||
outKey.alphaBlendOperation = request.rasterState.blendEquationAlpha;
|
||||
outKey.alphaBlendSourceFactor = request.rasterState.blendFunctionSrcAlpha;
|
||||
outKey.alphaBlendDestinationFactor = request.rasterState.blendFunctionDstAlpha;
|
||||
outKey.targetRenderFlags = request.targetRenderFlags;
|
||||
outKey.multisampleCount = request.multisampleCount;
|
||||
outKey.depthStencilFormat = request.depthStencilFormat;
|
||||
assert_invariant(request.colorFormatCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
outKey.colorFormatCount = request.colorFormatCount;
|
||||
for (size_t colorIndex{ 0 }; colorIndex < request.colorFormatCount; colorIndex++) {
|
||||
outKey.colorFormats[colorIndex] = request.colorFormats[colorIndex];
|
||||
}
|
||||
// vertex buffers...
|
||||
for (WebGPUVertexBufferInfo::WebGPUSlotBindingInfo const& vertexBufferSlot:
|
||||
request.vertexBufferSlots) {
|
||||
assert_invariant(vertexBufferSlot.sourceBufferIndex < MAX_VERTEX_BUFFER_COUNT);
|
||||
outKey.vertexBuffers[vertexBufferSlot.sourceBufferIndex].stride = vertexBufferSlot.stride;
|
||||
outKey.vertexBuffers[vertexBufferSlot.sourceBufferIndex].offset =
|
||||
vertexBufferSlot.bufferOffset;
|
||||
}
|
||||
// vertex attributes...
|
||||
uint8_t currentAttributeIndex{ 0 };
|
||||
for (size_t bufferIndex{ 0 }; bufferIndex < request.vertexBufferSlots.size(); bufferIndex++) {
|
||||
wgpu::VertexBufferLayout const& vertexBufferLayout{
|
||||
request.vertexBufferLayouts[bufferIndex]
|
||||
};
|
||||
for (size_t attributeIndex{ 0 }; attributeIndex < vertexBufferLayout.attributeCount;
|
||||
attributeIndex++) {
|
||||
assert_invariant(attributeIndex < MAX_VERTEX_ATTRIBUTE_COUNT);
|
||||
wgpu::VertexAttribute const& vertexAttribute{
|
||||
vertexBufferLayout.attributes[attributeIndex]
|
||||
};
|
||||
outKey.vertexAttributes[currentAttributeIndex].bufferIndex = bufferIndex;
|
||||
outKey.vertexAttributes[currentAttributeIndex].offset =
|
||||
static_cast<uint8_t>(vertexAttribute.offset);
|
||||
outKey.vertexAttributes[currentAttributeIndex].shaderLocation =
|
||||
static_cast<uint8_t>(vertexAttribute.shaderLocation);
|
||||
outKey.vertexAttributes[currentAttributeIndex].format = vertexAttribute.format;
|
||||
currentAttributeIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::RenderPipeline WebGPUPipelineCache::createRenderPipeline(
|
||||
RenderPipelineRequest const& request) {
|
||||
assert_invariant(request.vertexShaderModule);
|
||||
wgpu::DepthStencilState depthStencilState{};
|
||||
const bool requestedDepth{ any(request.targetRenderFlags & TargetBufferFlags::DEPTH) };
|
||||
const bool requestedStencil{ any(request.targetRenderFlags & TargetBufferFlags::STENCIL) };
|
||||
const bool depthOrStencilRequested{ requestedDepth || requestedStencil };
|
||||
// depth/stencil...
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(request.depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
"wgpu::TextureFormat::Undefined.";
|
||||
depthStencilState.format = request.depthStencilFormat;
|
||||
if (requestedDepth) {
|
||||
assert_invariant(hasDepth(depthStencilState.format));
|
||||
depthStencilState.depthWriteEnabled = request.rasterState.depthWrite;
|
||||
depthStencilState.depthCompare = toWebGPU(request.rasterState.depthFunc);
|
||||
depthStencilState.depthBias = static_cast<int32_t>(request.polygonOffset.constant);
|
||||
depthStencilState.depthBiasSlopeScale = request.polygonOffset.slope;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
} else {
|
||||
depthStencilState.depthWriteEnabled = false;
|
||||
depthStencilState.depthCompare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.depthBias = 0;
|
||||
depthStencilState.depthBiasSlopeScale = 0.0f;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
}
|
||||
if (requestedStencil) {
|
||||
assert_invariant(hasStencil(depthStencilState.format));
|
||||
depthStencilState.stencilFront = {
|
||||
.compare = toWebGPU(request.stencilState.front.stencilFunc),
|
||||
.failOp = toWebGPU(request.stencilState.front.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(request.stencilState.front.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(request.stencilState.front.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilBack = {
|
||||
.compare = toWebGPU(request.stencilState.back.stencilFunc),
|
||||
.failOp = toWebGPU(request.stencilState.back.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(request.stencilState.back.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(request.stencilState.back.stencilOpDepthStencilPass),
|
||||
};
|
||||
// TODO: should we also consider the back readMask and writeMask?
|
||||
depthStencilState.stencilReadMask = request.stencilState.front.readMask;
|
||||
depthStencilState.stencilWriteMask =
|
||||
request.stencilState.stencilWrite ? request.stencilState.front.writeMask : 0u;
|
||||
} else {
|
||||
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.stencilFront.failOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.passOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilBack = depthStencilState.stencilFront;
|
||||
depthStencilState.stencilReadMask = 0;
|
||||
depthStencilState.stencilWriteMask = 0;
|
||||
}
|
||||
}
|
||||
wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
||||
.label = wgpu::StringView(request.label.c_str_safe()),
|
||||
.layout = request.pipelineLayout,
|
||||
.vertex = {
|
||||
.module = request.vertexShaderModule,
|
||||
.entryPoint = "main",
|
||||
// we do not use WebGPU's override constants due to 2 limitations
|
||||
// (at least at the time of write this):
|
||||
// 1. they cannot be used for the size of an array, which is needed
|
||||
// 2. if we pass the WebGPU API (CPU-side) constants not referenced in the
|
||||
// shader WebGPU fails. This is a problem with how Filament is designed,
|
||||
// where certain constants may be optimized out of the shader based
|
||||
// on build configuration, etc.
|
||||
//
|
||||
// to bypass these problems, we do not use override constants in the
|
||||
// WebGPU backend, instead replacing placeholder constants in the shader
|
||||
// text before creating the shader module (essentially implementing
|
||||
// override constants ourselves)
|
||||
.constantCount = 0,
|
||||
.constants = nullptr,
|
||||
.bufferCount = request.vertexBufferSlots.size(),
|
||||
.buffers = request.vertexBufferLayouts,
|
||||
},
|
||||
.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,
|
||||
.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
|
||||
// starting there
|
||||
.unclippedDepth = !request.rasterState.depthClamp &&
|
||||
mDevice.HasFeature(wgpu::FeatureName::DepthClipControl),
|
||||
},
|
||||
.depthStencil = depthOrStencilRequested ? &depthStencilState: nullptr,
|
||||
.multisample = {
|
||||
.count = request.multisampleCount,
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = (request.multisampleCount > 1) && request.rasterState.alphaToCoverage
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
wgpu::FragmentState fragmentState = {};
|
||||
const wgpu::BlendState blendState {
|
||||
.color = {
|
||||
.operation = toWebGPU(request.rasterState.blendEquationRGB),
|
||||
.srcFactor = toWebGPU(request.rasterState.blendFunctionSrcRGB),
|
||||
.dstFactor = toWebGPU(request.rasterState.blendFunctionDstRGB)
|
||||
},
|
||||
.alpha = {
|
||||
.operation = toWebGPU(request.rasterState.blendEquationAlpha),
|
||||
.srcFactor = toWebGPU(request.rasterState.blendFunctionSrcAlpha),
|
||||
.dstFactor = toWebGPU(request.rasterState.blendFunctionDstAlpha)
|
||||
}
|
||||
};
|
||||
// According to the WebGPU spec, a pipeline cannot have a fragment stage with zero color
|
||||
// targets. This situation can arise in Filament during depth-only passes (like shadow map
|
||||
// generation) if the material variant still includes a fragment shader.
|
||||
//
|
||||
// To handle this, we check if any color targets are configured for this pipeline. If not, we
|
||||
// create a pipeline *without* a fragment stage. This makes the pipeline valid for a
|
||||
// depth-only pass, allowing depth writes to proceed correctly.
|
||||
if (request.fragmentShaderModule != nullptr && request.colorFormatCount > 0) {
|
||||
fragmentState.module = request.fragmentShaderModule;
|
||||
fragmentState.entryPoint = "main";
|
||||
// see the comment about constants for the vertex state, as the same reasoning applies
|
||||
// here
|
||||
fragmentState.constantCount = 0;
|
||||
fragmentState.constants = nullptr;
|
||||
fragmentState.targetCount = request.colorFormatCount;
|
||||
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
|
||||
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
|
||||
wgpu::ColorTargetState& colorTarget = colorTargets[targetIndex];
|
||||
colorTarget.format = request.colorFormats[targetIndex];
|
||||
colorTarget.blend = request.rasterState.hasBlending() ? &blendState : nullptr;
|
||||
colorTarget.writeMask = request.rasterState.colorWrite ? wgpu::ColorWriteMask::All
|
||||
: wgpu::ColorWriteMask::None;
|
||||
}
|
||||
fragmentState.targets = colorTargets.data();
|
||||
pipelineDescriptor.fragment = &fragmentState;
|
||||
}
|
||||
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
bool WebGPUPipelineCache::RenderPipelineKeyEqual::operator()(RenderPipelineKey const& key1,
|
||||
RenderPipelineKey const& key2) const {
|
||||
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
|
||||
sizeof(key1));
|
||||
}
|
||||
|
||||
void WebGPUPipelineCache::removeExpiredPipelines() {
|
||||
using Iterator = decltype(mRenderPipelines)::const_iterator;
|
||||
for (Iterator iterator{ mRenderPipelines.begin() }; iterator != mRenderPipelines.end();) {
|
||||
RenderPipelineCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_RENDER_PIPELINE_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// pipeline expired...
|
||||
iterator = mRenderPipelines.erase(iterator);
|
||||
} else {
|
||||
// pipeline not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace filament::backend
|
||||
192
filament/backend/src/webgpu/WebGPUPipelineCache.h
Normal file
192
filament/backend/src/webgpu/WebGPUPipelineCache.h
Normal file
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
|
||||
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUPipelineCache final {
|
||||
public:
|
||||
struct RenderPipelineRequest final {
|
||||
utils::CString const& label;
|
||||
wgpu::ShaderModule const& vertexShaderModule;
|
||||
wgpu::ShaderModule const& fragmentShaderModule;
|
||||
std::vector<WebGPUVertexBufferInfo::WebGPUSlotBindingInfo> const& vertexBufferSlots;
|
||||
wgpu::VertexBufferLayout const* vertexBufferLayouts;
|
||||
wgpu::PipelineLayout const& pipelineLayout;
|
||||
const PrimitiveType primitiveType;
|
||||
RasterState const& rasterState;
|
||||
StencilState const& stencilState;
|
||||
PolygonOffset const& polygonOffset;
|
||||
const TargetBufferFlags targetRenderFlags;
|
||||
const uint8_t multisampleCount;
|
||||
const wgpu::TextureFormat depthStencilFormat;
|
||||
const uint8_t colorFormatCount;
|
||||
wgpu::TextureFormat const* colorFormats;
|
||||
};
|
||||
|
||||
explicit WebGPUPipelineCache(wgpu::Device const&);
|
||||
WebGPUPipelineCache(WebGPUPipelineCache const&) = delete;
|
||||
WebGPUPipelineCache(WebGPUPipelineCache const&&) = delete;
|
||||
WebGPUPipelineCache& operator=(WebGPUPipelineCache const&) = delete;
|
||||
WebGPUPipelineCache& operator=(WebGPUPipelineCache const&&) = delete;
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipeline(
|
||||
RenderPipelineRequest const&);
|
||||
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Part of the pipeline key specifically about one of the vertex attributes
|
||||
*/
|
||||
struct VertexAttribute final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
uint8_t bufferIndex{ 0 }; // 1 : 0
|
||||
// this is the webgpu offset, //
|
||||
// bytes from the start of //
|
||||
// the vertex data //
|
||||
// (interleaved offset) //
|
||||
uint8_t offset{ 0 }; // 1 : 1
|
||||
uint8_t shaderLocation{ 0 }; // 1 : 2
|
||||
uint8_t padding { 0 }; // 1 : 3
|
||||
wgpu::VertexFormat format{ 0 }; // 4 : 4
|
||||
};
|
||||
static_assert(sizeof(VertexAttribute) == 8, "VertexAttribute must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<VertexAttribute>::value,
|
||||
"VertexAttribute must be a trivially copyable POD for fast hashing.");
|
||||
/**
|
||||
* Part of the pipeline key specifically about one of the vertex buffers
|
||||
*/
|
||||
struct VertexBuffer final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
uint8_t stride{ 0 }; // 1 : 0
|
||||
uint8_t padding[3]{ 0 }; // 3 : 1
|
||||
// offset in bytes from //
|
||||
// the start of the //
|
||||
// (physical) GPU buffer //
|
||||
// (not logical buffer //
|
||||
// partition) //
|
||||
uint32_t offset{ 0 }; // 4 : 4
|
||||
};
|
||||
static_assert(sizeof(VertexBuffer) == 8, "VertexBuffer must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<VertexBuffer>::value,
|
||||
"VertexAttribute must be a trivially copyable POD for fast hashing.");
|
||||
/**
|
||||
* Key designed for efficient hashing and uniquely identifying all the parameters for
|
||||
* creating a render pipeline.
|
||||
* The efficient hashing requires a small memory footprint
|
||||
* (using the smallest representations of enums, just handle instances instead of wrapper class
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct RenderPipelineKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
// shaders... //
|
||||
WGPUShaderModule vertexShaderModuleHandle{ nullptr }; // 8 : 0
|
||||
WGPUShaderModule fragmentShaderModuleHandle{ nullptr }; // 8 : 8
|
||||
// vertex attributes... //
|
||||
VertexAttribute vertexAttributes[MAX_VERTEX_ATTRIBUTE_COUNT]{}; // 128 : 16
|
||||
VertexBuffer vertexBuffers[MAX_VERTEX_BUFFER_COUNT]{}; // 128 : 144
|
||||
// pipeline layout... //
|
||||
WGPUPipelineLayout pipelineLayoutHandle{ nullptr }; // 8 : 272
|
||||
// general settings... //
|
||||
int32_t depthBias{ 0 }; // 4 : 280
|
||||
float depthBiasSlopeScale { 0.0f }; // 4 : 284
|
||||
PrimitiveType primitiveType{ PrimitiveType::POINTS }; // 1 : 288
|
||||
// stencil state... //
|
||||
SamplerCompareFunc stencilFrontCompare{ SamplerCompareFunc::LE }; // 1 : 289
|
||||
StencilOperation stencilFrontFailOperation{ StencilOperation::KEEP }; // 1 : 290
|
||||
StencilOperation stencilFrontDepthFailOperation{ StencilOperation::KEEP }; // 1 : 291
|
||||
StencilOperation stencilFrontPassOperation{ StencilOperation::KEEP }; // 1 : 292
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t stencilWrite{ 0 }; // 1 : 293
|
||||
uint8_t stencilFrontReadMask{ 0 }; // 1 : 294
|
||||
uint8_t stencilFrontWriteMask{ 0 }; // 1 : 295
|
||||
SamplerCompareFunc stencilBackCompare{ SamplerCompareFunc::LE }; // 1 : 296
|
||||
StencilOperation stencilBackFailOperation{ StencilOperation::KEEP }; // 1 : 297
|
||||
StencilOperation stencilBackDepthFailOperation{ StencilOperation::KEEP }; // 1 : 298
|
||||
StencilOperation stencilBackPassOperation{ StencilOperation::KEEP }; // 1 : 299
|
||||
// general rasterization settings... //
|
||||
CullingMode cullingMode{ CullingMode::NONE }; // 1 : 300
|
||||
/* bool, 0 -> counter-clockwise, 1 -> clockwise */ uint8_t inverseFrontFaces{ 0 }; // 1 : 301
|
||||
// rasterization depth state... //
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t depthWriteEnabled{ 0 }; // 1 : 302
|
||||
SamplerCompareFunc depthCompare{ SamplerCompareFunc::LE }; // 1 : 303
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t depthClamp{ 0 }; // 1 : 304
|
||||
// more rasterization flags... //
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t colorWrite{ 0 }; // 1 : 305
|
||||
/* bool, 0 -> false, 1 -> true */ uint8_t alphaToCoverageEnabled{ 0 }; // 1 : 306
|
||||
// color blending... //
|
||||
BlendEquation colorBlendOperation{ BlendEquation::ADD }; // 1 : 307
|
||||
BlendFunction colorBlendSourceFactor{ BlendFunction::ZERO }; // 1 : 308
|
||||
BlendFunction colorBlendDestinationFactor{ BlendFunction::ZERO }; // 1 : 309
|
||||
// alpha blending... //
|
||||
BlendEquation alphaBlendOperation{ BlendEquation::ADD }; // 1 : 310
|
||||
BlendFunction alphaBlendSourceFactor{ BlendFunction::ZERO }; // 1 : 311
|
||||
BlendFunction alphaBlendDestinationFactor{ BlendFunction::ZERO }; // 1 : 312
|
||||
// render targets... //
|
||||
uint8_t multisampleCount{ 0 }; // 1 : 313
|
||||
uint8_t colorFormatCount{ 0 }; // 1 : 314
|
||||
uint8_t padding[5]{ 0 }; // 5 : 319
|
||||
TargetBufferFlags targetRenderFlags{ TargetBufferFlags::NONE }; // 4 : 320
|
||||
wgpu::TextureFormat depthStencilFormat { wgpu::TextureFormat::Undefined }; // 4 : 324
|
||||
wgpu::TextureFormat colorFormats[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]{ //
|
||||
wgpu::TextureFormat::Undefined //
|
||||
}; // 32 : 328
|
||||
};
|
||||
static_assert(sizeof(RenderPipelineKey) == 360,
|
||||
"RenderPipelineKey must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<RenderPipelineKey>::value,
|
||||
"RenderPipelineKey must be a trivially copyable POD for fast hashing.");
|
||||
|
||||
struct RenderPipelineKeyEqual {
|
||||
bool operator()(RenderPipelineKey const&, RenderPipelineKey const&) const;
|
||||
};
|
||||
|
||||
struct RenderPipelineCacheEntry final {
|
||||
wgpu::RenderPipeline pipeline{ nullptr };
|
||||
uint64_t lastUsedFrameCount{ 0 };
|
||||
};
|
||||
|
||||
static void populateKey(RenderPipelineRequest const&, RenderPipelineKey& outKey);
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline createRenderPipeline(RenderPipelineRequest const&);
|
||||
|
||||
void removeExpiredPipelines();
|
||||
|
||||
wgpu::Device mDevice;
|
||||
tsl::robin_map<RenderPipelineKey, RenderPipelineCacheEntry,
|
||||
utils::hash::MurmurHashFn<RenderPipelineKey>, RenderPipelineKeyEqual>
|
||||
mRenderPipelines{};
|
||||
uint64_t mFrameCount{ 0 };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUPIPELINECACHE_H
|
||||
@@ -1,312 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUPipelineCreation.h"
|
||||
|
||||
#include "WebGPUProgram.h"
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr wgpu::PrimitiveTopology toWebGPU(PrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case PrimitiveType::POINTS:
|
||||
return wgpu::PrimitiveTopology::PointList;
|
||||
case PrimitiveType::LINES:
|
||||
return wgpu::PrimitiveTopology::LineList;
|
||||
case PrimitiveType::LINE_STRIP:
|
||||
return wgpu::PrimitiveTopology::LineStrip;
|
||||
case PrimitiveType::TRIANGLES:
|
||||
return wgpu::PrimitiveTopology::TriangleList;
|
||||
case PrimitiveType::TRIANGLE_STRIP:
|
||||
return wgpu::PrimitiveTopology::TriangleStrip;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::CullMode toWebGPU(CullingMode cullMode) {
|
||||
switch (cullMode) {
|
||||
case CullingMode::NONE:
|
||||
return wgpu::CullMode::None;
|
||||
case CullingMode::FRONT:
|
||||
return wgpu::CullMode::Front;
|
||||
case CullingMode::BACK:
|
||||
return wgpu::CullMode::Back;
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
// no WegGPU equivalent of front and back
|
||||
FILAMENT_CHECK_POSTCONDITION(false)
|
||||
<< "WebGPU does not support CullingMode::FRONT_AND_BACK";
|
||||
return wgpu::CullMode::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasStencilAspect(wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::Stencil8:
|
||||
case wgpu::TextureFormat::Depth24PlusStencil8:
|
||||
case wgpu::TextureFormat::Depth32FloatStencil8:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::CompareFunction toWebGPU(SamplerCompareFunc compareFunction) {
|
||||
switch (compareFunction) {
|
||||
case SamplerCompareFunc::LE:
|
||||
return wgpu::CompareFunction::LessEqual;
|
||||
case SamplerCompareFunc::GE:
|
||||
return wgpu::CompareFunction::GreaterEqual;
|
||||
case SamplerCompareFunc::L:
|
||||
return wgpu::CompareFunction::Less;
|
||||
case SamplerCompareFunc::G:
|
||||
return wgpu::CompareFunction::Greater;
|
||||
case SamplerCompareFunc::E:
|
||||
return wgpu::CompareFunction::Equal;
|
||||
case SamplerCompareFunc::NE:
|
||||
return wgpu::CompareFunction::NotEqual;
|
||||
case SamplerCompareFunc::A:
|
||||
return wgpu::CompareFunction::Always;
|
||||
case SamplerCompareFunc::N:
|
||||
return wgpu::CompareFunction::Never;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::StencilOperation toWebGPU(StencilOperation stencilOp) {
|
||||
switch (stencilOp) {
|
||||
case StencilOperation::KEEP:
|
||||
return wgpu::StencilOperation::Keep;
|
||||
case StencilOperation::ZERO:
|
||||
return wgpu::StencilOperation::Zero;
|
||||
case StencilOperation::REPLACE:
|
||||
return wgpu::StencilOperation::Replace;
|
||||
case StencilOperation::INCR:
|
||||
return wgpu::StencilOperation::IncrementClamp;
|
||||
case StencilOperation::INCR_WRAP:
|
||||
return wgpu::StencilOperation::IncrementWrap;
|
||||
case StencilOperation::DECR:
|
||||
return wgpu::StencilOperation::DecrementClamp;
|
||||
case StencilOperation::DECR_WRAP:
|
||||
return wgpu::StencilOperation::DecrementWrap;
|
||||
case StencilOperation::INVERT:
|
||||
return wgpu::StencilOperation::Invert;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::BlendOperation toWebGPU(BlendEquation blendOp) {
|
||||
switch (blendOp) {
|
||||
case BlendEquation::ADD:
|
||||
return wgpu::BlendOperation::Add;
|
||||
case BlendEquation::SUBTRACT:
|
||||
return wgpu::BlendOperation::Subtract;
|
||||
case BlendEquation::REVERSE_SUBTRACT:
|
||||
return wgpu::BlendOperation::ReverseSubtract;
|
||||
case BlendEquation::MIN:
|
||||
return wgpu::BlendOperation::Min;
|
||||
case BlendEquation::MAX:
|
||||
return wgpu::BlendOperation::Max;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) {
|
||||
switch (blendFunction) {
|
||||
case BlendFunction::ZERO:
|
||||
return wgpu::BlendFactor::Zero;
|
||||
case BlendFunction::ONE:
|
||||
return wgpu::BlendFactor::One;
|
||||
case BlendFunction::SRC_COLOR:
|
||||
return wgpu::BlendFactor::Src;
|
||||
case BlendFunction::ONE_MINUS_SRC_COLOR:
|
||||
return wgpu::BlendFactor::OneMinusSrc;
|
||||
case BlendFunction::DST_COLOR:
|
||||
return wgpu::BlendFactor::Dst;
|
||||
case BlendFunction::ONE_MINUS_DST_COLOR:
|
||||
return wgpu::BlendFactor::OneMinusDst;
|
||||
case BlendFunction::SRC_ALPHA:
|
||||
return wgpu::BlendFactor::SrcAlpha;
|
||||
case BlendFunction::ONE_MINUS_SRC_ALPHA:
|
||||
return wgpu::BlendFactor::OneMinusSrcAlpha;
|
||||
case BlendFunction::DST_ALPHA:
|
||||
return wgpu::BlendFactor::DstAlpha;
|
||||
case BlendFunction::ONE_MINUS_DST_ALPHA:
|
||||
return wgpu::BlendFactor::OneMinusDstAlpha;
|
||||
case BlendFunction::SRC_ALPHA_SATURATE:
|
||||
return wgpu::BlendFactor::SrcAlphaSaturated;
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
|
||||
WebGPUProgram const& program, WebGPUVertexBufferInfo const& vertexBufferInfo,
|
||||
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
|
||||
StencilState const& stencilState, PolygonOffset const& polygonOffset,
|
||||
const PrimitiveType primitiveType, std::vector<wgpu::TextureFormat> const& colorFormats,
|
||||
wgpu::TextureFormat const& depthStencilFormat, const uint8_t samplesCount, const bool requestedDepth, const bool requestedStencil) {
|
||||
assert_invariant(program.vertexShaderModule);
|
||||
wgpu::DepthStencilState depthStencilState{};
|
||||
const bool depthOrStencilRequested = (requestedDepth || requestedStencil);
|
||||
|
||||
if (depthOrStencilRequested) {
|
||||
FILAMENT_CHECK_PRECONDITION(depthStencilFormat != wgpu::TextureFormat::Undefined)
|
||||
<< "Depth or Stencil requested for pipeline, but depthStencilFormat is "
|
||||
"wgpu::TextureFormat::Undefined.";
|
||||
depthStencilState.format = depthStencilFormat;
|
||||
|
||||
if (requestedDepth) {
|
||||
depthStencilState.depthWriteEnabled = rasterState.depthWrite;
|
||||
depthStencilState.depthCompare = toWebGPU(rasterState.depthFunc);
|
||||
depthStencilState.depthBias = static_cast<int32_t>(polygonOffset.constant);
|
||||
depthStencilState.depthBiasSlopeScale = polygonOffset.slope;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
} else {
|
||||
depthStencilState.depthWriteEnabled = false;
|
||||
depthStencilState.depthCompare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.depthBias = 0;
|
||||
depthStencilState.depthBiasSlopeScale = 0.0f;
|
||||
depthStencilState.depthBiasClamp = 0.0f;
|
||||
}
|
||||
|
||||
if (hasStencilAspect(depthStencilFormat) && requestedStencil) {
|
||||
depthStencilState.stencilFront = {
|
||||
.compare = toWebGPU(stencilState.front.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.front.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.front.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.front.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilBack = {
|
||||
.compare = toWebGPU(stencilState.back.stencilFunc),
|
||||
.failOp = toWebGPU(stencilState.back.stencilOpStencilFail),
|
||||
.depthFailOp = toWebGPU(stencilState.back.stencilOpDepthFail),
|
||||
.passOp = toWebGPU(stencilState.back.stencilOpDepthStencilPass),
|
||||
};
|
||||
depthStencilState.stencilReadMask = stencilState.front.readMask;
|
||||
depthStencilState.stencilWriteMask = stencilState.stencilWrite ? stencilState.front.writeMask : 0u;
|
||||
} else {
|
||||
depthStencilState.stencilFront.compare = wgpu::CompareFunction::Undefined;
|
||||
depthStencilState.stencilFront.failOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.depthFailOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilFront.passOp = wgpu::StencilOperation::Keep;
|
||||
depthStencilState.stencilBack = depthStencilState.stencilFront;
|
||||
depthStencilState.stencilReadMask = 0;
|
||||
depthStencilState.stencilWriteMask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream pipelineLabelStream;
|
||||
pipelineLabelStream << program.name.c_str() << " pipeline";
|
||||
const auto pipelineLabel = pipelineLabelStream.str();
|
||||
wgpu::RenderPipelineDescriptor pipelineDescriptor{
|
||||
.label = wgpu::StringView(pipelineLabel),
|
||||
.layout = layout,
|
||||
.vertex = { .module = program.vertexShaderModule,
|
||||
.entryPoint = "main",
|
||||
// we do not use WebGPU's override constants due to 2 limitations
|
||||
// (at least at the time of write this):
|
||||
// 1. they cannot be used for the size of an array, which is needed
|
||||
// 2. if we pass the WebGPU API (CPU-side) constants not referenced in the
|
||||
// shader WebGPU fails. This is a problem with how Filament is designed,
|
||||
// where certain constants may be optimized out of the shader based
|
||||
// on build configuration, etc.
|
||||
//
|
||||
// to bypass these problems, we do not use override constants in the
|
||||
// WebGPU backend, instead replacing placeholder constants in the shader
|
||||
// text before creating the shader module (essentially implementing
|
||||
// override constants ourselves)
|
||||
.constantCount = 0,
|
||||
.constants = nullptr,
|
||||
.bufferCount = vertexBufferInfo.getVertexBufferLayoutCount(),
|
||||
.buffers = vertexBufferInfo.getVertexBufferLayouts()
|
||||
},
|
||||
.primitive = {
|
||||
.topology = toWebGPU(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,
|
||||
.frontFace = rasterState.inverseFrontFaces ? wgpu::FrontFace::CW : wgpu::FrontFace::CCW,
|
||||
.cullMode = toWebGPU(rasterState.culling),
|
||||
// TODO no depth clamp in WebGPU supported directly. unclippedDepth is close, so we are
|
||||
// starting there
|
||||
.unclippedDepth = !rasterState.depthClamp &&
|
||||
device.HasFeature(wgpu::FeatureName::DepthClipControl)
|
||||
},
|
||||
.depthStencil = depthOrStencilRequested ? &depthStencilState: nullptr,
|
||||
.multisample = {
|
||||
.count = samplesCount,
|
||||
.mask = 0xFFFFFFFF,
|
||||
.alphaToCoverageEnabled = (samplesCount > 1) && rasterState.alphaToCoverage
|
||||
},
|
||||
.fragment = nullptr // will add below if fragment module is included
|
||||
};
|
||||
wgpu::FragmentState fragmentState = {};
|
||||
std::array<wgpu::ColorTargetState, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> colorTargets {};
|
||||
const wgpu::BlendState blendState {
|
||||
.color = {
|
||||
.operation = toWebGPU(rasterState.blendEquationRGB),
|
||||
.srcFactor = toWebGPU(rasterState.blendFunctionSrcRGB),
|
||||
.dstFactor = toWebGPU(rasterState.blendFunctionDstRGB)
|
||||
},
|
||||
.alpha = {
|
||||
.operation = toWebGPU(rasterState.blendEquationAlpha),
|
||||
.srcFactor = toWebGPU(rasterState.blendFunctionSrcAlpha),
|
||||
.dstFactor = toWebGPU(rasterState.blendFunctionDstAlpha)
|
||||
}
|
||||
};
|
||||
if (program.fragmentShaderModule != nullptr) {
|
||||
fragmentState.module = program.fragmentShaderModule;
|
||||
fragmentState.entryPoint = "main";
|
||||
// see the comment about constants for the vertex state, as the same reasoning applies
|
||||
// here
|
||||
fragmentState.constantCount = 0,
|
||||
fragmentState.constants = nullptr,
|
||||
fragmentState.targetCount = colorFormats.size();
|
||||
fragmentState.targets = colorTargets.data();
|
||||
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
|
||||
// We expect a fragment shader implies at least one color target if it outputs color.
|
||||
// This should be guaranteed by the caller ensuring colorFormats is not empty.
|
||||
// However, this fails on shadowtest.cpp, TODO investigate why
|
||||
// assert_invariant(fragmentState.targetCount > 0);
|
||||
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
|
||||
auto& colorTarget = colorTargets[targetIndex];
|
||||
colorTarget.format = colorFormats[targetIndex];
|
||||
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
|
||||
colorTarget.writeMask =
|
||||
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;
|
||||
}
|
||||
pipelineDescriptor.fragment = &fragmentState;
|
||||
}
|
||||
const wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(pipeline)
|
||||
<< "Failed to create render pipeline for " << pipelineDescriptor.label;
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace wgpu {
|
||||
class Device;
|
||||
class PipelineLayout;
|
||||
class RenderPipeline;
|
||||
enum class TextureFormat : uint32_t;
|
||||
}// namespace wgpu
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct PolygonOffset;
|
||||
enum class PrimitiveType : uint8_t;
|
||||
struct RasterState;
|
||||
struct StencilState;
|
||||
|
||||
class WebGPUVertexBufferInfo;
|
||||
class WebGPUProgram;
|
||||
|
||||
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
|
||||
WebGPUProgram const&, WebGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
|
||||
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType primitiveType,
|
||||
std::vector<wgpu::TextureFormat> const& colorFormats, wgpu::TextureFormat const& depthStencilFormat,
|
||||
uint8_t samplesCount, bool requestedDepth, bool requestedStencil);
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
|
||||
105
filament/backend/src/webgpu/WebGPUPipelineLayoutCache.cpp
Normal file
105
filament/backend/src/webgpu/WebGPUPipelineLayoutCache.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUPipelineLayoutCache.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUPipelineLayoutCache::WebGPUPipelineLayoutCache(wgpu::Device const& device)
|
||||
: mDevice{ device } {}
|
||||
|
||||
wgpu::PipelineLayout const& WebGPUPipelineLayoutCache::getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const& request) {
|
||||
PipelineLayoutKey key{};
|
||||
populateKey(request, key);
|
||||
if (auto iterator{ mPipelineLayouts.find(key) }; iterator != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry{ iterator.value() };
|
||||
entry.lastUsedFrameCount = mFrameCount;
|
||||
return entry.layout;
|
||||
}
|
||||
const wgpu::PipelineLayout layout{ createPipelineLayout(request) };
|
||||
mPipelineLayouts.emplace(key, PipelineLayoutCacheEntry{
|
||||
.layout = layout,
|
||||
.lastUsedFrameCount = mFrameCount,
|
||||
});
|
||||
return mPipelineLayouts[key].layout;
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::onFrameEnd() {
|
||||
++mFrameCount;
|
||||
removeExpiredPipelineLayouts();
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::populateKey(PipelineLayoutRequest const& request,
|
||||
PipelineLayoutKey& outKey) {
|
||||
outKey.bindGroupLayoutCount = static_cast<uint8_t>(request.bindGroupLayoutCount);
|
||||
for (size_t bindGroupIndex{ 0 }; bindGroupIndex < request.bindGroupLayoutCount;
|
||||
++bindGroupIndex) {
|
||||
outKey.bindGroupLayoutHandles[bindGroupIndex] =
|
||||
request.bindGroupLayouts[bindGroupIndex]
|
||||
? request.bindGroupLayouts[bindGroupIndex].Get()
|
||||
: nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::PipelineLayout WebGPUPipelineLayoutCache::createPipelineLayout(
|
||||
PipelineLayoutRequest const& request) {
|
||||
const wgpu::PipelineLayoutDescriptor descriptor{
|
||||
.label = wgpu::StringView(request.label.c_str_safe()),
|
||||
.bindGroupLayoutCount = request.bindGroupLayoutCount,
|
||||
.bindGroupLayouts = request.bindGroupLayouts.data(),
|
||||
// TODO investigate immediateDataRangeByteSize
|
||||
};
|
||||
const wgpu::PipelineLayout layout{ mDevice.CreatePipelineLayout(&descriptor) };
|
||||
FILAMENT_CHECK_POSTCONDITION(layout)
|
||||
<< "Failed to create pipeline layout " << descriptor.label << "?";
|
||||
return layout;
|
||||
}
|
||||
|
||||
bool WebGPUPipelineLayoutCache::PipelineLayoutKeyEqual::operator()(PipelineLayoutKey const& key1,
|
||||
PipelineLayoutKey const& key2) const {
|
||||
return 0 == memcmp(reinterpret_cast<void const*>(&key1), reinterpret_cast<void const*>(&key2),
|
||||
sizeof(key1));
|
||||
}
|
||||
|
||||
void WebGPUPipelineLayoutCache::removeExpiredPipelineLayouts() {
|
||||
using Iterator = decltype(mPipelineLayouts)::const_iterator;
|
||||
for (Iterator iterator{ mPipelineLayouts.begin() }; iterator != mPipelineLayouts.end();) {
|
||||
PipelineLayoutCacheEntry const& entry{ iterator.value() };
|
||||
if (mFrameCount > (entry.lastUsedFrameCount +
|
||||
FILAMENT_WEBGPU_PIPELINE_LAYOUT_EXPIRATION_IN_FRAME_COUNT)) {
|
||||
// pipeline layout expired...
|
||||
iterator = mPipelineLayouts.erase(iterator);
|
||||
} else {
|
||||
// pipeline layout not yet expired...
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
96
filament/backend/src/webgpu/WebGPUPipelineLayoutCache.h
Normal file
96
filament/backend/src/webgpu/WebGPUPipelineLayoutCache.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUPipelineLayoutCache final {
|
||||
public:
|
||||
struct PipelineLayoutRequest final {
|
||||
utils::CString const& label;
|
||||
std::array<wgpu::BindGroupLayout, MAX_DESCRIPTOR_SET_COUNT> const& bindGroupLayouts;
|
||||
size_t bindGroupLayoutCount;
|
||||
};
|
||||
|
||||
explicit WebGPUPipelineLayoutCache(wgpu::Device const&);
|
||||
WebGPUPipelineLayoutCache(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&) = delete;
|
||||
WebGPUPipelineLayoutCache& operator=(WebGPUPipelineLayoutCache const&&) = delete;
|
||||
|
||||
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayout(
|
||||
PipelineLayoutRequest const&);
|
||||
|
||||
void onFrameEnd();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Key designed for efficient hashing and uniquely identifying all the parameters for
|
||||
* creating a pipeline layout.
|
||||
* The efficient hashing requires a small memory footprint
|
||||
* (using the smallest representations of enums, just handle instances instead of wrapper class
|
||||
* instances, single bytes for booleans etc.), trivial copying and comparison (byte by byte),
|
||||
* and a word-aligned structure with a size in bytes as a multiple of 4 (for murmer hash).
|
||||
*/
|
||||
struct PipelineLayoutKey final { // size : offset (need multiples of 4 bytes for hashing)
|
||||
WGPUBindGroupLayout bindGroupLayoutHandles[MAX_DESCRIPTOR_SET_COUNT]{ nullptr }; // 32 : 0
|
||||
uint8_t bindGroupLayoutCount{ 0 }; // 1 : 32
|
||||
uint8_t padding[7]{ 0 }; // 7 : 33
|
||||
};
|
||||
static_assert(sizeof(PipelineLayoutKey) == 40,
|
||||
"PipelineLayoutKey must not have implicit padding.");
|
||||
static_assert(std::is_trivially_copyable<PipelineLayoutKey>::value,
|
||||
"PipelineLayoutKey must be a trivially copyable POD for fast hashing.");
|
||||
|
||||
struct PipelineLayoutKeyEqual {
|
||||
bool operator()(PipelineLayoutKey const&, PipelineLayoutKey const&) const;
|
||||
};
|
||||
|
||||
struct PipelineLayoutCacheEntry final {
|
||||
wgpu::PipelineLayout layout{ nullptr };
|
||||
uint64_t lastUsedFrameCount{ 0 };
|
||||
};
|
||||
|
||||
static void populateKey(PipelineLayoutRequest const&, PipelineLayoutKey& outKey);
|
||||
|
||||
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(PipelineLayoutRequest const&);
|
||||
|
||||
void removeExpiredPipelineLayouts();
|
||||
|
||||
wgpu::Device mDevice;
|
||||
tsl::robin_map<PipelineLayoutKey, PipelineLayoutCacheEntry,
|
||||
utils::hash::MurmurHashFn<PipelineLayoutKey>, PipelineLayoutKeyEqual>
|
||||
mPipelineLayouts{};
|
||||
uint64_t mFrameCount{ 0 };
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUPIPELINELAYOUTCACHE_H
|
||||
@@ -234,7 +234,7 @@ namespace {
|
||||
FWGPU_LOGD << descriptor.label << " compiled successfully";
|
||||
#endif
|
||||
}),
|
||||
SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_WEBGPU_SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
|
||||
switch (waitResult) {
|
||||
case wgpu::WaitStatus::Success:
|
||||
break;
|
||||
|
||||
@@ -56,7 +56,7 @@ private:
|
||||
void generateMipmap(wgpu::CommandEncoder const&, wgpu::Texture const&,
|
||||
wgpu::RenderPipeline const&, uint32_t layer, uint32_t mipLevel);
|
||||
|
||||
wgpu::Device const& mDevice;
|
||||
wgpu::Device mDevice;
|
||||
const wgpu::Sampler mPreviousMipLevelSampler{ nullptr };
|
||||
const wgpu::ShaderModule mShaderModule{ nullptr };
|
||||
const wgpu::BindGroupLayout mTextureBindGroupLayout{ nullptr };
|
||||
|
||||
@@ -486,7 +486,8 @@ struct AdapterDetailsHash final {
|
||||
for (size_t i = 0; i < futures.size(); i++) {
|
||||
wgpu::RequestAdapterOptions const& options = requests[i];
|
||||
wgpu::Future& future = futures[i];
|
||||
wgpu::WaitStatus status = instance.WaitAny(future, REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
|
||||
wgpu::WaitStatus status =
|
||||
instance.WaitAny(future, FILAMENT_WEBGPU_REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
|
||||
<< "Timed out requesting a WebGPU adapter with options "
|
||||
<< adapterOptionsToString(options);
|
||||
@@ -658,7 +659,7 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
assert_invariant(status == wgpu::RequestDeviceStatus::Success);
|
||||
device = readyDevice;
|
||||
}),
|
||||
REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_WEBGPU_REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
|
||||
<< "Failed to request a WebGPU device due to a timeout.";
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <vector>
|
||||
|
||||
// Platform specific includes and defines
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,8 +120,11 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use transform() instead
|
||||
* @see transform()
|
||||
* Transform a Box by a linear transform and a translation.
|
||||
*
|
||||
* @param m a linear transform matrix
|
||||
* @param box the box to transform
|
||||
* @return the bounding box of the transformed box
|
||||
*/
|
||||
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept {
|
||||
return transform(m.upperLeft(), m[3].xyz, box);
|
||||
@@ -235,8 +238,10 @@ struct UTILS_PUBLIC Aabb {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use transform() instead
|
||||
* @see transform()
|
||||
* Applies an affine transformation to the AABB.
|
||||
*
|
||||
* @param m the affine transformation to apply
|
||||
* @return the bounding box of the transformed box
|
||||
*/
|
||||
Aabb transform(const math::mat4f& m) const noexcept {
|
||||
return transform(m.upperLeft(), m[3].xyz, *this);
|
||||
|
||||
@@ -339,13 +339,24 @@ public:
|
||||
static math::float4 getColorEstimate(const math::float3 sh[UTILS_NONNULL 9],
|
||||
math::float3 direction) noexcept;
|
||||
|
||||
|
||||
/** @deprecated use static versions instead */
|
||||
UTILS_DEPRECATED
|
||||
/**
|
||||
* Helper to estimate the direction of the dominant light in the environment represented by
|
||||
* spherical harmonics.
|
||||
* Spherical harmonics must be set in the Builder or the result is undefined.
|
||||
* @see getDirectionEstimate(const math::float3)
|
||||
* @see Builder::irradiance(uint8_t, math::float3 const*)
|
||||
* @see Builder::radiance(uint8_t, math::float3 const*)
|
||||
*/
|
||||
math::float3 getDirectionEstimate() const noexcept;
|
||||
|
||||
/** @deprecated use static versions instead */
|
||||
UTILS_DEPRECATED
|
||||
/**
|
||||
* Helper to estimate the color and relative intensity of the environment represented by
|
||||
* spherical harmonics in a given direction.
|
||||
* Spherical harmonics must be set in the Builder or the result is undefined.
|
||||
* @see getColorEstimate(const math::float3, math::float3)
|
||||
* @see Builder::irradiance(uint8_t, math::float3 const*)
|
||||
* @see Builder::radiance(uint8_t, math::float3 const*)
|
||||
*/
|
||||
math::float4 getColorEstimate(math::float3 direction) const noexcept;
|
||||
|
||||
protected:
|
||||
|
||||
Reference in New Issue
Block a user