Compare commits

...
Author SHA1 Message Date
Powei Feng 5f08c87424 fgviewer: add ability to look at rendertargets 2026-01-20 14:05:27 -08:00
Mathias Agopian 408b2371f0 allow FILAMENT_TRACING_CALL to take extra parameters (#9593) 2026-01-09 15:08:27 -08:00
Powei Feng 6ea08b40a8 gl: small fixes to push constant implementation and test (#9594)
- Add code for handling empty/null constant name
 - Use glUniform1ui() instead of glUniform1i() to workaround
   crash on macbook pro (m4).
 - Fully specify the constant name in the test (e.g.
   `pushConstantsF.red` instead of `red`).
 - Make sure that the uniform names are different between
   vertex and fragment in the test. This is due to different
   constant structs being defined between the two stages.

FIXES=453757504
2026-01-09 14:48:52 -08:00
Doris Wu 5eb0f7d3ec Fix the vbotest sample (#9581)
* set clear to true

* feedback
2026-01-09 10:15:44 +08:00
Powei Feng d465d59c3a github: run filament tests in presbumit (#9586) 2026-01-08 21:25:58 +00:00
Filament Bot af9a60d175 [automated] Updating /docs due to commit 8e0f0c9
Full commit hash is 8e0f0c92ce

DOCS_ALLOW_DIRECT_EDITS
2026-01-08 20:19:49 +00:00
Benjamin Doherty 8e0f0c92ce Release Filament 1.68.3 2026-01-08 12:14:07 -08:00
Anish Goyal 5a14fb4b5d Cache prewarm base case (#9552)
Prevents hitching for most pipelines, on certain devices with Turnip-based drivers

This does not address external samplers yet. This simply handles the
case where we want to prewarm a pipeline with the base configuration.

Refactor program's descriptor set layout bindings, clean up some leaking
resources
2026-01-08 11:43:32 -08:00
dependabot[bot] 22a1aacb21 Bump urllib3 in /docs_src/build in the pip group across 1 directory (#9585)
Bumps the pip group with 1 update in the /docs_src/build directory: [urllib3](https://github.com/urllib3/urllib3).


Updates `urllib3` from 2.6.0 to 2.6.3
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.0...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-08 18:48:08 +00:00
dependabot[bot] e5b6c11399 Bump urllib3 in /test/renderdiff/src in the pip group across 1 directory (#9582)
Bumps the pip group with 1 update in the /test/renderdiff/src directory: [urllib3](https://github.com/urllib3/urllib3).


Updates `urllib3` from 2.6.0 to 2.6.3
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.0...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: direct:production
  dependency-group: pip
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-08 10:28:15 -08:00
Mathias Agopian 85ebd67a28 feat(taa): Overhaul TAA implementation for improved quality and upscaling (#9573)
This commit introduces a significant rework of the Temporal Anti-Aliasing (TAA) system, focusing on improving reconstruction quality, robustness, and introducing flexible upscaling.

Core TAA Algorithm improvements:
- Replaced the Catmull-Rom filter with a more efficient 5-tap Lanczos filter for history sampling, which includes deringing to reduce artifacts.
- The input color buffer is now properly "unjittered" using a Lanczos reconstruction filter.
- Improved the history rejection algorithm by skipping the expensive accurate clipping when the history sample is already within the neighborhood's color gamut.
- Added a new `hdr` option to properly handle HDR content by tonemapping colors before blending and untonemapping the result.
- Removed the ineffective `VARIANCE` only history rejection method.
- Added protection against negative numbers in `sqrt()` for increased stability.

TAA Upscaling:
- Replaced the boolean `upscaling` flag with a float factor, allowing for variable upscaling ratios (e.g., 1.5x, 2x).
- Upscaling now correctly adjusts viewport and projection settings.
- The TAA shader now receives viewport and resolution information to correctly handle upscaled rendering.

API and Configuration Changes:
- Deprecated the `filterWidth` TAA option as it no longer has an effect.
- Introduced the `upscaling` float property to `TemporalAntiAliasingOptions`.
- Added the `hdr` boolean property to `TemporalAntiAliasingOptions`.

Other Changes:
- Updated UI elements in the viewer and material sandbox to reflect the new TAA options.
- Updated Javascript bindings and TypeScript definitions for the new TAA settings.
- Refactored shader code for clarity and performance.
2026-01-07 15:25:38 -08:00
Powei Feng f33a779668 vk: move swapchain acquire() to as late as possible (#9541)
makeCurrent is not meant to acquire the swapchain; we should
do so at the momemnt we need it, to shorten the amount of time
a swapchain image/buffer needs to be held.
2026-01-07 18:36:54 +00:00
Sungun Park 1b5916b655 Rename AsyncCallbackType to AsyncCompletionCallback (#9579) 2026-01-07 18:18:49 +00:00
Anish Goyal d8720cd858 Rename DescriptorSetLayoutBinding to DescriptorSetLayoutDescriptor (#9576)
Since the struct refers to descriptors as opposed to specifically
bindings (although they do contain binding information as well), this
name is more appropriate.
2026-01-07 16:52:50 +00:00
Doris Wu 44636bddaa Split Skinning out of Visibility struct (#9575) 2026-01-07 11:37:42 +08:00
Sungun Park 1741723687 Add user parameter to runCommandAsync (#9577) 2026-01-06 18:37:42 -08:00
Mathias Agopian ad2c291d4a dispersion in XYZ space (#9572)
- spectral reconstructions with 4 samples
- unroll the whole dispersion computation to improve performance
  (batch texture fetches and reuse common values)
- tool to generate the matrices for spectral integration
2026-01-06 16:58:56 -08:00
Mathias Agopian 52ffd80041 Refactor FrameGraph pass API for flexibility (#9571)
This commit simplifies and unifies the FrameGraph's `addPass` API.
Key changes:
- The `addPass` method now uses a single, more flexible implementation
  that handles different lambda signatures for both the setup and
  execute stages.
- The execute lambda can now optionally omit the `DriverApi&` parameter,
  reducing boilerplate for simple passes.
- The `addTrivialSideEffectPass` helper has been removed.
  Side-effect-only passes can now be created directly with `addPass`
2026-01-06 14:20:47 -08:00
Doris Wu 44dde744ea Several optimizations for UBO batching (#9545) 2026-01-06 05:52:32 +00:00
Mathias Agopian c18ddf929e Improve frame_generator sample tool (#9570)
- Add support for vector parameters (float2, float3, float4) in the 
  parameters file using `{x, y, z}` syntax.
- Allow comments (`#`, `//`) and empty lines in the parameters file.
- Make the end value optional in the parameters file 
  (defaults to start value).
- Add `--size` / `-S` option to specify viewport dimensions.
- Optimize rendering by pre-filtering active material parameters.
- Remove hardcoded default parameters and silently ignore missing 
  parameters to support shared configuration files.
- Add documentation for the parameters file format.
2026-01-05 20:28:04 -08:00
Mathias Agopian 5661365248 improved the dispersion computation (#9544)
- Split the function so that the 3-components loop is more apparent.
- Factored out the parts of the computation that don't depend on the
index of refraction, so they're only computed once.
- Moved the lod-from-roughness computation out of the loop as well. 
  It does depend on the IOR, but so little that it's not worth it.

The most important result of this change is that the code is now in
good shape if we wanted to change the implementation of dispersion for
these cases (for e.g.) :
- do the computation in the XYZ space
- use more than 3 wavelengths samples
- or use a stochastic approach, trading banding for noise

RDIFF_BRANCH=ma/better-dispersion
2026-01-05 20:27:34 -08:00
Powei Feng bcb9d258c2 vk: fix broken android build (#9574)
Should use VK_NULL_HANDLE instead of nullptr where applicable
in VulkanPipelineCache.
2026-01-05 20:27:07 -08:00
Anish Goyal 53a94c4547 Add thread pool and callback manager / knobs for enabling + disabling prewarm (#9515)
* Update createPipeline to have pipeline key input
Right now, createPipeline in VulkanPipelineCache assumes that you are
building a pipeline using mPipelineRequirements as the key, and that it
should be stored in the cache. In the case of the pipeline prewarming
change, this is not going to be the case; sometimes, the pipeline will
be stored in the cache, and sometimes it will be deleted immediately.
Also, the prewarmed pipelines will be created using separately defined
keys.

* Add knobs for enabling / disabling prewarm

* Add thread pool to VulkanPipelineCache

* Fix dynamic rendering enablement check
Query device features to ensure it is supported / we don't just have the
extension. Also, proactively enable dynamic rendering when creating the
device.
2026-01-05 23:55:08 +00:00
Filament Bot 928ffe94a7 [automated] Updating /docs due to commit dcb78c7
Full commit hash is dcb78c7144

DOCS_ALLOW_DIRECT_EDITS
2026-01-05 21:56:49 +00:00
Mathias Agopian dcb78c7144 update dispersion documentation (#9567)
* add dispersion parameter to material_sandbox

* frame_generator fixes

- fix skybox
- for baseColor to white (workaround)

* update dispersion documentation source
2026-01-05 13:54:24 -08:00
Sungun Park 43dd8fa1cc Fix MockDriver (#9569)
This is missing changes from
https://github.com/google/filament/commit/0eff54b807d236ed0782be18b75412ccc3c0ce8f
2026-01-05 21:08:50 +00:00
Anish Goyal 91a5f00d30 Remove unnecessary log statement (#9568)
It is not an error for a device to not have this API.
2026-01-05 12:41:34 -08:00
Sungun Park 0eff54b807 Add async support check to backend (#9542)
BUGS=[442921995]
2026-01-05 20:10:36 +00:00
Lucas Leandro 68eb0ce3f1 Add java bindings for changing from default to screen-space reflection in real-time MaterialBuilder (#9548) 2026-01-05 11:44:54 -08:00
Benjamin SchulzandSungun Park 023318c08d Fixes "error: templates must have C++ linkage" when compiling with older clang versions. (#9562)
Co-authored-by: Sungun Park <sungunpark@google.com>
2026-01-05 11:43:54 -08:00
rafadevaiandSerge Metral c0e3aa2b0a VK: In UMA, apply the staging bypass for everything (#9538)
Currently only uniform buffers benefit from the bypass,
but this can also be extended to others buffers too.

This also help trying to match the performance of the
GL backend in Android.

FIXES=[470138463]

Co-authored-by: Serge Metral <sergemetral@google.com>
2026-01-05 11:36:54 -08:00
Anish Goyal a6d1b9188f Add descriptor set layouts to Program (#9522)
This will help later for parallel compilation
2026-01-05 18:59:07 +00:00
Sungun Park 43193c5ac5 Add missing async destruction for BufferObject and IndexBuffer (#9565)
As with Texture, now BufferObject and IndexBuffer will be destroyed via
the async queue if they were asynchronously created.

BUGS=[442921995]
2026-01-05 18:15:01 +00:00
Mathias Agopian 35c58047ee move the depth clear prepare into the framegraph (#9549)
depth clear prepare was done outside of the framegraph, it's now
moved into the colorpass prepare pass.
2026-01-03 15:04:51 -08:00
Mathias Agopian 2190f3544f fix build when FILAMENT_SUPPORTS_VULKAN is OFF (#9557) 2026-01-03 15:03:30 -08:00
Mathias Agopian fd8ea78e04 Fix TAA (#9556)
TAA had been broken for a while, the jitter was no longer applied.

We now compute the jittered camera before we set it into the 
per frame UBO.
2026-01-03 15:03:06 -08:00
Emily Banerjee 7fc336a6d9 Fix GCC type mismatch error in Platform::debugUpdateStat (#9563) 2026-01-02 09:48:28 -08:00
yein 3cc6fae5c0 Add api level enforcement (#9561)
Api level is now enforced when generating code by appending #defines of client requested and unstable apis.
This ensures that the client can only use unstable apis when they explicitly add `apiLevel` in their .mat file.

FIXES=468340631
2025-12-29 13:38:02 -08:00
yein f4f75da016 Fix garbage data getting added to the source material (#9553) 2025-12-26 09:29:13 -08:00
Filament Bot bdeba1aa86 [automated] Updating /docs due to commit 8991be4
Full commit hash is 8991be4eea

DOCS_ALLOW_DIRECT_EDITS
2025-12-24 00:55:04 +00:00
Ben Doherty 8991be4eea Fix incorrect GLSL types in Materials documentation (#9555) 2025-12-23 16:52:27 -08:00
yein 940b93ceb8 Replace slog to LOG (#9554) 2025-12-23 13:45:42 -08:00
Filament Bot 00eb3d0d4f [automated] Updating /docs due to commit 7f61eb7
Full commit hash is 7f61eb7a0a

DOCS_ALLOW_DIRECT_EDITS
2025-12-23 19:44:04 +00:00
Benn Herrera 7f61eb7a0a GLTFIO Webp Texture Support (#9406) 2025-12-23 11:40:01 -08:00
yein 53f506670c Add apiLevel and add parsing logic (#9535) 2025-12-22 13:52:32 -08:00
Mathias AgopianandPowei Feng 9fe7ab31ed Make FrameGraph resource management extensible (#9536)
* rename ResourceAllocator to TextureCache

This better represents what this class is for.

* Make FrameGraph resource management extensible

This refactors the FrameGraph's resource management to allow for 
user-defined custom resources.
 
A new `ResourceAllocator` template abstracts the creation and 
destruction of resources, with specializations for different resource 
types (e.g., generic, backend-dependent, textures). 
This removes hardcoded resource management logic from the `FrameGraph` 
execution loop.
 
A `ResourceCreationContext` is introduced to provide the necessary 
context for resource allocation.
 
Tests for generic and backend custom resources have been added to 
validate the new system.

* Update filament/src/fg/FrameGraphDummyLink.h

Co-authored-by: Powei Feng <powei@google.com>

---------

Co-authored-by: Powei Feng <powei@google.com>
2025-12-22 11:08:53 -08:00
Mathias Agopian 008534b8a2 Fix dispersion with uber-shaders (#9543)
Fixes #9540
2025-12-22 10:12:54 -08:00
Sungun Park 2c5842f908 Implement asynchronous resource management (#9508)
This commit introduces async methods and functions across both the
public and backend APIs, enabling non-blocking creation and updates for
Texture, VertexBuffer, and IndexBuffer resources.

Asynchronous resource management is disabled by default. Users should
enable this to use the feature.

Java bindings and a C++ code sample will follow in subsequent PRs.

BUGS=[442921995]
2025-12-19 21:18:32 -08:00
Powei Feng a8ee9d473e android: fix texture rendertarget sample for vk (#9528)
- add a flag to run the sample using regular, non-external
   texture as render target.
 - Add a non-external version of the material
 - vk: fix y-flipping issue with uvToRenderTargetUV()
2025-12-19 22:15:22 +00:00
Mathias Agopian 1e3ab83e6a Remove the dependency of RenderPassBuilder on DriverApi (#9533)
The main goal of this CL is to break the dependency of RenderPassBuilder
on DriverApi. The dependency only existed for the auto-instancing 
feature, which needed to allocate and update a UBO on the fly.

We want to break that dependency so that RenderPassBuilder::build().
which is an expensive call, could run on a separate thread. In the
future this could allow, for example, shadow and ssao passes to 
run in parallel.

A new method RenderPass::finalize(DriverApi&) is added which *must* be 
called before calling getExecutor(), this is where the temporary
UBO is allocated and updated. We need a new method because this must be 
called on the main thread before a render pass is started.

This implementation stores the UBO data needed for instancing until
finalize() is called, if any.

We introduce a compact() utility that efficiently scans a range finding
"equivalent" consecutive objects and removes duplicates. The previous
code did that with an extra remove pass which invalidated all 
iterators.
2025-12-19 13:50:20 -08:00
yein 9720da2c68 Fix Json lexeme trying to allocate a string with a huge size due to underflow (#9539) 2025-12-19 13:29:01 -08:00
yein ee74e2d50c Fix missing dep in CompressedStringChunk (#9534) 2025-12-18 13:35:49 -08:00
Powei Feng 6171009add test: fixing broken webgpu+android build (#9529)
The build was failing because the some tint libs are no longer
real dependencies

Fixes #9520
2025-12-18 18:16:21 +00:00
Mathias Agopian d299a444fa don't limit FILAMENT_ENABLE_COVERAGE to DEBUG builds (#9532) 2025-12-18 09:24:19 -08:00
rafadevai 107276e9cc VK: Refactor platform Vulkan initialization (#9523)
* VK: Refactor platform Vulkan initialization

Refactor the platform code to allow overriding
how the vkInstance, vkPhysicalDevice and vkDevice
are created.

This refactor will also consolidate where the extensions
and features are enabled/disabled into VulkanPlatform,
instead of each platform doing its own individual setup
if needed. It also address knowing if a feature or
extension has been enabled, unless we keep adding
entries to the SharedContext which is not scalable.

The createDriver function now have a more defined structure,
mostly how and where features are queried and enabled.

- Get shared context data.
- Create instance if missing.
- Select physical device if missing.
- Query physical device support and set supported
engine features into the VulkanContext.
- Select the queues
- Create the device and queues

* Address review comments

Added some documentation on the new functions
Move the querying and config of features into its
own function.
2025-12-18 09:23:18 -08:00
Mathias Agopian fe00cbf106 feature flags now default to const. (#9530)
This PR doesn't update most of the existing flags that are marked as
"mutable", they're probably wrong.
2025-12-17 10:34:31 -08:00
Filament Bot 02d0ddbcfe [automated] Updating /docs due to commit c4802f7
Full commit hash is c4802f7683

DOCS_ALLOW_DIRECT_EDITS
2025-12-16 23:01:53 +00:00
230 changed files with 12207 additions and 6635 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- uses: ./.github/actions/mac-prereq
- name: Run build script
run: |
cd build/mac && printf "y" | ./build.sh presubmit
cd build/mac && printf "y" | ./build.sh presubmit-with-test
- name: Test material parser
run: |
out/cmake-release/filament/test/test_material_parser
+6 -2
View File
@@ -473,7 +473,7 @@ endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${EXTRA_SANITIZE_OPTIONS}")
if (FILAMENT_ENABLE_COVERAGE)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping -Wno-pass-failed")
endif()
# Disable the stack check for macOS to workaround a known issue in clang 11.0.0.
@@ -563,6 +563,10 @@ if (FILAMENT_SUPPORTS_WEBGPU)
add_definitions(-DFILAMENT_SUPPORTS_WEBGPU)
endif()
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
endif()
# Build with Metal support on non-WebGL Apple platforms.
if (APPLE AND NOT WEBGL)
option(FILAMENT_SUPPORTS_METAL "Include the Metal backend" ON)
@@ -915,7 +919,6 @@ endif()
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_subdirectory(${EXTERNAL}/libwebp/tnt)
add_definitions(-DFILAMENT_SUPPORTS_WEBP_TEXTURES)
endif()
if (FILAMENT_SUPPORTS_VULKAN)
@@ -973,6 +976,7 @@ if (IS_HOST_PLATFORM)
add_subdirectory(${TOOLS}/roughness-prefilter)
add_subdirectory(${TOOLS}/specular-color)
add_subdirectory(${TOOLS}/uberz)
add_subdirectory(${TOOLS}/specgen)
endif()
# Generate exported executables for cross-compiled builds (Android, WebGL, and iOS)
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.68.2'
implementation 'com.google.android.filament:filament-android:1.68.3'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```shell
pod 'Filament', '~> 1.68.2'
pod 'Filament', '~> 1.68.3'
```
## Documentation
+4
View File
@@ -7,6 +7,10 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.68.4
- gltfio: Add optional support for webp textures (EXT_texture_webp), controlled via FILAMENT_SUPPORTS_WEBP_TEXTURES cmake option
## v1.68.3
- materials: added support for the glTF `KHR_materials_dispersion` extension, which adds dispersion for refractive objects
@@ -335,6 +335,13 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderRefract
builder->refractionMode((MaterialBuilder::RefractionMode) mode);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderReflectionMode(JNIEnv* env,
jclass, jlong nativeBuilder, jint mode) {
auto builder = (MaterialBuilder*) nativeBuilder;
builder->reflectionMode((MaterialBuilder::ReflectionMode) mode);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderRefractionType(JNIEnv* env,
jclass, jlong nativeBuilder, jint type) {
@@ -177,6 +177,11 @@ public class MaterialBuilder {
SCREEN_SPACE
}
public enum ReflectionMode {
DEFAULT,
SCREEN_SPACE
}
public enum RefractionType {
SOLID,
THIN
@@ -403,6 +408,12 @@ public class MaterialBuilder {
return this;
}
@NonNull
public MaterialBuilder reflectionMode(ReflectionMode mode) {
nMaterialBuilderReflectionMode(mNativeObject, mode.ordinal());
return this;
}
@NonNull
public MaterialBuilder refractionType(RefractionType type) {
nMaterialBuilderRefractionType(mNativeObject, type.ordinal());
@@ -604,6 +615,7 @@ public class MaterialBuilder {
private static native void nMaterialBuilderSpecularAntiAliasingThreshold(long mNativeObject,
float threshold);
private static native void nMaterialBuilderRefractionMode(long nativeBuilder, int mode);
private static native void nMaterialBuilderReflectionMode(long nativeBuilder, int mode);
private static native void nMaterialBuilderRefractionType(long nativeBuilder, int type);
private static native void nMaterialBuilderClearCoatIorChange(long mNativeObject,
boolean clearCoatIorChange);
@@ -2041,10 +2041,6 @@ public class View {
* use an AABB neighborhood
*/
AABB,
/**
* use the variance of the neighborhood (not recommended)
*/
VARIANCE,
/**
* use both AABB and variance
*/
@@ -2075,7 +2071,7 @@ public class View {
}
/**
* reconstruction filter width typically between 1 (sharper) and 2 (smoother)
* @deprecated has no effect.
*/
public float filterWidth = 1.0f;
/**
@@ -2095,9 +2091,9 @@ public class View {
*/
public boolean enabled = false;
/**
* 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
* Upscaling factor. Disables Dynamic Resolution. [BETA]
*/
public boolean upscaling = false;
public float upscaling = 1.0f;
/**
* whether to filter the history buffer
*/
@@ -2110,6 +2106,10 @@ public class View {
* whether to use the YcoCg color-space for history rejection
*/
public boolean useYCoCg = false;
/**
* set to true for HDR content
*/
public boolean hdr = true;
/**
* type of color gamut box
*/
+14
View File
@@ -6,6 +6,7 @@ option(FILAMENT_ENABLE_FGVIEWER "Enables Frame Graph Viewer" OFF)
option(FILAMENT_ENABLE_MATDBG "Enables Material debugger" OFF)
option(FILAMENT_DISABLE_MATOPT "Disables material optimizations" OFF)
option(FILAMENT_SUPPORTS_WEBGPU "Enables WebGPU on Android" OFF)
option(FILAMENT_SUPPORTS_WEBP_TEXTURES "Enable webp texture support on Android" OFF)
set(CMAKE_CXX_STANDARD 20)
@@ -30,6 +31,12 @@ add_library(stb STATIC IMPORTED)
set_target_properties(stb PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libstb.a)
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
add_library(webpdecoder STATIC IMPORTED)
set_target_properties(webpdecoder PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libwebpdecoder.a)
endif()
add_library(basis_transcoder STATIC IMPORTED)
set_target_properties(basis_transcoder PROPERTIES IMPORTED_LOCATION
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbasis_transcoder.a)
@@ -81,6 +88,10 @@ set(GLTFIO_INCLUDE_DIRS
../../libs/ktxreader/include
)
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
list(APPEND GLTFIO_INCLUDE_DIRS ../../third_party/libwebp/src)
endif()
add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
@@ -107,3 +118,6 @@ target_link_libraries(gltfio-jni
PRIVATE perfetto # needed only when FILAMENT_ENABLE_PERFETTO is defined
PRIVATE log # needed only when perfetto above is used
)
if (FILAMENT_SUPPORTS_WEBP_TEXTURES)
target_link_libraries(gltfio-jni PRIVATE webpdecoder)
endif()
@@ -128,6 +128,18 @@ Java_com_google_android_filament_gltfio_ResourceLoader_nCreateKtx2Provider(JNIEn
return (jlong) createKtx2Provider(engine);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nIsWebpSupported(JNIEnv*, jclass) {
return (jboolean) isWebpSupported();
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateWebpProvider(JNIEnv*, jclass,
jlong nativeEngine) {
Engine* engine = (Engine*) nativeEngine;
return (jlong) createWebpProvider(engine);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_gltfio_ResourceLoader_nDestroyTextureProvider(JNIEnv*, jclass,
jlong nativeProvider) {
@@ -37,6 +37,7 @@ public class ResourceLoader {
private final long mNativeObject;
private final long mNativeStbProvider;
private final long mNativeKtx2Provider;
private final long mNativeWebpProvider;
/**
* Constructs a resource loader tied to the given Filament engine.
@@ -50,9 +51,17 @@ public class ResourceLoader {
mNativeObject = nCreateResourceLoader(nativeEngine, false);
mNativeStbProvider = nCreateStbProvider(nativeEngine);
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
if (nIsWebpSupported()) {
mNativeWebpProvider = nCreateWebpProvider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/webp", mNativeWebpProvider);
}
else {
mNativeWebpProvider = 0;
}
}
/**
@@ -68,9 +77,17 @@ public class ResourceLoader {
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights);
mNativeStbProvider = nCreateStbProvider(nativeEngine);
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
if (nIsWebpSupported()) {
mNativeWebpProvider = nCreateWebpProvider(nativeEngine);
nAddTextureProvider(mNativeObject, "image/webp", mNativeWebpProvider);
}
else {
mNativeWebpProvider = 0;
}
}
/**
@@ -80,6 +97,9 @@ public class ResourceLoader {
nDestroyResourceLoader(mNativeObject);
nDestroyTextureProvider(mNativeStbProvider);
nDestroyTextureProvider(mNativeKtx2Provider);
if (nIsWebpSupported()) {
nDestroyTextureProvider(mNativeWebpProvider);
}
}
/**
@@ -191,6 +211,9 @@ public class ResourceLoader {
private static native long nCreateStbProvider(long nativeEngine);
private static native long nCreateKtx2Provider(long nativeEngine);
private static native boolean nIsWebpSupported();
private static native long nCreateWebpProvider(long nativeEngine);
private static native void nAddTextureProvider(long nativeLoader, String url, long nativeProvider);
private static native void nDestroyTextureProvider(long nativeProvider);
}
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.68.2
VERSION_NAME=1.68.3
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -76,11 +76,16 @@ class MainActivity : Activity() {
private var texture: Texture? = null
private var renderTarget: RenderTarget? = null
private var useExternalTexture = true
private lateinit var offscreenView: View
private lateinit var offscreenCamera: Camera
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// To use set this flag with adb, run
// adb shell am start -n com.google.android.filament.texturetarget/.MainActivity --ez useExternalTexture false
useExternalTexture = intent.getBooleanExtra("useExternalTexture", true)
surfaceView = SurfaceView(this)
setContentView(surfaceView)
choreographer = Choreographer.getInstance()
@@ -173,8 +178,15 @@ class MainActivity : Activity() {
readUncompressedAsset("materials/baked_color.filamat").let {
triangleMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
}
readUncompressedAsset("materials/textured.filamat").let {
texturedMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
if (useExternalTexture) {
readUncompressedAsset("materials/texturedExternal.filamat").let {
texturedMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
}
} else {
readUncompressedAsset("materials/textured.filamat").let {
texturedMaterial = Material.Builder().payload(it, it.remaining()).build(engine)
}
}
}
@@ -385,33 +397,41 @@ class MainActivity : Activity() {
texture?.let { engine.destroyTexture(it) }
hardwareBuffer?.close()
// Create a new render target.
hardwareBuffer = HardwareBuffer.create(width, height,
HardwareBuffer.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
if (useExternalTexture) {
// Create a new render target.
hardwareBuffer = HardwareBuffer.create(width, height,
HardwareBuffer.RGBA_8888, 1,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT)
texture = Texture.Builder()
.width(width)
.height(height)
.usage(Texture.Usage.COLOR_ATTACHMENT or Texture.Usage.SAMPLEABLE)
.sampler(Texture.Sampler.SAMPLER_EXTERNAL)
.format(Texture.InternalFormat.RGBA8)
.external()
.build(engine)
texture = Texture.Builder()
.width(width)
.height(height)
.usage(Texture.Usage.COLOR_ATTACHMENT or Texture.Usage.SAMPLEABLE)
.sampler(Texture.Sampler.SAMPLER_EXTERNAL)
.format(Texture.InternalFormat.RGBA8)
.external()
.build(engine)
texture!!.setExternalImage(engine, hardwareBuffer!!)
texture!!.setExternalImage(engine, hardwareBuffer!!)
} else {
texture = Texture.Builder()
.width(width)
.height(height)
.levels(1)
.usage(Texture.Usage.COLOR_ATTACHMENT or Texture.Usage.SAMPLEABLE)
.format(Texture.InternalFormat.RGBA8)
.build(engine)
}
renderTarget = RenderTarget.Builder()
.texture(RenderTarget.AttachmentPoint.COLOR, texture!!)
.build(engine)
offscreenView.renderTarget = renderTarget
// Set the texture on the quad material.
texturedMaterial.defaultInstance.setParameter("texture", texture!!,
TextureSampler(TextureSampler.MinFilter.LINEAR, TextureSampler.MagFilter.LINEAR,
TextureSampler.WrapMode.CLAMP_TO_EDGE))
TextureSampler.WrapMode.CLAMP_TO_EDGE))
FilamentHelper.synchronizePendingFrames(engine)
}
}
@@ -3,7 +3,7 @@ material {
shadingModel : unlit,
parameters : [
{
type : samplerExternal,
type : sampler2d,
name : texture
}
],
@@ -15,6 +15,6 @@ material {
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture(materialParams_texture, getUV0());
material.baseColor = texture(materialParams_texture, uvToRenderTargetUV(getUV0()));
}
}
@@ -0,0 +1,20 @@
material {
name : textured,
shadingModel : unlit,
parameters : [
{
type : samplerExternal,
name : texture
}
],
requires: [
uv0
]
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = texture(materialParams_texture, uvToRenderTargetUV(getUV0()));
}
}
+5
View File
@@ -34,6 +34,11 @@ if [[ "$TARGET" == "presubmit" ]]; then
BUILD_RELEASE=release
fi
if [[ "$TARGET" == "presubmit-with-test" ]]; then
BUILD_RELEASE=release
RUN_TESTS=-u
fi
if [[ "$TARGET" == "debug" ]]; then
BUILD_DEBUG=debug
GENERATE_ARCHIVES=-a
+30 -6
View File
@@ -97,6 +97,7 @@ in table [standardProperties].
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
**microThickness** | Thickness of the thin layer of refractive objects
**dispersion** | Strength of the dispersion effect for refractive objects, specified as 20/Abbe number
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
[Table [standardProperties]: Properties of the standard model]
@@ -126,6 +127,7 @@ The type and range of each property is described in table [standardPropertiesTyp
**absorption** | float3 | [0..n] |
**microThickness** | float | [0..n] |
**thickness** | float | [0..n] |
**dispersion** | float | [0..n] | Realistic values are between [0, 1], with the exception of Rutile, which has a value of 2.04
[Table [standardPropertiesTypes]: Range and type of the standard model's properties]
@@ -153,13 +155,14 @@ The type and range of each property is described in table [standardPropertiesTyp
as-is, which can lead to physically impossible materials, however, this might be desirable
for artistic reasons.
!!! Note: About `thickness` and `microThickness` for refraction
!!! Note: About `thickness`, `microThickness` and `dispersion` for refraction
`thickness` represents the thickness of solid objects in the direction of the normal, for
satisfactory results, this should be provided per fragment (e.g.: as a texture) or at least per
vertex. `microThickness` represent the thickness of the thin layer of an object, and can
generally be provided as a constant value. For example, a 1mm thin hollow sphere of radius 1m,
would have a `thickness` of 1 and a `microThickness` of 0.001. Currently `thickness` is not
used when `refractionType` is set to `thin`.
would have a `thickness` of 1 and a `microThickness` of 0.001. Dispersion controls the angular
separation of colors transmitting through a volume, and can be set by a constant value.
Currently `thickness` and `dispersion` are not used when `refractionType` is set to `thin`.
### Base color
@@ -651,6 +654,26 @@ the `refractionType` is set to `solid` and `absorption` coefficients are set.
![Figure [varyingThickness]: `thickness` varying from 0.0 at the top of the prism to 3.0 at the
bottom of the prism](images/material_thickness.png)
### Dispersion
The dispersion property controls the angular separation of colors transmitting through a relatively
clear volume. It can only be used when `refractionType` is set to `volume`.
Its value is specified as 20/Abbe number. When the value is zero, no dispersion is used.
Table [commonMatDispersion] describes acceptable dispersion values for various types of materials.
Material | Abbe Number (V) | Dispersion (20/V)
--------------------------:|:------------------:|:-----------------
Rutile | 9.8 | 2.04
Polycarbonate | 32 | 0.625
Diamond | 55 | 0.36
Water | 55 | 0.36
Crown Glass | 59 | 0.33
[Table [commonMatDispersion]: Dispersion of common materials]
![Figure [dispersionProperty]: `dispersion` varying from 0.0
(left) to 5.0 (right)](images/materials/dispersion.png)
## Subsurface model
### Thickness
@@ -2276,6 +2299,7 @@ struct MaterialInputs {
float3 absorption; // default float3(0.0, 0.0, 0.0)
float ior; // default: 1.5
float microThickness; // default: 0.0, not available with refractionType "solid"
float dispersion; // default: 0.0, not available with refractionType "thin"
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2456,9 +2480,9 @@ type aliases:
**uint2** | uvec2 | A vector of 2 unsigned integers
**uint3** | uvec3 | A vector of 3 unsigned integers
**uint4** | uvec4 | A vector of 4 unsigned integers
**float2** | float2 | A vector of 2 floats
**float3** | float3 | A vector of 3 floats
**float4** | float4 | A vector of 4 floats
**float2** | vec2 | A vector of 2 floats
**float3** | vec3 | A vector of 3 floats
**float4** | vec4 | A vector of 4 floats
**float4x4** | mat4 | A 4x4 float matrix
**float3x3** | mat3 | A 3x3 float matrix
+29
View File
@@ -327,12 +327,41 @@ host tools are required to successfully build for Android.</p>
<p>Note that the main target is the ARM 64-bit target. Our implementation is optimized first and
foremost for <code>arm64-v8a</code>.</p>
<p>To build Android on Windows machines, see <a href="android/Windows.html">android/Windows.md</a>.</p>
<h4 id="important-sdk-location"><a class="header" href="#important-sdk-location">Important: SDK location</a></h4>
<p>Either ensure your <code>ANDROID_HOME</code> environment variable is set or make sure the root project
contains a <code>local.properties</code> file with the <code>sdk.dir</code> property pointing to your installation of
the Android SDK.</p>
<h4 id="easy-android-build"><a class="header" href="#easy-android-build">Easy Android build</a></h4>
<p>The easiest way to build Filament for Android is to use <code>build.sh</code> and the
<code>-p android</code> flag. For instance to build the release target:</p>
<pre><code class="language-shell">./build.sh -p android release
</code></pre>
<p>To build a sample (such as <code>android/samples/sample-hello-triangle</code>) for an ARM 64-bit phone, you would run</p>
<pre><code class="language-shell">./build.sh -p android -q arm64-v8a -k sample-hello-triangle release
</code></pre>
<p>The output APK can be found in <code>android/samples/sample-hello-triangle/build/outputs/apk/release/sample-hello-triangle-release-unsigned.apk</code></p>
<p>Run <code>build.sh -h</code> for more information.</p>
<h4 id="android-studio"><a class="header" href="#android-studio">Android Studio</a></h4>
<p>You must use the latest stable release of Android Studio.</p>
<p>The Android build of filament is separated into java/kotlin client APIs, a layer of jni bindings
that bridges java/kotlin with native code, and Filament and other component code that have been compiled
into architecture-specific libraries. Our default Android Studio gradle setup can compile java/kotlin and
the jni bindings for you, but it will treat the filament libraries as already compiled and present on
the system.</p>
<p>Therefore, before compiling the sample app or any other targets, you must
make sure that the native filament libraries have been compiled and are located at a prescribed location
so that the jni bindings can link against them. You can do so by using the easy build script</p>
<pre><code class="language-shell">./build.sh -p android release -q arm64-v8a
</code></pre>
<p>Note that the above step will also install host machine tools into prescribed locations. These tools are
required for compiling Filament assets such as materials and environment maps.</p>
<p>Now we are ready to compile the apps. To open the project, point Studio to the <code>android</code> folder.
After opening the project and syncing with Gradle, select the sample of your choice
using the drop-down widget in the toolbar. Additionally, you will need to select a deployment target.
By doing so, Android Studio will automatically try to compile the app only for that specific
device's architecture. So if you are targeting a new Pixel phone, make sure that the step above
(compiling the library) is targeting ARM 64-bit (<code>-q arm64-v8a</code> ), and if you are running the app on
an emulator on a Linux machine with an x86 64-bit chipset, you would indicate (<code>-q x86_64</code>) in the above step.</p>
<h4 id="manual-builds"><a class="header" href="#manual-builds">Manual builds</a></h4>
<p>Invoke CMake in a build directory of your choice, inside of filament's directory. The commands
below show how to build Filament for ARM 64-bit (<code>aarch64</code>).</p>
+1 -1
View File
@@ -203,7 +203,7 @@ tree into <code>docs_src/src_mdbook/src/dup</code>. Moreover, to restore valid l
to perform a number of URL replacements in addition to the copy. These replacements are
described in <a href="https://github.com/google/filament/blob/main/docs_src/build/duplicates.json"><code>docs_src/build/duplicates.json</code></a>.</p>
<h3 id="core-concept-docs"><a class="header" href="#core-concept-docs">Core concept docs</a></h3>
<p>The primary design of Filament as a phyiscally-based renderer and details of its materials
<p>The primary design of Filament as a physically-based renderer and details of its materials
system are described in <code>Filament.md.html</code> and <code>Materials.md.html</code>, respectively. These two
documents are written in <a href="https://casual-effects.com/markdeep/"><code>markdeep</code></a>. To embed them into our book, we</p>
<ol>
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.68.0'
implementation 'com.google.android.filament:filament-android:1.68.3'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.68.0'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.68.3'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

+2732 -2773
View File
File diff suppressed because one or more lines are too long
+1903 -1902
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -2
View File
@@ -307,7 +307,7 @@ class FilamentViewer extends LitElement {
asyncInterval: 30
};
const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider) => {
const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider, webpProvider) => {
this.srcBlobResources = {};
resourceLoader.asyncBeginLoad(this.asset);
const timer = setInterval(() => {
@@ -318,6 +318,9 @@ class FilamentViewer extends LitElement {
resourceLoader.delete();
stbProvider.delete();
ktx2Provider.delete();
if (webpProvider) {
webpProvider.delete();
}
this.animator = this.asset.getInstance().getAnimator();
this.animationStartTime = Date.now();
}
@@ -335,18 +338,25 @@ class FilamentViewer extends LitElement {
const stbProvider = new Filament.gltfio$StbProvider(this.engine);
const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
let webpProvider = null;
resourceLoader.addStbProvider("image/jpeg", stbProvider);
resourceLoader.addStbProvider("image/png", stbProvider);
resourceLoader.addKtx2Provider("image/ktx2", ktx2Provider);
if (Filament.gltfio$WebpProvider.isWebpSupported()) {
webpProvider = new Filament.gltfio$WebpProvider(this.engine);
resourceLoader.addWebpProvider("image/webp", webpProvider);
}
let remaining = Object.keys(this.srcBlobResources).length;
for (const name in this.srcBlobResources) {
this.srcBlobResources[name].arrayBuffer().then(buffer => {
const desc = getBufferDescriptor(new Uint8Array(buffer));
resourceLoader.addResourceData(name, getBufferDescriptor(desc));
if (--remaining === 0) {
doneAddingResources(resourceLoader, stbProvider, ktx2Provider);
doneAddingResources(resourceLoader, stbProvider, ktx2Provider, webpProvider);
}
});
}
+1 -1
View File
@@ -49,7 +49,7 @@ to perform a number of URL replacements in addition to the copy. These replaceme
described in [`docs_src/build/duplicates.json`].
### Core concept docs
The primary design of Filament as a phyiscally-based renderer and details of its materials
The primary design of Filament as a physically-based renderer and details of its materials
system are described in `Filament.md.html` and `Materials.md.html`, respectively. These two
documents are written in [`markdeep`]. To embed them into our book, we
1. Convert the markdeep into html
+1 -1
View File
@@ -10,6 +10,6 @@ sortedcontainers==2.4.0
trio==0.31.0
trio-websocket==0.12.2
typing_extensions==4.15.0
urllib3==2.6.0
urllib3==2.6.3
websocket-client==1.9.0
wsproto==1.2.0
+7 -4
View File
@@ -161,7 +161,7 @@ The type and range of each property is described in table [standardPropertiesTyp
vertex. `microThickness` represent the thickness of the thin layer of an object, and can
generally be provided as a constant value. For example, a 1mm thin hollow sphere of radius 1m,
would have a `thickness` of 1 and a `microThickness` of 0.001. Dispersion controls the angular
separation of colors transmitting through a volume, and can be set by a contant value.
separation of colors transmitting through a volume, and can be set by a constant value.
Currently `thickness` and `dispersion` are not used when `refractionType` is set to `thin`.
### Base color
@@ -671,6 +671,9 @@ Water | 55 | 0.36
Crown Glass | 59 | 0.33
[Table [commonMatDispersion]: Dispersion of common materials]
![Figure [dispersionProperty]: `dispersion` varying from 0.0
(left) to 5.0 (right)](images/materials/dispersion.png)
## Subsurface model
### Thickness
@@ -2477,9 +2480,9 @@ type aliases:
**uint2** | uvec2 | A vector of 2 unsigned integers
**uint3** | uvec3 | A vector of 3 unsigned integers
**uint4** | uvec4 | A vector of 4 unsigned integers
**float2** | float2 | A vector of 2 floats
**float3** | float3 | A vector of 3 floats
**float4** | float4 | A vector of 4 floats
**float2** | vec2 | A vector of 2 floats
**float3** | vec3 | A vector of 3 floats
**float4** | vec4 | A vector of 4 floats
**float4x4** | mat4 | A 4x4 float matrix
**float3x3** | mat3 | A 3x3 float matrix
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 MiB

+12 -2
View File
@@ -307,7 +307,7 @@ class FilamentViewer extends LitElement {
asyncInterval: 30
};
const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider) => {
const doneAddingResources = (resourceLoader, stbProvider, ktx2Provider, webpProvider) => {
this.srcBlobResources = {};
resourceLoader.asyncBeginLoad(this.asset);
const timer = setInterval(() => {
@@ -318,6 +318,9 @@ class FilamentViewer extends LitElement {
resourceLoader.delete();
stbProvider.delete();
ktx2Provider.delete();
if (webpProvider) {
webpProvider.delete();
}
this.animator = this.asset.getInstance().getAnimator();
this.animationStartTime = Date.now();
}
@@ -335,18 +338,25 @@ class FilamentViewer extends LitElement {
const stbProvider = new Filament.gltfio$StbProvider(this.engine);
const ktx2Provider = new Filament.gltfio$Ktx2Provider(this.engine);
let webpProvider = null;
resourceLoader.addStbProvider("image/jpeg", stbProvider);
resourceLoader.addStbProvider("image/png", stbProvider);
resourceLoader.addKtx2Provider("image/ktx2", ktx2Provider);
if (Filament.gltfio$WebpProvider.isWebpSupported()) {
webpProvider = new Filament.gltfio$WebpProvider(this.engine);
resourceLoader.addWebpProvider("image/webp", webpProvider);
}
let remaining = Object.keys(this.srcBlobResources).length;
for (const name in this.srcBlobResources) {
this.srcBlobResources[name].arrayBuffer().then(buffer => {
const desc = getBufferDescriptor(new Uint8Array(buffer));
resourceLoader.addResourceData(name, getBufferDescriptor(desc));
if (--remaining === 0) {
doneAddingResources(resourceLoader, stbProvider, ktx2Provider);
doneAddingResources(resourceLoader, stbProvider, ktx2Provider, webpProvider);
}
});
}
+7 -2
View File
@@ -97,7 +97,6 @@ set(SRCS
src/RenderableManager.cpp
src/Renderer.cpp
src/RendererUtils.cpp
src/ResourceAllocator.cpp
src/ResourceList.cpp
src/Scene.cpp
src/ShadowMap.cpp
@@ -108,6 +107,7 @@ set(SRCS
src/SwapChain.cpp
src/Sync.cpp
src/Texture.cpp
src/TextureCache.cpp
src/ToneMapper.cpp
src/TransformManager.cpp
src/UniformBuffer.cpp
@@ -158,6 +158,7 @@ set(SRCS
src/fg/FrameGraphTexture.cpp
src/fg/PassNode.cpp
src/fg/ResourceNode.cpp
src/fg/ResourceCreationContext.cpp
src/fsr.cpp
src/ZstdHelper.cpp
)
@@ -168,6 +169,7 @@ set(PRIVATE_HDRS
src/Bimap.h
src/BufferPoolAllocator.h
src/ColorSpaceUtils.h
src/compact.h
src/Culler.h
src/DFG.h
src/FilamentAPI-impl.h
@@ -188,16 +190,17 @@ set(PRIVATE_HDRS
src/RenderPass.h
src/RenderPrimitive.h
src/RendererUtils.h
src/ResourceAllocator.h
src/ResourceList.h
src/ShadowMap.h
src/ShadowMapManager.h
src/SharedHandle.h
src/TextureCache.h
src/UniformBuffer.h
src/components/CameraManager.h
src/components/LightManager.h
src/components/RenderableManager.h
src/components/TransformManager.h
src/details/AsyncHelpers.h
src/details/BufferAllocator.h
src/details/BufferObject.h
src/details/Camera.h
@@ -232,6 +235,7 @@ set(PRIVATE_HDRS
src/ds/TypedUniformBuffer.h
src/fg/Blackboard.h
src/fg/FrameGraph.h
src/fg/FrameGraphDummyLink.h
src/fg/FrameGraphId.h
src/fg/FrameGraphPass.h
src/fg/FrameGraphRenderPass.h
@@ -241,6 +245,7 @@ set(PRIVATE_HDRS
src/fg/details/DependencyGraph.h
src/fg/details/PassNode.h
src/fg/details/Resource.h
src/fg/details/ResourceAllocator.h
src/fg/details/ResourceNode.h
src/fg/details/Utilities.h
src/fsr.h
+1
View File
@@ -28,6 +28,7 @@ set(SRCS
src/BackendUtils.cpp
src/BlobCacheKey.cpp
src/Callable.cpp
src/CallbackHandler.cpp
src/CallbackManager.cpp
src/CircularBuffer.cpp
src/CommandBufferQueue.cpp
@@ -64,7 +64,7 @@ public:
virtual void post(void* user, Callback callback) = 0;
protected:
virtual ~CallbackHandler() = default;
virtual ~CallbackHandler();
};
} // namespace filament::backend
+10 -4
View File
@@ -499,7 +499,7 @@ using descriptor_set_t = uint8_t;
using descriptor_binding_t = uint8_t;
struct DescriptorSetLayoutBinding {
struct DescriptorSetLayoutDescriptor {
static bool isSampler(DescriptorType type) noexcept {
return int(type) <= int(DescriptorType::SAMPLER_EXTERNAL);
}
@@ -513,8 +513,8 @@ struct DescriptorSetLayoutBinding {
DescriptorFlags flags = DescriptorFlags::NONE;
uint16_t count = 0;
friend bool operator==(DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) noexcept {
friend bool operator==(DescriptorSetLayoutDescriptor const& lhs,
DescriptorSetLayoutDescriptor const& rhs) noexcept {
return lhs.type == rhs.type &&
lhs.flags == rhs.flags &&
lhs.count == rhs.count &&
@@ -1385,7 +1385,7 @@ static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
struct DescriptorSetLayout {
std::variant<utils::StaticString, utils::CString, std::monostate> label;
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
utils::FixedCapacityVector<DescriptorSetLayoutDescriptor> descriptors;
};
//! blending equation function
@@ -1715,6 +1715,12 @@ using CompositorTiming = Platform::CompositorTiming;
using AsynchronousMode = Platform::AsynchronousMode;
using AsyncCallId = uint32_t;
static constexpr AsyncCallId InvalidAsyncCallId = std::numeric_limits<AsyncCallId>::max();
using AsynchronousMode = Platform::AsynchronousMode;
} // namespace filament::backend
template<> struct utils::EnableBitMaskOperators<filament::backend::ShaderStageFlags>
+19 -1
View File
@@ -288,7 +288,8 @@ public:
/**
* Set to `true` to forcibly disable parallel shader compilation in the backend.
* Currently only honored by the GL and Metal backends.
* Currently only honored by the GL and Metal backends, and the Vulkan backend
* when some experimental features are enabled.
*/
bool disableParallelShaderCompile = false;
@@ -319,6 +320,12 @@ public:
*/
StereoscopicType stereoscopicType = StereoscopicType::NONE;
/*
* The number of eyes to render when stereoscopic rendering is enabled. Supported values are
* between 1 and Engine::getMaxStereoscopicEyes() (inclusive).
*/
uint8_t stereoscopicEyeCount = 2;
/**
* Assert the native window associated to a SwapChain is valid when calling makeCurrent().
* This is only supported for:
@@ -340,6 +347,17 @@ public:
*/
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
/**
* Enables asynchronous pipeline cache preloading, if supported on this device.
* This is only supported for:
* - VulkanPlatform
* When the following device extensions are available:
* - VK_KHR_dynamic_rendering
* - VK_EXT_vertex_input_dynamic_state
* Should be enabled only for devices where it has been shown this is effective.
*/
bool vulkanEnableAsyncPipelineCachePrewarming = false;
/**
* Bypass the staging buffer because the device is of Unified Memory Architecture.
* This is only supported for:
@@ -50,7 +50,13 @@ public:
descriptor_binding_t binding;
};
struct DescriptorSetLayoutBinding {
descriptor_set_t set;
DescriptorSetLayout layout;
};
using SpecializationConstant = std::variant<int32_t, float, bool>;
using DescriptorSetLayoutArray = utils::FixedCapacityVector<DescriptorSetLayoutBinding>;
struct Uniform { // For ES2 support
utils::CString name; // full qualified name of the uniform field
@@ -147,6 +153,19 @@ public:
return mDescriptorBindings;
}
inline Program& descriptorLayout(backend::descriptor_set_t set,
DescriptorSetLayout descriptorLayout) noexcept {
mDescriptorLayouts.push_back({
.set = set,
.layout = std::move(descriptorLayout),
});
return *this;
}
const DescriptorSetLayoutArray& getDescriptorSetLayouts() const noexcept {
return mDescriptorLayouts;
}
utils::FixedCapacityVector<PushConstant> const& getPushConstants(
ShaderStage stage) const noexcept {
return mPushConstants[static_cast<uint8_t>(stage)];
@@ -176,6 +195,11 @@ private:
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
DescriptorSetInfo mDescriptorBindings;
// Descriptions for descriptor set layouts that may be used for this Program, which
// can be useful for attempting to compile the pipeline ahead of time.
DescriptorSetLayoutArray mDescriptorLayouts =
DescriptorSetLayoutArray::with_capacity(MAX_DESCRIPTOR_SET_COUNT);
// For ES2 support only
AttributesInfo mAttributes;
BindingUniformsInfo mBindingUniformsInfo;
@@ -64,7 +64,7 @@ protected:
std::vector<int> mAttribs;
// For shared contexts
static constexpr int SHARED_CONTEXT_NUM = 1;
static constexpr int SHARED_CONTEXT_NUM = 2;
std::vector<HGLRC> mAdditionalContexts;
std::atomic<int> mNextFreeSharedContextIndex{0};
};
@@ -28,6 +28,7 @@
#include <utils/Hash.h>
#include <utils/PrivateImplementation.h>
#include <cstring>
#include <cstddef>
#include <functional>
#include <string>
@@ -61,8 +62,15 @@ public:
return std::hash<std::string>{}(s.data());
}
};
// Note: utils::CString::operator== has an edge case that breaks for the extension set.
// Instead, we'll provide our own comparator.
struct ExtensionEqualFn {
bool operator()(utils::CString const& a, utils::CString const& b) const noexcept {
return strcmp(a.c_str(), b.c_str()) == 0;
}
};
// Utility for managing device or instance extensions during initialization.
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn>;
using ExtensionSet = std::unordered_set<utils::CString, ExtensionHashFn, ExtensionEqualFn>;
/**
* A collection of handles to objects and metadata that comprises a Vulkan context. The client
@@ -437,6 +445,50 @@ protected:
std::shared_ptr<VulkanCmdFence> fenceStatus;
};
/**
* Creates the VkInstance used by Filament's Vulkan backend.
*
* This method can be overridden in subclasses to customize VkInstance creation, such as
* adding application-specific layers or extensions.
*
* The provided `createInfo` contains layers and extensions required by Filament.
* If you override this method and need to modify the `createInfo` struct, you must first
* make a copy of it and modify the copy.
*
* @param createInfo The VkInstanceCreateInfo prepared by Filament.
* @return The created VkInstance, or VK_NULL_HANDLE on failure.
*/
virtual VkInstance createVkInstance(VkInstanceCreateInfo const& createInfo) noexcept;
/**
* Selects a VkPhysicalDevice (GPU) for Filament's Vulkan backend to use.
*
* This method can be overridden in subclasses to implement custom GPU selection logic.
* For example, an application might override this to prefer a discrete GPU over an
* integrated one based on device properties.
*
* The default implementation selects the first device that meets Filament's requirements.
*
* @param instance The VkInstance to enumerate devices from.
* @return The selected VkPhysicalDevice, or VK_NULL_HANDLE if no suitable device is found.
*/
virtual VkPhysicalDevice selectVkPhysicalDevice(VkInstance instance) noexcept;
/**
* Creates the VkDevice used by Filament's Vulkan backend.
*
* This method can be overridden in subclasses to customize VkDevice creation, such as
* adding application-specific extensions or enabling features.
*
* The provided `createInfo` contains extensions and features required by Filament.
* If you override this method and need to modify the `createInfo` struct, you must first
* make a copy of it and modify the copy.
*
* @param createInfo The VkDeviceCreateInfo prepared by Filament.
* @return The created VkDevice, or VK_NULL_HANDLE on failure.
*/
virtual VkDevice createVkDevice(VkDeviceCreateInfo const& createInfo) noexcept;
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
virtual ExtensionSet getSwapchainInstanceExtensions() const = 0;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
@@ -450,6 +502,36 @@ protected:
bool isTransientAttachmentSupported() const noexcept;
private:
/**
* Contains information about features that should be requested
* when calling vkCreateDevice, based on feature support from
* vkGetPhysicalDeviceFeatures2.
*/
struct MiscDeviceFeatures {
/**
* This allows creation of a VkGraphicsPipeline without a
* render pass specified.
*/
bool dynamicRendering;
/**
* Allows creation of a 2d image view, or 2d image view array,
* to be created from a 3d VkImage.
*/
bool imageView2Don3DImage;
};
void createInstance(ExtensionSet const& requiredExts) noexcept;
void queryAndSetDeviceFeatures(Platform::DriverConfig const& driverConfig,
ExtensionSet const& instExts, ExtensionSet const& deviceExts,
void* sharedContext) noexcept;
void createLogicalDeviceAndQueues(ExtensionSet const& deviceExtensions,
VkPhysicalDeviceFeatures const& features,
VkPhysicalDeviceVulkan11Features const& vk11Features, bool createProtectedQueue,
MiscDeviceFeatures const& requestedFeatures) noexcept;
friend struct VulkanPlatformPrivate;
};
@@ -70,6 +70,11 @@ public:
// is where the driver can execute user callbacks.
virtual void purge() noexcept = 0;
// Called from the engine thread (render-thread) to execute the `callback` via the `handler` if
// it is available. Otherwise, if `handler` is null, the `callback` is executed on the main
// thread via `purge()`.
virtual void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback) = 0;
virtual ShaderModel getShaderModel() const noexcept = 0;
// The shader languages used for shaders for this driver in order of preference, used to inform
@@ -74,8 +74,9 @@
#define APPLY10(M, A, ...) EXPAND(M(A)), EXPAND(APPLY9(M, __VA_ARGS__))
#define APPLY11(M, A, ...) EXPAND(M(A)), EXPAND(APPLY10(M, __VA_ARGS__))
#define APPLY12(M, A, ...) EXPAND(M(A)), EXPAND(APPLY11(M, __VA_ARGS__))
#define APPLY_N__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, X, ...) APPLY##X
#define APPLY(M, ...) EXPAND(EXPAND(APPLY_N__(M, __VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))(M, __VA_ARGS__))
#define APPLY13(M, A, ...) EXPAND(M(A)), EXPAND(APPLY12(M, __VA_ARGS__))
#define APPLY_N__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, X, ...) APPLY##X
#define APPLY(M, ...) EXPAND(EXPAND(APPLY_N__(M, __VA_ARGS__, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))(M, __VA_ARGS__))
#define PAIR_ARGS_0(M, ...)
@@ -91,11 +92,12 @@
#define PAIR_ARGS_10(M, X, Y, ...) M(X, Y), EXPAND(PAIR_ARGS_9(M, __VA_ARGS__))
#define PAIR_ARGS_11(M, X, Y, ...) M(X, Y), EXPAND(PAIR_ARGS_10(M, __VA_ARGS__))
#define PAIR_ARGS_12(M, X, Y, ...) M(X, Y), EXPAND(PAIR_ARGS_11(M, __VA_ARGS__))
#define PAIR_ARGS_13(M, X, Y, ...) M(X, Y), EXPAND(PAIR_ARGS_12(M, __VA_ARGS__))
#define PAIR_ARGS_N__(_0, E1, _1, E2, _2, E3, _3, E4, _4, E5, _5, E6, _6, E7, _7, E8, _8, E9, _9, E10, _10, E11, _11, E12, _12, X, ...) PAIR_ARGS_##X
#define PAIR_ARGS_N__(_0, E1, _1, E2, _2, E3, _3, E4, _4, E5, _5, E6, _6, E7, _7, E8, _8, E9, _9, E10, _10, E11, _11, E12, _12, E13, _13, X, ...) PAIR_ARGS_##X
#define PAIR_ARGS_N(M, ...) \
EXPAND(EXPAND(PAIR_ARGS_N__(0, ##__VA_ARGS__, 12, E, 11, E, 10, E, 9, E, 8, E, 7, E, 6, E, 5, E, 4, E, 3, E, 2, E, 1, E, 0))(M, __VA_ARGS__))
EXPAND(EXPAND(PAIR_ARGS_N__(0, ##__VA_ARGS__, 13, E, 12, E, 11, E, 10, E, 9, E, 8, E, 7, E, 6, E, 5, E, 4, E, 3, E, 2, E, 1, E, 0))(M, __VA_ARGS__))
#define ARG(T, P) T P
@@ -192,11 +194,27 @@ DECL_DRIVER_API_TAGGED_R_N(backend::IndexBufferHandle, createIndexBuffer,
uint32_t, indexCount,
backend::BufferUsage, usage)
DECL_DRIVER_API_TAGGED_R_N(backend::IndexBufferHandle, createIndexBufferAsync,
backend::ElementType, elementType,
uint32_t, indexCount,
backend::BufferUsage, usage,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_TAGGED_R_N(backend::BufferObjectHandle, createBufferObject,
uint32_t, byteCount,
backend::BufferObjectBinding, bindingType,
backend::BufferUsage, usage)
DECL_DRIVER_API_TAGGED_R_N(backend::BufferObjectHandle, createBufferObjectAsync,
uint32_t, byteCount,
backend::BufferObjectBinding, bindingType,
backend::BufferUsage, usage,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTexture,
backend::SamplerType, target,
uint8_t, levels,
@@ -207,6 +225,19 @@ DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTextureAsync,
backend::SamplerType, target,
uint8_t, levels,
backend::TextureFormat, format,
uint8_t, samples,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::TextureUsage, usage,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTextureView,
backend::TextureHandle, texture,
uint8_t, baseLevel,
@@ -219,6 +250,16 @@ DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTextureViewSwizzle,
backend::TextureSwizzle, b,
backend::TextureSwizzle, a)
DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTextureViewSwizzleAsync,
backend::TextureHandle, texture,
backend::TextureSwizzle, r,
backend::TextureSwizzle, g,
backend::TextureSwizzle, b,
backend::TextureSwizzle, a,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, createTextureExternalImage2,
backend::SamplerType, target,
backend::TextureFormat, format,
@@ -254,6 +295,20 @@ DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, importTexture,
uint32_t, depth,
backend::TextureUsage, usage)
DECL_DRIVER_API_TAGGED_R_N(backend::TextureHandle, importTextureAsync,
intptr_t, id,
backend::SamplerType, target,
uint8_t, levels,
backend::TextureFormat, format,
uint8_t, samples,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::TextureUsage, usage,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_TAGGED_R_N(backend::RenderPrimitiveHandle, createRenderPrimitive,
backend::VertexBufferHandle, vbh,
backend::IndexBufferHandle, ibh,
@@ -357,6 +412,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthStencilResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isDepthStencilBlitSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedTexturesSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isDepthClampSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAsynchronousModeEnabled)
DECL_DRIVER_API_SYNCHRONOUS_0(uint8_t, getMaxDrawBuffers)
DECL_DRIVER_API_SYNCHRONOUS_0(size_t, getMaxUniformBufferSize)
DECL_DRIVER_API_SYNCHRONOUS_N(size_t, getMaxTextureSize, backend::SamplerType, target)
@@ -382,16 +438,40 @@ DECL_DRIVER_API_N(setVertexBufferObject,
uint32_t, index,
backend::BufferObjectHandle, bufferObject)
DECL_DRIVER_API_R_N(backend::AsyncCallId, setVertexBufferObjectAsync,
backend::VertexBufferHandle, vbh,
uint32_t, index,
backend::BufferObjectHandle, bufferObject,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_N(updateIndexBuffer,
backend::IndexBufferHandle, ibh,
backend::BufferDescriptor&&, data,
uint32_t, byteOffset)
DECL_DRIVER_API_R_N(backend::AsyncCallId, updateIndexBufferAsync,
backend::IndexBufferHandle, ibh,
backend::BufferDescriptor&&, data,
uint32_t, byteOffset,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_N(updateBufferObject,
backend::BufferObjectHandle, ibh,
backend::BufferDescriptor&&, data,
uint32_t, byteOffset)
DECL_DRIVER_API_R_N(backend::AsyncCallId, updateBufferObjectAsync,
backend::BufferObjectHandle, ibh,
backend::BufferDescriptor&&, data,
uint32_t, byteOffset,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_N(updateBufferObjectUnsynchronized,
backend::BufferObjectHandle, boh,
backend::BufferDescriptor&&, data,
@@ -411,6 +491,20 @@ DECL_DRIVER_API_N(update3DImage,
uint32_t, depth,
backend::PixelBufferDescriptor&&, data)
DECL_DRIVER_API_R_N(backend::AsyncCallId, update3DImageAsync,
backend::TextureHandle, th,
uint32_t, level,
uint32_t, xoffset,
uint32_t, yoffset,
uint32_t, zoffset,
uint32_t, width,
uint32_t, height,
uint32_t, depth,
backend::PixelBufferDescriptor&&, data,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_N(generateMipmaps,
backend::TextureHandle, th)
@@ -577,6 +671,19 @@ DECL_DRIVER_API_N(dispatchCompute,
DECL_DRIVER_API_N(scissor,
Viewport, scissor)
/*
* Asyncronous operation helpers
* --------------------
*/
DECL_DRIVER_API_R_N(backend::AsyncCallId, queueCommandAsync,
utils::Invocable<void(void)>&&, command,
backend::CallbackHandler*, handler,
backend::CallbackHandler::Callback, callback,
void*, user)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, cancelAsyncJob, backend::AsyncCallId, jobId)
#if defined(__clang__)
#pragma clang diagnostic pop
+26
View File
@@ -0,0 +1,26 @@
/*
* 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 <backend/CallbackHandler.h>
namespace filament::backend {
// Define the virtual destructor here to avoid a compiler warning that we treat as an error.
// "'CallbackHandler' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit"
// The warning occurs when a class inherits from CallbackHandler.
CallbackHandler::~CallbackHandler() = default;
} // namespace filament::backend
+30 -7
View File
@@ -19,13 +19,17 @@
#include <backend/PixelBufferDescriptor.h>
#include <cstdint>
#include <cstring>
#include <stddef.h>
#include <stdint.h>
#include <math/scalar.h>
#include <math/half.h>
#include <utils/debug.h>
#include <utils/Log.h>
#include <utils/ostream.h>
namespace filament {
namespace backend {
@@ -76,7 +80,7 @@ public:
}
}
// Converts a n-channel image of UBYTE, INT, UINT, or FLOAT to a different type.
// Converts a n-channel image of UBYTE, INT, UINT, HALF, or FLOAT to a different type.
template<typename dstComponentType, typename srcComponentType>
static void reshapeImage(uint8_t* UTILS_RESTRICT dest, const uint8_t* UTILS_RESTRICT src,
size_t srcBytesPerRow,
@@ -91,6 +95,8 @@ public:
UTILS_ASSUME(minChannelCount <= 4);
dest += (dstRowOffset * dstBytesPerRow);
const int inds[4] = { swizzle ? 2 : 0, 1, swizzle ? 0 : 2, 3 };
utils::slog.e <<"dstMaxValue=" << +dstMaxValue << utils::io::endl;
for (size_t row = 0; row < height; ++row) {
const srcComponentType* in = (const srcComponentType*) src;
dstComponentType* out = (dstComponentType*)dest + (dstColumnOffset * dstChannelCount);
@@ -119,6 +125,7 @@ public:
static bool reshapeImage(PixelBufferDescriptor* UTILS_RESTRICT dst, PixelDataType srcType,
uint32_t srcChannelCount, const uint8_t* UTILS_RESTRICT srcBytes, int srcBytesPerRow,
int width, int height, bool swizzle) {
using namespace utils;
size_t dstChannelCount;
switch (dst->format) {
case PixelDataFormat::R_INTEGER: dstChannelCount = 1; break;
@@ -129,7 +136,9 @@ public:
case PixelDataFormat::RG: dstChannelCount = 2; break;
case PixelDataFormat::RGB: dstChannelCount = 3; break;
case PixelDataFormat::RGBA: dstChannelCount = 4; break;
default: return false;
default:
slog.e << "DataReshaper: unsupported dst->format: " << (int)dst->format << io::endl;
return false;
}
void (*reshaper)(uint8_t* dest, const uint8_t* src, size_t srcBytesPerRow,
size_t srcChannelCount,
@@ -155,7 +164,10 @@ public:
case FLOAT: reshaper = reshapeImage<uint8_t, float>; break;
case INT: reshaper = reshapeImage<uint8_t, int32_t>; break;
case UINT: reshaper = reshapeImage<uint8_t, uint32_t>; break;
default: return false;
case HALF: reshaper = reshapeImage<uint8_t, math::half>; break;
default:
slog.e << "DataReshaper: UBYTE dst, unsupported srcType: " << (int)srcType << io::endl;
return false;
}
break;
case FLOAT:
@@ -164,7 +176,9 @@ public:
case FLOAT: reshaper = reshapeImage<float, float>; break;
case INT: reshaper = reshapeImage<float, int32_t>; break;
case UINT: reshaper = reshapeImage<float, uint32_t>; break;
default: return false;
default:
slog.e << "DataReshaper: FLOAT dst, unsupported srcType: " << (int)srcType << io::endl;
return false;
}
break;
case INT:
@@ -173,7 +187,9 @@ public:
case FLOAT: reshaper = reshapeImage<int32_t, float>; break;
case INT: reshaper = reshapeImage<int32_t, int32_t>; break;
case UINT: reshaper = reshapeImage<int32_t, uint32_t>; break;
default: return false;
default:
slog.e << "DataReshaper: INT dst, unsupported srcType: " << (int)srcType << io::endl;
return false;
}
break;
case UINT:
@@ -182,16 +198,21 @@ public:
case FLOAT: reshaper = reshapeImage<uint32_t, float>; break;
case INT: reshaper = reshapeImage<uint32_t, int32_t>; break;
case UINT: reshaper = reshapeImage<uint32_t, uint32_t>; break;
default: return false;
default:
slog.e << "DataReshaper: UINT dst, unsupported srcType: " << (int)srcType << io::endl;
return false;
}
break;
case HALF:
switch (srcType) {
case HALF: reshaper = copyImage; break;
default: return false;
default:
slog.e << "DataReshaper: HALF dst, unsupported srcType: " << (int)srcType << io::endl;
return false;
}
break;
default:
slog.e << "DataReshaper: unsupported dst->type: " << (int)dst->type << io::endl;
return false;
}
uint8_t* dstBytes = (uint8_t*) dst->buffer;
@@ -200,6 +221,7 @@ public:
reshaper(dstBytes, srcBytes, srcBytesPerRow, srcChannelCount,
dst->top, dst->left, dstBytesPerRow,
dstChannelCount, width, height, swizzle);
return true;
}
};
@@ -209,6 +231,7 @@ template<> inline int32_t getMaxValue() { return 0x7fffffff; }
template<> inline uint32_t getMaxValue() { return 0xffffffff; }
template<> inline uint16_t getMaxValue() { return 0x3c00; } // 0x3c00 is 1.0 in half-float.
template<> inline uint8_t getMaxValue() { return 0xff; }
template<> inline math::half getMaxValue() { return math::half(1.0f); }
} // namespace backend
} // namespace filament
+5 -1
View File
@@ -28,7 +28,9 @@
#include <utils/Logger.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/JobSystem.h>
#include <utils/ostream.h>
#include <utils/Panic.h>
#include <math/half.h>
#include <math/vec2.h>
@@ -47,10 +49,12 @@ using namespace filament::math;
namespace filament::backend {
DriverBase::DriverBase() noexcept {
DriverBase::DriverBase(const Platform::DriverConfig& driverConfig) noexcept
: mDriverConfig(driverConfig) {
if constexpr (UTILS_HAS_THREADING) {
// This thread services user callbacks
mServiceThread = std::thread([this]() {
JobSystem::setThreadName("ServiceThread");
do {
auto& serviceThreadCondition = mServiceThreadCondition;
auto& serviceThreadCallbackQueue = mServiceThreadCallbackQueue;
+23 -12
View File
@@ -72,22 +72,24 @@ struct HwVertexBuffer : public HwBase {
};
struct HwBufferObject : public HwBase {
uint32_t byteCount{};
uint32_t byteCount : 31;
uint32_t asynchronous : 1;
HwBufferObject() noexcept = default;
explicit HwBufferObject(uint32_t byteCount) noexcept : byteCount(byteCount) {}
explicit HwBufferObject(uint32_t byteCount, bool async) noexcept : byteCount(byteCount), asynchronous(async) {}
};
struct HwMemoryMappedBuffer : public HwBase {
};
struct HwIndexBuffer : public HwBase {
uint32_t count : 27;
uint32_t count : 26; // 67M indices
uint32_t elementSize : 5;
uint32_t asynchronous : 1;
HwIndexBuffer() noexcept : count{}, elementSize{} { }
HwIndexBuffer(uint8_t elementSize, uint32_t indexCount) noexcept :
count(indexCount), elementSize(elementSize) {
HwIndexBuffer(uint8_t elementSize, uint32_t indexCount, bool async) noexcept :
count(indexCount), elementSize(elementSize), asynchronous(async) {
// we could almost store elementSize on 4 bits because it's never > 16 and never 0
assert_invariant(elementSize > 0 && elementSize <= 16);
assert_invariant(indexCount < (1u << 27));
@@ -120,16 +122,19 @@ struct HwTexture : public HwBase {
uint8_t levels : 4; // This allows up to 15 levels (max texture size of 32768 x 32768)
uint8_t samples : 4; // Sample count per pixel (should always be a power of 2)
TextureFormat format{};
uint8_t reserved0 = 0;
struct {
uint8_t asynchronous : 1;
uint8_t reserved : 7;
};
TextureUsage usage{};
uint16_t reserved1 = 0;
HwStream* hwStream = nullptr;
HwTexture() noexcept : levels{}, samples{} {}
HwTexture(backend::SamplerType target, uint8_t levels, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureFormat fmt, TextureUsage usage) noexcept
: width(width), height(height), depth(depth),
target(target), levels(levels), samples(samples), format(fmt), usage(usage) { }
HwTexture(backend::SamplerType target, uint8_t levels, uint8_t samples, uint32_t width,
uint32_t height, uint32_t depth, TextureFormat fmt, TextureUsage usage, bool async) noexcept
: width(width), height(height), depth(depth), target(target), levels(levels),
samples(samples), format(fmt), asynchronous(async), usage(usage) {}
};
struct HwRenderTarget : public HwBase {
@@ -172,7 +177,7 @@ struct HwTimerQuery : public HwBase {
class DriverBase : public Driver {
public:
DriverBase() noexcept;
explicit DriverBase(const Platform::DriverConfig& driverConfig) noexcept;
~DriverBase() noexcept override;
void purge() noexcept final;
@@ -205,7 +210,7 @@ public:
});
}
void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback);
void scheduleCallback(CallbackHandler* handler, void* user, CallbackHandler::Callback callback) final;
// --------------------------------------------------------------------------------------------
// Privates
@@ -214,6 +219,10 @@ public:
protected:
class CallbackDataDetails;
Platform::DriverConfig const& getDriverConfig() const noexcept {
return mDriverConfig;
}
void scheduleDestroy(BufferDescriptor&& buffer) {
if (buffer.hasCallback()) {
scheduleDestroySlow(std::move(buffer));
@@ -228,6 +237,8 @@ protected:
void debugCommandEnd(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
private:
const Platform::DriverConfig mDriverConfig;
std::mutex mPurgeLock;
std::vector<std::pair<void*, CallbackHandler::Callback>> mCallbacks;
+1 -1
View File
@@ -194,7 +194,7 @@ void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
callback = mDebugUpdateStat;
}
if (callback) {
(*callback)(key, intValue, "");
(*callback)(key, intValue, utils::CString(""));
}
}
+1
View File
@@ -161,6 +161,7 @@ private:
const std::function<void(const BufferState&, MetalBuffer*, uint32_t)>& f);
backend::StereoscopicType const mStereoscopicType;
backend::AsynchronousMode const mAsynchronousMode;
};
} // namespace backend
+123 -2
View File
@@ -111,14 +111,16 @@ Dispatcher MetalDriver::getDispatcher() const noexcept {
MetalDriver::MetalDriver(
PlatformMetal* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
: DriverBase(driverConfig),
mPlatform(*platform),
mContext(new MetalContext),
mHandleAllocator(
"Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags),
mStereoscopicType(driverConfig.stereoscopicType) {
mStereoscopicType(driverConfig.stereoscopicType),
mAsynchronousMode(driverConfig.asynchronousMode) {
mContext->driver = this;
TrackedMetalBuffer::setPlatform(platform);
@@ -461,6 +463,12 @@ void MetalDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elem
mHandleAllocator.associateTagToHandle(ibh.getId(), std::move(tag));
}
void MetalDriver::createIndexBufferAsyncR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage, CallbackHandler* handler,
CallbackHandler::Callback callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void MetalDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, utils::ImmutableCString&& tag) {
auto* bufferObject =
@@ -472,6 +480,12 @@ void MetalDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteC
mHandleAllocator.associateTagToHandle(boh.getId(), std::move(tag));
}
void MetalDriver::createBufferObjectAsyncR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, CallbackHandler* handler,
CallbackHandler::Callback callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
// fixme: TextureUsage is a bitfield
inline const char* stringify(TextureUsage usage) {
switch (usage) {
@@ -523,6 +537,13 @@ void MetalDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void MetalDriver::createTextureAsyncR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height,
uint32_t depth, TextureUsage usage, CallbackHandler* handler,
CallbackHandler::Callback callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void MetalDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount, utils::ImmutableCString&& tag) {
MetalTexture const* src = handle_cast<MetalTexture>(srch);
@@ -543,6 +564,13 @@ void MetalDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTextu
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void MetalDriver::createTextureViewSwizzleAsyncR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
backend::TextureSwizzle a, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void MetalDriver::createTextureExternalImage2R(Handle<HwTexture> th,
backend::SamplerType target,
backend::TextureFormat format,
@@ -602,6 +630,14 @@ void MetalDriver::importTextureR(Handle<HwTexture> th, intptr_t i,
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void MetalDriver::importTextureAsyncR(Handle<HwTexture> th, intptr_t i,
SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height,
uint32_t depth, TextureUsage usage, CallbackHandler* handler,
CallbackHandler::Callback callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void MetalDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType pt, utils::ImmutableCString&& tag) {
@@ -794,14 +830,26 @@ Handle<HwIndexBuffer> MetalDriver::createIndexBufferS() noexcept {
return alloc_handle<MetalIndexBuffer>();
}
Handle<HwIndexBuffer> MetalDriver::createIndexBufferAsyncS() noexcept {
return alloc_handle<MetalIndexBuffer>();
}
Handle<HwBufferObject> MetalDriver::createBufferObjectS() noexcept {
return alloc_handle<MetalBufferObject>();
}
Handle<HwBufferObject> MetalDriver::createBufferObjectAsyncS() noexcept {
return alloc_handle<MetalBufferObject>();
}
Handle<HwTexture> MetalDriver::createTextureS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::createTextureAsyncS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::createTextureViewS() noexcept {
return alloc_handle<MetalTexture>();
}
@@ -810,6 +858,10 @@ Handle<HwTexture> MetalDriver::createTextureViewSwizzleS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::createTextureViewSwizzleAsyncS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::createTextureExternalImage2S() noexcept {
return alloc_handle<MetalTexture>();
}
@@ -826,6 +878,30 @@ Handle<HwTexture> MetalDriver::importTextureS() noexcept {
return alloc_handle<MetalTexture>();
}
Handle<HwTexture> MetalDriver::importTextureAsyncS() noexcept {
return alloc_handle<MetalTexture>();
}
AsyncCallId MetalDriver::update3DImageAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId MetalDriver::setVertexBufferObjectAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId MetalDriver::updateBufferObjectAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId MetalDriver::updateIndexBufferAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
Handle<HwRenderPrimitive> MetalDriver::createRenderPrimitiveS() noexcept {
return alloc_handle<MetalRenderPrimitive>();
}
@@ -874,6 +950,11 @@ Handle<HwDescriptorSet> MetalDriver::createDescriptorSetS() noexcept {
return alloc_handle<MetalDescriptorSet>();
}
AsyncCallId MetalDriver::queueCommandAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
void MetalDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
if (vbih) {
destruct_handle<MetalVertexBufferInfo>(vbih);
@@ -1216,6 +1297,10 @@ bool MetalDriver::isDepthClampSupported() {
return mContext->supportsDepthClamp;
}
bool MetalDriver::isAsynchronousModeEnabled() {
return false;
}
bool MetalDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
@@ -1291,6 +1376,12 @@ void MetalDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&
[&]() { return mHandleAllocator.getHandleTag(ibh.getId()).c_str_safe(); });
scheduleDestroy(std::move(data));
}
void MetalDriver::updateIndexBufferAsyncR(AsyncCallId jobId, Handle<HwIndexBuffer> ibh,
BufferDescriptor&& data, uint32_t byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& data,
uint32_t byteOffset) {
@@ -1307,6 +1398,12 @@ void MetalDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescripto
scheduleDestroy(std::move(data));
}
void MetalDriver::updateBufferObjectAsyncR(AsyncCallId jobId, Handle<HwBufferObject> boh,
BufferDescriptor&& data, uint32_t byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void MetalDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
BufferDescriptor&& data, uint32_t byteOffset) {
auto* bo = handle_cast<MetalBufferObject>(boh);
@@ -1332,6 +1429,12 @@ void MetalDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t ind
vertexBuffer->buffers[index] = bufferObject->getBuffer();
}
void MetalDriver::setVertexBufferObjectAsyncR(AsyncCallId jobId, Handle<HwVertexBuffer> vbh,
uint32_t index, Handle<HwBufferObject> boh, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void MetalDriver::update3DImage(Handle<HwTexture> th, uint32_t level,
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
@@ -1348,6 +1451,14 @@ void MetalDriver::update3DImage(Handle<HwTexture> th, uint32_t level,
th.getId(), level, xoffset, yoffset, zoffset, width, height, depth);
}
void MetalDriver::update3DImageAsyncR(AsyncCallId jobId, Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void MetalDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
// FIXME: implement setupExternalImage2
}
@@ -2300,6 +2411,16 @@ void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t
mmb->copy(*this, offset, std::move(data));
}
void MetalDriver::queueCommandAsyncR(AsyncCallId jobId, utils::Invocable<void()>&& command,
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
bool MetalDriver::cancelAsyncJob(AsyncCallId jobId) {
// TODO: implement this.
return false;
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<MetalDriver>;
+1 -1
View File
@@ -510,7 +510,7 @@ class MetalDescriptorSetLayout : public HwDescriptorSetLayout {
public:
MetalDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept;
const auto& getBindings() const noexcept { return mLayout.bindings; }
const auto& getBindings() const noexcept { return mLayout.descriptors; }
size_t getDynamicOffsetCount() const noexcept { return mDynamicOffsetCount; }
+9 -9
View File
@@ -544,7 +544,7 @@ MetalAttachment MetalSwapChain::acquireBaseDrawable() {
MetalBufferObject::MetalBufferObject(MetalContext& context, BufferObjectBinding bindingType,
BufferUsage usage, uint32_t byteCount)
: HwBufferObject(byteCount), buffer(context, bindingType, usage, byteCount) {}
: HwBufferObject(byteCount, false), buffer(context, bindingType, usage, byteCount) {}
void MetalBufferObject::updateBuffer(
void* data, size_t size, uint32_t byteOffset, TagResolver&& getHandleTag) {
@@ -635,7 +635,7 @@ MetalVertexBuffer::MetalVertexBuffer(MetalContext& context,
}
MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount, false),
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
@@ -664,7 +664,7 @@ void MetalProgram::initialize() {
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
: HwTexture(target, levels, samples, width, height, depth, format, usage, false), context(context) {
assert_invariant(target != SamplerType::SAMPLER_EXTERNAL);
devicePixelFormat = decidePixelFormat(&context, format);
@@ -756,7 +756,7 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, uint8_t baseLevel,
uint8_t levelCount) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
src->format, src->usage, false),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
@@ -767,7 +767,7 @@ MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, uint8
MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, TextureSwizzle r,
TextureSwizzle g, TextureSwizzle b, TextureSwizzle a) noexcept
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
src->format, src->usage, false),
context(context),
devicePixelFormat(src->devicePixelFormat),
externalImage(src->externalImage) {
@@ -786,13 +786,13 @@ MetalTexture::MetalTexture(MetalContext& context, MetalTexture const* src, Textu
MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t levels, TextureFormat format,
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
id<MTLTexture> metalTexture) noexcept
: HwTexture(target, levels, samples, width, height, depth, format, usage), context(context) {
: HwTexture(target, levels, samples, width, height, depth, format, usage, false), context(context) {
texture = metalTexture;
}
MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width,
uint32_t height, TextureUsage usage, CVPixelBufferRef image) noexcept
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage),
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage, false),
context(context),
externalImage(std::make_shared<MetalExternalImage>(
MetalExternalImage::createFromImage(context, image))) {
@@ -801,7 +801,7 @@ MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t
MetalTexture::MetalTexture(MetalContext& context, TextureFormat format, uint32_t width,
uint32_t height, TextureUsage usage, CVPixelBufferRef image, uint32_t plane) noexcept
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage),
: HwTexture(SamplerType::SAMPLER_EXTERNAL, 1, 1, width, height, 1, format, usage, false),
context(context),
externalImage(std::make_shared<MetalExternalImage>(
MetalExternalImage::createFromImagePlane(context, image, plane))) {
@@ -1441,7 +1441,7 @@ void MetalFence::cancel() {
MetalDescriptorSetLayout::MetalDescriptorSetLayout(DescriptorSetLayout&& l) noexcept
: mLayout(std::move(l)) {
size_t dynamicBindings = 0;
for (const auto& binding : mLayout.bindings) {
for (const auto& binding : mLayout.descriptors) {
if (any(binding.flags & DescriptorFlags::DYNAMIC_OFFSET)) {
dynamicBindings++;
}
+13 -2
View File
@@ -28,10 +28,13 @@
namespace filament::backend {
Driver* NoopDriver::create() {
return new NoopDriver();
Platform::DriverConfig driverConfig;
return new NoopDriver(driverConfig);
}
NoopDriver::NoopDriver() noexcept = default;
NoopDriver::NoopDriver(const Platform::DriverConfig& driverConfig) noexcept
: DriverBase(driverConfig) {
}
NoopDriver::~NoopDriver() noexcept = default;
@@ -247,6 +250,10 @@ bool NoopDriver::isDepthClampSupported() {
return false;
}
bool NoopDriver::isAsynchronousModeEnabled() {
return false;
}
bool NoopDriver::isWorkaroundNeeded(Workaround) {
return false;
}
@@ -465,4 +472,8 @@ bool NoopDriver::queryFrameTimestamps(SwapChainHandle swapChain, uint64_t frameI
return false;
}
bool NoopDriver::cancelAsyncJob(AsyncCallId jobId) {
return false;
}
} // namespace filament
+2 -2
View File
@@ -26,7 +26,7 @@
namespace filament::backend {
class NoopDriver final : public DriverBase {
NoopDriver() noexcept;
NoopDriver(const Platform::DriverConfig& driverConfig) noexcept;
~NoopDriver() noexcept override;
Dispatcher getDispatcher() const noexcept final;
@@ -55,7 +55,7 @@ private:
#define DECL_DRIVER_API_RETURN(RetType, methodName, paramsDecl, params) \
RetType methodName##S() noexcept override { \
return RetType((RetType::HandleId)nextFakeHandle++); } \
return RetType(nextFakeHandle++); } \
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl) { }
#include "private/backend/DriverAPI.inc"
+1 -1
View File
@@ -70,7 +70,7 @@ public:
assert_invariant(binding < MAX_DESCRIPTOR_COUNT);
assert_invariant(entry.binding < 128); // we reserve 1 bit for the type right now
mStorage[set][binding] = { uint8_t(entry.binding),
DescriptorSetLayoutBinding::isSampler(entry.type) };
DescriptorSetLayoutDescriptor::isSampler(entry.type) };
mActiveDescriptors[set].set(binding);
}
+2 -2
View File
@@ -30,8 +30,8 @@ namespace filament::backend {
struct GLBufferObject : public HwBufferObject {
using HwBufferObject::HwBufferObject;
GLBufferObject(uint32_t size,
BufferObjectBinding bindingType, BufferUsage usage) noexcept
: HwBufferObject(size), usage(usage), bindingType(bindingType) {
BufferObjectBinding bindingType, BufferUsage usage, bool async) noexcept
: HwBufferObject(size, async), usage(usage), bindingType(bindingType) {
}
struct {
@@ -56,7 +56,7 @@ GLDescriptorSet::GLDescriptorSet(OpenGLContext& gl, DescriptorSetLayoutHandle ds
// We have allocated enough storage for all descriptors. Now allocate the empty descriptor
// themselves.
for (auto const& entry : layout->bindings) {
for (auto const& entry : layout->descriptors) {
size_t const index = entry.binding;
// now we'll initialize the alternative for each way we can handle this descriptor.
@@ -372,10 +372,10 @@ void GLDescriptorSet::validate(HandleAllocatorGL& allocator,
UTILS_UNUSED_IN_RELEASE
bool const pipelineLayoutMatchesDescriptorSetLayout = std::equal(
dsl->bindings.begin(), dsl->bindings.end(),
cur->bindings.begin(),
[](DescriptorSetLayoutBinding const& lhs,
DescriptorSetLayoutBinding const& rhs) {
dsl->descriptors.begin(), dsl->descriptors.end(),
cur->descriptors.begin(),
[](DescriptorSetLayoutDescriptor const& lhs,
DescriptorSetLayoutDescriptor const& rhs) {
return lhs.type == rhs.type &&
lhs.stageFlags == rhs.stageFlags &&
lhs.binding == rhs.binding &&
@@ -33,12 +33,12 @@ struct GLDescriptorSetLayout : public HwDescriptorSetLayout, public DescriptorSe
explicit GLDescriptorSetLayout(DescriptorSetLayout&& layout) noexcept
: DescriptorSetLayout(std::move(layout)) {
std::sort(bindings.begin(), bindings.end(),
std::sort(descriptors.begin(), descriptors.end(),
[](auto&& lhs, auto&& rhs){
return lhs.binding < rhs.binding;
});
auto p = std::max_element(bindings.cbegin(), bindings.cend(),
auto p = std::max_element(descriptors.cbegin(), descriptors.cend(),
[](auto const& lhs, auto const& rhs) {
return lhs.binding < rhs.binding;
});
+3 -3
View File
@@ -373,8 +373,8 @@ public:
FeatureLevel getFeatureLevel() const noexcept { return mFeatureLevel; }
// This is the index of the context in use. Must be 0 or 1. This is used to manange the
// OpenGL name of ContainerObjects within each context.
// This is the index of the context in use. Must be either 0 (Unprotected) or 1 (Protected).
// This is used to manage the OpenGL name of ContainerObjects within each context.
uint32_t contextIndex = 0;
// Try to keep the State structure sorted by data-access patterns
@@ -536,7 +536,7 @@ private:
TimerQueryFactoryInterface* mTimerQueryFactory = nullptr;
std::vector<std::function<void(OpenGLContext&)>> mDestroyWithNormalContext;
RenderPrimitive mDefaultVAO;
std::optional<GLuint> mDefaultFbo[2];
std::optional<GLuint> mDefaultFbo[2]; // 0:Unprotected, 1:Protected
mutable tsl::robin_map<SamplerParams, GLuint,
SamplerParams::Hasher, SamplerParams::EqualTo> mSamplerMap;
+495 -103
View File
@@ -17,8 +17,8 @@
#include "OpenGLDriver.h"
#include "CommandStreamDispatcher.h"
#include "GLTexture.h"
#include "GLMemoryMappedBuffer.h"
#include "GLTexture.h"
#include "GLUtils.h"
#include "OpenGLContext.h"
#include "OpenGLDriverFactory.h"
@@ -126,6 +126,7 @@
#if DEBUG_MARKER_LEVEL == DEBUG_MARKER_PROFILE
# define DEBUG_MARKER()
# define DEBUG_MARKER_NAME(name)
# define PROFILE_MARKER(marker) PROFILE_SCOPE(marker);
# if DEBUG_GROUP_MARKER_LEVEL != DEBUG_GROUP_MARKER_NONE
# error PROFILING is exclusive; group markers must be disabled.
@@ -135,12 +136,14 @@
# endif
#elif DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
# define DEBUG_MARKER() DebugMarker _debug_marker(*this, __func__);
# define DEBUG_MARKER_NAME(name) DebugMarker _debug_marker(*this, name);
# define PROFILE_MARKER(marker) DEBUG_MARKER()
# if DEBUG_MARKER_LEVEL & DEBUG_MARKER_PROFILE
# error PROFILING is exclusive; all other debug features must be disabled.
# endif
#else
# define DEBUG_MARKER()
# define DEBUG_MARKER_NAME(name)
# define PROFILE_MARKER(marker)
#endif
@@ -286,14 +289,14 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
// ------------------------------------------------------------------------------------------------
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(*platform),
: OpenGLDriverBase(driverConfig),
mPlatform(*platform),
mContext(mPlatform, driverConfig),
mShaderCompilerService(*this),
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags),
mDriverConfig(driverConfig),
mCurrentPushConstants(new(std::nothrow) PushConstantBundle{}) {
// set a reasonable default value for our stream array
mTexturesWithStreamsAttached.reserve(8);
@@ -312,6 +315,26 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
#endif
mShaderCompilerService.init();
if (driverConfig.asynchronousMode != AsynchronousMode::NONE) {
mJobQueue = JobQueue::create();
bool const useThreadWorker =
(driverConfig.asynchronousMode == AsynchronousMode::THREAD_PREFERRED) &&
UTILS_HAS_THREADING;
if (useThreadWorker) {
ThreadWorker::Config threadWorkerConfig{
"JobQueueThreadWorker",
JobSystem::Priority::NORMAL,
[this]() { mPlatform.createContext(true); },
[this]() { mPlatform.releaseContext(); },
};
mJobWorker = ThreadWorker::create(mJobQueue, std::move(threadWorkerConfig));
} else {
mJobWorker = AmortizationWorker::create(mJobQueue);
}
}
}
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
@@ -359,8 +382,10 @@ void OpenGLDriver::terminate() {
delete mCurrentPushConstants;
mCurrentPushConstants = nullptr;
if (getJobWorker()) {
getJobWorker()->terminate();
}
mContext.terminate();
mPlatform.terminate();
}
@@ -412,7 +437,8 @@ void OpenGLDriver::setPushConstant(ShaderStage const stage, uint8_t const index,
if (std::holds_alternative<bool>(value)) {
assert_invariant(type == ConstantType::BOOL);
bool const bval = std::get<bool>(value);
glUniform1i(location, bval ? 1 : 0);
// This must be the 'ui' version of glUniform1 due to a crash on M-series macbooks.
glUniform1ui(location, bval ? 1 : 0);
} else if (std::holds_alternative<float>(value)) {
assert_invariant(type == ConstantType::FLOAT);
float const fval = std::get<float>(value);
@@ -576,10 +602,18 @@ Handle<HwIndexBuffer> OpenGLDriver::createIndexBufferS() noexcept {
return initHandle<GLIndexBuffer>();
}
Handle<HwIndexBuffer> OpenGLDriver::createIndexBufferAsyncS() noexcept {
return initHandle<GLIndexBuffer>();
}
Handle<HwBufferObject> OpenGLDriver::createBufferObjectS() noexcept {
return initHandle<GLBufferObject>();
}
Handle<HwBufferObject> OpenGLDriver::createBufferObjectAsyncS() noexcept {
return initHandle<GLBufferObject>();
}
Handle<HwRenderPrimitive> OpenGLDriver::createRenderPrimitiveS() noexcept {
return initHandle<GLRenderPrimitive>();
}
@@ -592,6 +626,10 @@ Handle<HwTexture> OpenGLDriver::createTextureS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureAsyncS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewS() noexcept {
return initHandle<GLTexture>();
}
@@ -600,6 +638,10 @@ Handle<HwTexture> OpenGLDriver::createTextureViewSwizzleS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureViewSwizzleAsyncS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::createTextureExternalImage2S() noexcept {
return initHandle<GLTexture>();
}
@@ -616,6 +658,30 @@ Handle<HwTexture> OpenGLDriver::importTextureS() noexcept {
return initHandle<GLTexture>();
}
Handle<HwTexture> OpenGLDriver::importTextureAsyncS() noexcept {
return initHandle<GLTexture>();
}
AsyncCallId OpenGLDriver::update3DImageAsyncS() noexcept {
assert_invariant(getJobQueue());
return getJobQueue()->issueJobId();
}
AsyncCallId OpenGLDriver::setVertexBufferObjectAsyncS() noexcept {
assert_invariant(getJobQueue());
return getJobQueue()->issueJobId();
}
AsyncCallId OpenGLDriver::updateBufferObjectAsyncS() noexcept {
assert_invariant(getJobQueue());
return getJobQueue()->issueJobId();
}
AsyncCallId OpenGLDriver::updateIndexBufferAsyncS() noexcept {
assert_invariant(getJobQueue());
return getJobQueue()->issueJobId();
}
Handle<HwRenderTarget> OpenGLDriver::createDefaultRenderTargetS() noexcept {
return initHandle<GLRenderTarget>();
}
@@ -656,6 +722,11 @@ MemoryMappedBufferHandle OpenGLDriver::mapBufferS() noexcept {
return initHandle<GLMemoryMappedBuffer>();
}
AsyncCallId OpenGLDriver::queueCommandAsyncS() noexcept {
assert_invariant(getJobQueue());
return getJobQueue()->issueJobId();
}
void OpenGLDriver::createVertexBufferInfoR(
Handle<HwVertexBufferInfo> vbih,
uint8_t bufferCount,
@@ -677,17 +748,11 @@ void OpenGLDriver::createVertexBufferR(
mHandleAllocator.associateTagToHandle(vbh.getId(), std::move(tag));
}
void OpenGLDriver::createIndexBufferR(
Handle<HwIndexBuffer> ibh,
ElementType const elementType,
uint32_t indexCount,
BufferUsage const usage,
ImmutableCString&& tag) {
DEBUG_MARKER()
void OpenGLDriver::createIndexBufferCommon(Handle<HwIndexBuffer> ibh, ElementType const elementType,
uint32_t indexCount, BufferUsage const usage, utils::ImmutableCString&& tag) {
auto& gl = mContext;
uint8_t const elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
GLIndexBuffer* ib = construct<GLIndexBuffer>(ibh, elementSize, indexCount);
GLIndexBuffer* ib = handle_cast<GLIndexBuffer*>(ibh);
glGenBuffers(1, &ib->gl.buffer);
GLsizeiptr const size = elementSize * GLsizeiptr(indexCount);
gl.bindVertexArray(nullptr);
@@ -697,9 +762,52 @@ void OpenGLDriver::createIndexBufferR(
mHandleAllocator.associateTagToHandle(ibh.getId(), std::move(tag));
}
void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, ImmutableCString&& tag) {
void OpenGLDriver::createIndexBufferR(
Handle<HwIndexBuffer> ibh,
ElementType const elementType,
uint32_t indexCount,
BufferUsage const usage,
ImmutableCString&& tag) {
DEBUG_MARKER()
uint8_t const elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLIndexBuffer>(ibh, elementSize, indexCount, false);
createIndexBufferCommon(ibh, elementType, indexCount, usage, std::move(tag));
}
void OpenGLDriver::createIndexBufferAsyncR(
Handle<HwIndexBuffer> ibh,
ElementType const elementType,
uint32_t indexCount,
BufferUsage const usage,
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user,
ImmutableCString&& tag) {
uint8_t const elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLIndexBuffer>(ibh, elementSize, indexCount, true);
assert_invariant(getJobQueue());
getJobQueue()->push([this, ibh, elementType, indexCount, usage, handler, callback, user,
tag=std::move(tag)]() mutable {
DEBUG_MARKER_NAME("createIndexBufferAsyncR")
createIndexBufferCommon(ibh, elementType, indexCount, usage, std::move(tag));
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
});
}
void OpenGLDriver::createBufferObjectCommon(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, utils::ImmutableCString&& tag) {
assert_invariant(byteCount > 0);
auto& gl = mContext;
@@ -707,7 +815,7 @@ void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byte
gl.bindVertexArray(nullptr);
}
GLBufferObject* bo = construct<GLBufferObject>(boh, byteCount, bindingType, usage);
GLBufferObject* bo = handle_cast<GLBufferObject*>(boh);
if (UTILS_UNLIKELY(bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
bo->gl.id = ++mLastAssignedEmulatedUboId;
bo->gl.buffer = malloc(byteCount);
@@ -723,6 +831,40 @@ void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byte
mHandleAllocator.associateTagToHandle(boh.getId(), std::move(tag));
}
void OpenGLDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, ImmutableCString&& tag) {
DEBUG_MARKER()
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLBufferObject>(boh, byteCount, bindingType, usage, false);
createBufferObjectCommon(boh, byteCount, bindingType, usage, std::move(tag));
}
void OpenGLDriver::createBufferObjectAsyncR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user, ImmutableCString&& tag) {
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLBufferObject>(boh, byteCount, bindingType, usage, true);
assert_invariant(getJobQueue());
getJobQueue()->push([this, boh, byteCount, bindingType, usage, handler, callback, user,
tag=std::move(tag)]() mutable {
DEBUG_MARKER_NAME("createBufferObjectAsyncR")
createBufferObjectCommon(boh, byteCount, bindingType, usage, std::move(tag));
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
});
}
void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
PrimitiveType const pt, ImmutableCString&& tag) {
@@ -870,11 +1012,9 @@ void OpenGLDriver::textureStorage(GLTexture* t,
t->depth = depth;
}
void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
void OpenGLDriver::createTextureCommon(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage, ImmutableCString&& tag) {
DEBUG_MARKER()
GLenum internalFormat = getInternalFormat(format);
assert_invariant(internalFormat);
@@ -895,7 +1035,11 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
auto const& gl = mContext;
samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples));
GLTexture* t = construct<GLTexture>(th, target, levels, samples, width, height, depth, format, usage);
GLTexture* t = handle_cast<GLTexture*>(th);
// Overwrite the updated `usage`.
t->usage = usage;
if (UTILS_LIKELY(usage & TextureUsage::SAMPLEABLE)) {
if (UTILS_UNLIKELY(gl.isES2())) {
@@ -964,6 +1108,44 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage, ImmutableCString&& tag) {
DEBUG_MARKER()
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLTexture>(th, target, levels, samples, width, height, depth, format, usage, false);
createTextureCommon(th, target, levels, format, samples, width, height, depth, usage,
std::move(tag));
}
void OpenGLDriver::createTextureAsyncR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage, CallbackHandler* handler, CallbackHandler::Callback const callback,
void* user, ImmutableCString&& tag) {
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLTexture>(th, target, levels, samples, width, height, depth, format, usage, true);
assert_invariant(getJobQueue());
getJobQueue()->push([this, th, target, levels, format, samples, width, height, depth, usage,
handler, callback, user, tag=std::move(tag)]() mutable {
DEBUG_MARKER_NAME("createTextureAsyncR")
createTextureCommon(th, target, levels, format, samples, width, height, depth, usage,
std::move(tag));
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
});
}
void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
Handle<HwTexture> srch, uint8_t const baseLevel, uint8_t const levelCount, ImmutableCString&& tag) {
DEBUG_MARKER()
@@ -986,7 +1168,8 @@ void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
src->samples,
src->width, src->height, src->depth,
src->format,
src->usage);
src->usage,
src->asynchronous);
t->gl = src->gl;
t->gl.sidecarRenderBufferMS = 0;
@@ -1011,11 +1194,9 @@ void OpenGLDriver::createTextureViewR(Handle<HwTexture> th,
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
TextureSwizzle const r, TextureSwizzle const g, TextureSwizzle const b, TextureSwizzle const a,
ImmutableCString&& tag) {
DEBUG_MARKER()
void OpenGLDriver::createTextureViewSwizzleCommon(Handle<HwTexture> th, Handle<HwTexture> srch,
TextureSwizzle const r, TextureSwizzle const g, TextureSwizzle const b,
TextureSwizzle const a, ImmutableCString&& tag) {
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
FILAMENT_CHECK_PRECONDITION(any(src->usage & TextureUsage::SAMPLEABLE))
@@ -1029,14 +1210,7 @@ void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
src->ref = initHandle<GLTextureRef>();
}
GLTexture* t = construct<GLTexture>(th,
src->target,
src->levels,
src->samples,
src->width, src->height, src->depth,
src->format,
src->usage);
GLTexture* t = handle_cast<GLTexture*>(th);
t->gl = src->gl;
t->gl.baseLevel = src->gl.baseLevel;
t->gl.maxLevel = src->gl.maxLevel;
@@ -1077,6 +1251,46 @@ void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void OpenGLDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwTexture> srch,
TextureSwizzle const r, TextureSwizzle const g, TextureSwizzle const b, TextureSwizzle const a,
ImmutableCString&& tag) {
DEBUG_MARKER()
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
construct<GLTexture>(th, src->target, src->levels, src->samples, src->width, src->height,
src->depth, src->format, src->usage, src->asynchronous);
createTextureViewSwizzleCommon(th, srch, r, g, b, a, std::move(tag));
}
void OpenGLDriver::createTextureViewSwizzleAsyncR(Handle<HwTexture> th, Handle<HwTexture> srch,
TextureSwizzle const r, TextureSwizzle const g, TextureSwizzle const b, TextureSwizzle const a,
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user,
ImmutableCString&& tag) {
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
GLTexture const* const src = handle_cast<GLTexture const*>(srch);
construct<GLTexture>(th, src->target, src->levels, src->samples, src->width, src->height,
src->depth, src->format, src->usage, src->asynchronous);
assert_invariant(getJobQueue());
getJobQueue()->push([this, th, srch, r, g, b, a, handler, callback, user,
tag=std::move(tag)]() mutable {
DEBUG_MARKER_NAME("createTextureViewSwizzleAsyncR")
createTextureViewSwizzleCommon(th, srch, r, g, b, a, std::move(tag));
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
});
}
void OpenGLDriver::createTextureExternalImage2R(Handle<HwTexture> th, SamplerType target,
TextureFormat format, uint32_t width, uint32_t height, TextureUsage usage,
Platform::ExternalImageHandleRef image, ImmutableCString&& tag) {
@@ -1094,7 +1308,7 @@ void OpenGLDriver::createTextureExternalImage2R(Handle<HwTexture> th, SamplerTyp
}
assert_invariant(internalFormat);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage, false);
assert_invariant(t);
t->externalTexture = mPlatform.createExternalImageTexture();
@@ -1146,7 +1360,7 @@ void OpenGLDriver::createTextureExternalImageR(Handle<HwTexture> th, SamplerType
}
assert_invariant(internalFormat);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage);
GLTexture* const t = construct<GLTexture>(th, target, 1, 1, width, height, 1, format, usage, false);
assert_invariant(t);
t->externalTexture = mPlatform.createExternalImageTexture();
@@ -1186,14 +1400,12 @@ void OpenGLDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
// not relevant for the OpenGL backend
}
void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t const id,
void OpenGLDriver::importTextureCommon(Handle<HwTexture> th, intptr_t const id,
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, ImmutableCString&& tag) {
DEBUG_MARKER()
auto const& gl = mContext;
samples = std::clamp(samples, uint8_t(1u), uint8_t(gl.gets.max_samples));
GLTexture* t = construct<GLTexture>(th, target, levels, samples, width, height, depth, format, usage);
GLTexture* t = handle_cast<GLTexture*>(th);
t->gl.id = GLuint(id);
t->gl.imported = true;
@@ -1244,6 +1456,46 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t const id,
mHandleAllocator.associateTagToHandle(th.getId(), std::move(tag));
}
void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t const id,
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage, ImmutableCString&& tag) {
DEBUG_MARKER()
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLTexture>(th, target, levels, samples, width, height, depth, format, usage, false);
importTextureCommon(th, id, target, levels, format, samples, width, height, depth, usage,
std::move(tag));
}
void OpenGLDriver::importTextureAsyncR(Handle<HwTexture> th, intptr_t const id,
SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage,
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user,
ImmutableCString&& tag) {
// For object creation, the object should be constructed first to determine the initial settings
// early. For example, the `asynchronous` field needs to be decided at this stage so that
// subsequent backend APIs can handle operations based on this setting.
construct<GLTexture>(th, target, levels, samples, width, height, depth, format, usage, true);
assert_invariant(getJobQueue());
getJobQueue()->push([this, th, id, target, levels, format, samples, width, height, depth, usage,
handler, callback, user, tag=std::move(tag)]() mutable {
DEBUG_MARKER_NAME("importTextureAsyncR")
importTextureCommon(th, id, target, levels, format, samples, width, height, depth, usage,
std::move(tag));
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
});
}
void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) {
// NOTE: this is called often and must be as efficient as possible.
@@ -1931,30 +2183,53 @@ void OpenGLDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
}
}
void OpenGLDriver::destroyIndexBufferCommon(Handle<HwIndexBuffer> ibh) {
GLIndexBuffer const* ib = handle_cast<const GLIndexBuffer*>(ibh);
auto& gl = mContext;
gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
destruct(ibh, ib);
}
void OpenGLDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
DEBUG_MARKER()
if (ibh) {
auto& gl = mContext;
GLIndexBuffer const* ib = handle_cast<const GLIndexBuffer*>(ibh);
gl.deleteBuffer(ib->gl.buffer, GL_ELEMENT_ARRAY_BUFFER);
destruct(ibh, ib);
if (ib->asynchronous) {
getJobQueue()->push([this, ibh]() {
destroyIndexBufferCommon(ibh);
});
} else {
destroyIndexBufferCommon(ibh);
}
}
}
void OpenGLDriver::destroyBufferObjectCommon(Handle<HwBufferObject> boh) {
GLBufferObject const* bo = handle_cast<const GLBufferObject*>(boh);
auto& gl = mContext;
// check we're not destroying a buffer that has active mappings
assert_invariant(bo->mappingCount == 0);
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
free(bo->gl.buffer);
} else {
gl.deleteBuffer(bo->gl.id, bo->gl.binding);
}
destruct(boh, bo);
}
void OpenGLDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
DEBUG_MARKER()
if (boh) {
auto& gl = mContext;
GLBufferObject const* bo = handle_cast<const GLBufferObject*>(boh);
// check we're not destroying a buffer that has active mappings
assert_invariant(bo->mappingCount == 0);
if (UTILS_UNLIKELY(bo->bindingType == BufferObjectBinding::UNIFORM && gl.isES2())) {
free(bo->gl.buffer);
if (bo->asynchronous) {
getJobQueue()->push([this, boh]() {
destroyBufferObjectCommon(boh);
});
} else {
gl.deleteBuffer(bo->gl.id, bo->gl.binding);
destroyBufferObjectCommon(boh);
}
destruct(boh, bo);
}
}
@@ -1990,53 +2265,63 @@ void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
}
}
void OpenGLDriver::destroyTextureCommon(Handle<HwTexture> th) {
GLTexture* t = handle_cast<GLTexture*>(th);
auto& gl = mContext;
if (UTILS_LIKELY(!t->gl.imported)) {
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
// drop a reference
uint16_t count = 0;
if (UTILS_UNLIKELY(t->ref)) {
// the common case is that we don't have a ref handle
GLTextureRef* const ref = handle_cast<GLTextureRef*>(t->ref);
count = --(ref->count);
if (count == 0) {
destruct(t->ref, ref);
}
}
if (count == 0) {
// if this was the last reference, we destroy the refcount as well as
// the GL texture name itself.
gl.unbindTexture(t->gl.target, t->gl.id);
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (UTILS_UNLIKELY(t->externalTexture)) {
mPlatform.destroyExternalImageTexture(t->externalTexture);
} else {
glDeleteTextures(1, &t->gl.id);
}
} else {
// The Handle<HwTexture> is always destroyed. For extra precaution we also
// check that the GLTexture has a trivial destructor.
static_assert(std::is_trivially_destructible_v<GLTexture>);
}
} else {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
glDeleteRenderbuffers(1, &t->gl.id);
}
if (t->gl.sidecarRenderBufferMS) {
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
} else {
gl.unbindTexture(t->gl.target, t->gl.id);
}
destruct(th, t);
}
void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
DEBUG_MARKER()
if (th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture*>(th);
if (UTILS_LIKELY(!t->gl.imported)) {
if (UTILS_LIKELY(t->usage & TextureUsage::SAMPLEABLE)) {
// drop a reference
uint16_t count = 0;
if (UTILS_UNLIKELY(t->ref)) {
// the common case is that we don't have a ref handle
GLTextureRef* const ref = handle_cast<GLTextureRef*>(t->ref);
count = --(ref->count);
if (count == 0) {
destruct(t->ref, ref);
}
}
if (count == 0) {
// if this was the last reference, we destroy the refcount as well as
// the GL texture name itself.
gl.unbindTexture(t->gl.target, t->gl.id);
if (UTILS_UNLIKELY(t->hwStream)) {
detachStream(t);
}
if (UTILS_UNLIKELY(t->externalTexture)) {
mPlatform.destroyExternalImageTexture(t->externalTexture);
} else {
glDeleteTextures(1, &t->gl.id);
}
} else {
// The Handle<HwTexture> is always destroyed. For extra precaution we also
// check that the GLTexture has a trivial destructor.
static_assert(std::is_trivially_destructible_v<GLTexture>);
}
} else {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
glDeleteRenderbuffers(1, &t->gl.id);
}
if (t->gl.sidecarRenderBufferMS) {
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
if (t->asynchronous) {
getJobQueue()->push([this, th]() {
destroyTextureCommon(th);
});
} else {
gl.unbindTexture(t->gl.target, t->gl.id);
destroyTextureCommon(th);
}
destruct(th, t);
}
}
@@ -2577,7 +2862,7 @@ bool OpenGLDriver::isStereoSupported() {
if (UTILS_UNLIKELY(mContext.isES2())) {
return false;
}
switch (mDriverConfig.stereoscopicType) {
switch (getDriverConfig().stereoscopicType) {
case StereoscopicType::INSTANCED:
return mContext.ext.EXT_clip_cull_distance;
case StereoscopicType::MULTIVIEW:
@@ -2596,7 +2881,7 @@ bool OpenGLDriver::isParallelShaderCompileSupported() {
// GL-specific. It would also be nice to inform the engine that they're working with this fake
// amortized system, but this fact will become implicit when we generalize this feature for all
// backends.
if (mDriverConfig.disableAmortizedShaderCompile) {
if (getDriverConfig().disableAmortizedShaderCompile) {
return mShaderCompilerService.isParallelShaderCompileSupported();
}
return true;
@@ -2618,6 +2903,10 @@ bool OpenGLDriver::isDepthClampSupported() {
return getContext().ext.EXT_depth_clamp;
}
bool OpenGLDriver::isAsynchronousModeEnabled() {
return getJobQueue() != nullptr;
}
bool OpenGLDriver::isWorkaroundNeeded(Workaround const workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU:
@@ -2799,10 +3088,8 @@ void OpenGLDriver::makeCurrent(Handle<HwSwapChain> schDraw, Handle<HwSwapChain>
// Updating driver objects
// ------------------------------------------------------------------------------------------------
void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
void OpenGLDriver::setVertexBufferObjectCommon(Handle<HwVertexBuffer> vbh,
uint32_t const index, Handle<HwBufferObject> boh) {
DEBUG_MARKER()
GLVertexBuffer* vb = handle_cast<GLVertexBuffer *>(vbh);
GLBufferObject const* bo = handle_cast<GLBufferObject *>(boh);
@@ -2822,10 +3109,28 @@ void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
CHECK_GL_ERROR()
}
void OpenGLDriver::updateIndexBuffer(
Handle<HwIndexBuffer> ibh, BufferDescriptor&& p, uint32_t const byteOffset) {
void OpenGLDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh,
uint32_t const index, Handle<HwBufferObject> boh) {
DEBUG_MARKER()
setVertexBufferObjectCommon(vbh, index, boh);
}
void OpenGLDriver::setVertexBufferObjectAsyncR(AsyncCallId jobId, Handle<HwVertexBuffer> vbh,
uint32_t const index, Handle<HwBufferObject> boh, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
getJobQueue()->push([this, vbh, index, boh, handler, callback, user]() mutable {
DEBUG_MARKER_NAME("setVertexBufferObjectAsyncR")
setVertexBufferObjectCommon(vbh, index, boh);
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
}, jobId);
}
void OpenGLDriver::updateIndexBufferCommon(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t const byteOffset) {
auto& gl = mContext;
GLIndexBuffer const* ib = handle_cast<GLIndexBuffer *>(ibh);
assert_invariant(ib->elementSize == 2 || ib->elementSize == 4);
@@ -2839,9 +3144,29 @@ void OpenGLDriver::updateIndexBuffer(
CHECK_GL_ERROR()
}
void OpenGLDriver::updateBufferObject(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
void OpenGLDriver::updateIndexBuffer(
Handle<HwIndexBuffer> ibh, BufferDescriptor&& p, uint32_t const byteOffset) {
DEBUG_MARKER()
updateIndexBufferCommon(ibh, std::move(p), byteOffset);
}
void OpenGLDriver::updateIndexBufferAsyncR(AsyncCallId jobId, Handle<HwIndexBuffer> ibh,
BufferDescriptor&& p, uint32_t const byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
getJobQueue()->push([this, ibh, p=std::move(p), byteOffset, handler, callback,
user]() mutable {
DEBUG_MARKER_NAME("updateIndexBufferAsyncR")
updateIndexBufferCommon(ibh, std::move(p), byteOffset);
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
}, jobId);
}
void OpenGLDriver::updateBufferObjectCommon(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
auto& gl = mContext;
GLBufferObject* bo = handle_cast<GLBufferObject *>(boh);
@@ -2874,6 +3199,27 @@ void OpenGLDriver::updateBufferObject(
CHECK_GL_ERROR()
}
void OpenGLDriver::updateBufferObject(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
DEBUG_MARKER()
updateBufferObjectCommon(boh, std::move(bd), byteOffset);
}
void OpenGLDriver::updateBufferObjectAsyncR(AsyncCallId jobId, Handle<HwBufferObject> boh,
BufferDescriptor&& bd, uint32_t const byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
getJobQueue()->push([this, boh, bd=std::move(bd), byteOffset, handler, callback,
user]() mutable {
DEBUG_MARKER_NAME("updateBufferObjectAsyncR")
updateBufferObjectCommon(boh, std::move(bd), byteOffset);
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
}, jobId);
}
void OpenGLDriver::updateBufferObjectUnsynchronized(
Handle<HwBufferObject> boh, BufferDescriptor&& bd, uint32_t const byteOffset) {
DEBUG_MARKER()
@@ -2937,12 +3283,10 @@ void OpenGLDriver::resetBufferObject(Handle<HwBufferObject> boh) {
}
}
void OpenGLDriver::update3DImage(Handle<HwTexture> th,
void OpenGLDriver::update3DImageCommon(Handle<HwTexture> th,
uint32_t const level, uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset,
uint32_t const width, uint32_t const height, uint32_t const depth,
PixelBufferDescriptor&& data) {
DEBUG_MARKER()
GLTexture const* t = handle_cast<GLTexture *>(th);
if (data.type == PixelDataType::COMPRESSED) {
setCompressedTextureData(t,
@@ -2953,6 +3297,33 @@ void OpenGLDriver::update3DImage(Handle<HwTexture> th,
}
}
void OpenGLDriver::update3DImage(Handle<HwTexture> th,
uint32_t const level, uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset,
uint32_t const width, uint32_t const height, uint32_t const depth,
PixelBufferDescriptor&& data) {
DEBUG_MARKER()
update3DImageCommon(th, level, xoffset, yoffset, zoffset, width, height, depth,
std::move(data));
}
void OpenGLDriver::update3DImageAsyncR(AsyncCallId jobId, Handle<HwTexture> th,
uint32_t const level, uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset,
uint32_t const width, uint32_t const height, uint32_t const depth,
PixelBufferDescriptor&& data, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
getJobQueue()->push([this, th, level, xoffset, yoffset, zoffset, width, height, depth,
data=std::move(data), handler, callback, user]() mutable {
DEBUG_MARKER_NAME("update3DImageAsync")
update3DImageCommon(th, level, xoffset, yoffset, zoffset, width, height, depth,
std::move(data));
// glFlush() should be called when using a shared context for this operation. Without it,
// the driver may delay submitting commands to the GPU, preventing other contexts from
// seeing the changes immediately. This ensures submitting the current commands right away.
glFlush();
scheduleCallback(handler, user, callback);
}, jobId);
}
void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
DEBUG_MARKER()
@@ -3852,6 +4223,10 @@ void OpenGLDriver::tick(int) {
#endif
executeEveryNowAndThenOps();
getShaderCompilerService().tick();
if (getJobWorker()) {
// This number is randomly/heuristically chosen. Consider making the number optional.
getJobWorker()->process(5);
}
}
void OpenGLDriver::beginFrame(
@@ -4472,6 +4847,23 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, uint3 const workGr
#endif
}
void OpenGLDriver::queueCommandAsyncR(AsyncCallId jobId, Invocable<void()>&& command, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
assert_invariant(getJobQueue());
getJobQueue()->push([this, command=std::move(command), handler, callback, user]() {
DEBUG_MARKER_NAME("queueCommandAsync")
if (command) {
command();
}
scheduleCallback(handler, user, callback);
}, jobId);
}
bool OpenGLDriver::cancelAsyncJob(AsyncCallId jobId) {
assert_invariant(getJobQueue());
return getJobQueue()->cancel(jobId);
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<OpenGLDriver>;
+35 -3
View File
@@ -26,6 +26,7 @@
#include "GLDescriptorSetLayout.h"
#include "GLMemoryMappedBuffer.h"
#include "GLTexture.h"
#include "JobQueue.h"
#include "ShaderCompilerService.h"
#include <backend/AcquiredImage.h>
@@ -248,6 +249,9 @@ private:
return utils::CString{ mContext.state.renderer };
}
JobQueue* getJobQueue() const noexcept { return mJobQueue.get(); }
JobWorker* getJobWorker() const noexcept { return mJobWorker.get(); }
template<typename T>
friend class ConcreteDispatcher;
@@ -358,6 +362,34 @@ private:
static GLsizei getAttachments(AttachmentArray& attachments, TargetBufferFlags buffers,
bool isDefaultFramebuffer) noexcept;
// Common methods
void createTextureCommon(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage, utils::ImmutableCString&& tag);
void update3DImageCommon(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data);
void createTextureViewSwizzleCommon(Handle<HwTexture> th, Handle<HwTexture> srch,
TextureSwizzle r, TextureSwizzle g, TextureSwizzle b,
TextureSwizzle a, utils::ImmutableCString&& tag);
void importTextureCommon(Handle<HwTexture> th, intptr_t id, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
TextureUsage usage, utils::ImmutableCString&& tag);
void createBufferObjectCommon(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, utils::ImmutableCString&& tag);
void setVertexBufferObjectCommon(Handle<HwVertexBuffer> vbh, uint32_t index,
Handle<HwBufferObject> boh);
void updateBufferObjectCommon(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset);
void createIndexBufferCommon(Handle<HwIndexBuffer> ibh, ElementType const elementType,
uint32_t indexCount, BufferUsage const usage, utils::ImmutableCString&& tag);
void updateIndexBufferCommon(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t const byteOffset);
void destroyTextureCommon(Handle<HwTexture> th);
void destroyBufferObjectCommon(Handle<HwBufferObject> boh);
void destroyIndexBufferCommon(Handle<HwIndexBuffer> ibh);
// state required to represent the current render pass
Handle<HwRenderTarget> mRenderPassTarget;
RenderPassParams mRenderPassParams;
@@ -413,15 +445,15 @@ private:
void executeEveryNowAndThenOps() noexcept;
std::vector<std::function<bool()>> mEveryNowAndThenOps;
const Platform::DriverConfig mDriverConfig;
Platform::DriverConfig const& getDriverConfig() const noexcept { return mDriverConfig; }
// for ES2 sRGB support
GLSwapChain* mCurrentDrawSwapChain = nullptr;
bool mRec709OutputColorspace = false;
PushConstantBundle* mCurrentPushConstants = nullptr;
PipelineLayout::SetLayout mCurrentSetLayout;
JobQueue::Ptr mJobQueue;
JobWorker::Ptr mJobWorker;
};
// ------------------------------------------------------------------------------------------------
@@ -19,12 +19,16 @@
#include "DriverBase.h"
#include <backend/Platform.h>
#include <utils/CString.h>
namespace filament::backend {
class OpenGLDriverBase : public DriverBase {
protected:
explicit OpenGLDriverBase(const Platform::DriverConfig& driverConfig)
: DriverBase(driverConfig) {}
~OpenGLDriverBase() override;
public:
@@ -254,8 +254,14 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
mPushConstants.reserve(totalConstantCount);
mPushConstantFragmentStageOffset = vertexConstants.size();
auto const transformAndAdd = [&](Program::PushConstant const& constant) {
GLint const loc = glGetUniformLocation(program, constant.name.c_str());
mPushConstants.push_back({loc, constant.type});
if (!constant.name.empty()) {
GLint const loc = glGetUniformLocation(program, constant.name.c_str());
mPushConstants.push_back({loc, constant.type});
} else {
// If the constant is not named, then we assume it will not be referenced in this
// program.
mPushConstants.push_back({ -1, constant.type });
}
};
std::for_each(vertexConstants.cbegin(), vertexConstants.cend(), transformAndAdd);
std::for_each(fragmentConstants.cbegin(), fragmentConstants.cend(), transformAndAdd);
@@ -267,8 +267,6 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
eglDupNativeFenceFDANDROID =
PFNEGLDUPNATIVEFENCEFDANDROIDPROC(eglGetProcAddress(
"eglDupNativeFenceFDANDROID"));
} else {
LOG(ERROR) << "EGL_ANDROID_native_fence_sync not supported!";
}
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
@@ -54,17 +54,12 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
// Check if we can just memcpy directly to the GPU memory.
bool const isMemcopyable = mBuffer->getGpuBuffer()->allocationInfo.pMappedData != nullptr;
// In the case of UNIFORMS, check that is available to see to know if a memcpy is possible.
// This works regardless if it's a full or partial update of the buffer.
bool const isUniformAndAvailable = getBinding() == VulkanBufferBinding::UNIFORM && isAvailable;
// In the case the content is marked as memory mapped or static, is guaranteed to be safe to do
// a memcpy if its available.
bool const isStaticOrShared =
any(mUsage & (BufferUsage::STATIC | BufferUsage::SHARED_WRITE_BIT));
bool const useMemcpy =
((isUniformAndAvailable && mStagingBufferBypassEnabled) || isStaticOrShared) &&
isMemcopyable;
((isAvailable && mStagingBufferBypassEnabled) || isStaticOrShared) && isMemcopyable;
if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset;
@@ -76,5 +76,4 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
};
}
} // namespace filament::backend
+38 -5
View File
@@ -126,6 +126,10 @@ public:
return mDebugUtilsSupported;
}
inline bool isDynamicRenderingSupported() const noexcept {
return mDynamicRenderingFeatures.dynamicRendering == VK_TRUE;
}
inline bool isMultiviewEnabled() const noexcept {
return mPhysicalDeviceVk11Features.multiview == VK_TRUE;
}
@@ -150,14 +154,33 @@ public:
return mIsUnifiedMemoryArchitecture;
}
inline bool stagingBufferBypassEnabled() const noexcept {
return mStagingBufferBypassEnabled;
inline bool isVertexInputDynamicStateSupported() const noexcept {
return mVertexInputDynamicStateSupported;
}
inline bool pipelineCreationFeedbackSupported() const noexcept {
return mPipelineCreationFeedbackSupported;
}
inline bool asyncPipelineCachePrewarmingEnabled() const noexcept {
return mAsyncPipelineCachePrewarmingEnabled;
}
inline bool parallelShaderCompilationDisabled() const noexcept {
return mParallelShaderCompileDisabled;
}
inline bool stagingBufferBypassEnabled() const noexcept {
return mStagingBufferBypassEnabled;
}
inline bool shouldUsePipelineCachePrewarming() const noexcept {
return asyncPipelineCachePrewarmingEnabled() &&
!parallelShaderCompilationDisabled() &&
isVertexInputDynamicStateSupported() &&
isDynamicRenderingSupported();
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -169,6 +192,9 @@ private:
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
};
VkPhysicalDeviceDynamicRenderingFeaturesKHR mDynamicRenderingFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
};
VkPhysicalDevicePortabilitySubsetFeaturesKHR mPortabilitySubsetFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR,
// By default, on platforms where we don't have portability subset, then this feature must
@@ -179,13 +205,20 @@ private:
VkExternalFenceHandleTypeFlags mFenceExportFlags = {};
// These are options that are either supported or not supported in the current
// device and instance.
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false;
bool mLazilyAllocatedMemorySupported = false;
bool mPipelineCreationFeedbackSupported = false;
bool mProtectedMemorySupported = false;
bool mVertexInputDynamicStateSupported = false;
// These are options that can be enabled or disabled at an application level.
bool mAsyncPipelineCachePrewarmingEnabled = false;
bool mParallelShaderCompileDisabled = false;
bool mStagingBufferBypassEnabled = false;
fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats;
+197 -20
View File
@@ -217,7 +217,8 @@ Dispatcher VulkanDriver::getDispatcher() const noexcept {
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
Platform::DriverConfig const& driverConfig)
: mPlatform(platform),
: DriverBase(driverConfig),
mPlatform(platform),
mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags),
// Note that we always create the default rendertarget before createDefaultRenderTarget(). We
@@ -233,7 +234,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mPlatform->getGraphicsQueueFamilyIndex(), mPlatform->getProtectedGraphicsQueue(),
mPlatform->getProtectedGraphicsQueueFamilyIndex(), mContext, &mSemaphoreManager),
mPipelineLayoutCache(mPlatform->getDevice()),
mPipelineCache(mPlatform->getDevice(), mContext),
mPipelineCache(*this, mPlatform->getDevice(), mContext),
mStagePool(mAllocator, &mResourceManager, &mCommands, &mContext.getPhysicalDeviceLimits()),
mBufferCache(mContext, mResourceManager, mAllocator),
mFramebufferCache(mPlatform->getDevice()),
@@ -249,7 +250,9 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mStreamedImageManager(&mExternalImageManager),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
mStereoscopicType(driverConfig.stereoscopicType) {
mStereoscopicType(driverConfig.stereoscopicType),
mStereoscopicEyeCount(driverConfig.stereoscopicEyeCount),
mAsynchronousMode(driverConfig.asynchronousMode) {
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
DebugUtils::mSingleton =
@@ -601,6 +604,12 @@ void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType ele
mResourceManager.associateHandle(ibh.getId(), std::move(tag));
}
void VulkanDriver::createIndexBufferAsyncR(Handle<HwIndexBuffer> ibh, ElementType elementType,
uint32_t indexCount, BufferUsage usage, CallbackHandler* handler,
CallbackHandler::Callback callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
if (!ibh) {
return;
@@ -619,6 +628,12 @@ void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byte
mResourceManager.associateHandle(boh.getId(), std::move(tag));
}
void VulkanDriver::createBufferObjectAsyncR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage, CallbackHandler* handler,
CallbackHandler::Callback callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
if (!boh) {
return;
@@ -646,6 +661,13 @@ void VulkanDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
mResourceManager.associateHandle(th.getId(), std::move(tag));
}
void VulkanDriver::createTextureAsyncR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage, CallbackHandler* handler, CallbackHandler::Callback callback,
void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void VulkanDriver::createTextureViewR(Handle<HwTexture> th, Handle<HwTexture> srch,
uint8_t baseLevel, uint8_t levelCount, utils::ImmutableCString&& tag) {
auto src = resource_ptr<VulkanTexture>::cast(&mResourceManager, srch);
@@ -668,6 +690,13 @@ void VulkanDriver::createTextureViewSwizzleR(Handle<HwTexture> th, Handle<HwText
mResourceManager.associateHandle(th.getId(), std::move(tag));
}
void VulkanDriver::createTextureViewSwizzleAsyncR(Handle<HwTexture> th, Handle<HwTexture> srch,
backend::TextureSwizzle r, backend::TextureSwizzle g, backend::TextureSwizzle b,
backend::TextureSwizzle a, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::SamplerType target,
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
Platform::ExternalImageHandleRef externalImage, utils::ImmutableCString&& tag) {
@@ -742,6 +771,16 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
mResourceManager.associateHandle(th.getId(), std::move(tag));
}
void VulkanDriver::importTextureAsyncR(Handle<HwTexture> th, intptr_t id,
SamplerType target, uint8_t levels,
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
TextureUsage usage, CallbackHandler* handler, CallbackHandler::Callback callback,
void* user, utils::ImmutableCString&& tag) {
// not supported in this backend
assert_invariant(false && "Not supported in Vulkan backend");
mResourceManager.associateHandle(th.getId(), std::move(tag));
}
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
if (!th) {
return;
@@ -758,6 +797,32 @@ void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program, utils
program);
vprogram.inc();
mResourceManager.associateHandle(ph.getId(), std::move(tag));
if (!mContext.shouldUsePipelineCachePrewarming()) {
return;
}
// If async prewarming is enabled, let's find the proper layout and build the pipeline.
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vkLayouts {};
for (const auto& layoutBinding : program.getDescriptorSetLayouts()) {
DescriptorSetLayout layoutDescription = layoutBinding.layout;
auto layoutHandle = mResourceManager.allocHandle<VulkanDescriptorSetLayout>();
auto layout = mDescriptorSetLayoutCache.createLayout(layoutHandle, std::move(layoutDescription));
vkLayouts[layoutBinding.set] = layout->getVkLayout();
}
StereoscopicType stereoscopicType = mStereoscopicType;
if (stereoscopicType == StereoscopicType::MULTIVIEW && !program.isMultiview()) {
stereoscopicType = StereoscopicType::NONE;
}
VkPipelineLayout layout = mPipelineLayoutCache.getLayout(vkLayouts, vprogram);
mPipelineCache.asyncPrewarmCache(
*vprogram.get(),
layout,
stereoscopicType,
mStereoscopicEyeCount,
program.getPriorityQueue());
}
void VulkanDriver::destroyProgram(Handle<HwProgram> ph) {
@@ -772,7 +837,7 @@ void VulkanDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, utils:
assert_invariant(mDefaultRenderTarget); // Default render target should already exist.
auto renderTarget = resource_ptr<VulkanRenderTarget>::make(&mResourceManager, rth,
std::move(std::move(*mDefaultRenderTarget.get())));
std::move(*mDefaultRenderTarget.get()));
mDefaultRenderTarget = renderTarget;
mResourceManager.associateHandle(rth.getId(), std::move(tag));
@@ -1000,14 +1065,26 @@ Handle<HwIndexBuffer> VulkanDriver::createIndexBufferS() noexcept {
return mResourceManager.allocHandle<VulkanIndexBuffer>();
}
Handle<HwIndexBuffer> VulkanDriver::createIndexBufferAsyncS() noexcept {
return mResourceManager.allocHandle<VulkanIndexBuffer>();
}
Handle<HwBufferObject> VulkanDriver::createBufferObjectS() noexcept {
return mResourceManager.allocHandle<VulkanBufferObject>();
}
Handle<HwBufferObject> VulkanDriver::createBufferObjectAsyncS() noexcept {
return mResourceManager.allocHandle<VulkanBufferObject>();
}
Handle<HwTexture> VulkanDriver::createTextureS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureAsyncS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
@@ -1016,6 +1093,10 @@ Handle<HwTexture> VulkanDriver::createTextureViewSwizzleS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureViewSwizzleAsyncS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::createTextureExternalImage2S() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
@@ -1032,6 +1113,30 @@ Handle<HwTexture> VulkanDriver::importTextureS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
Handle<HwTexture> VulkanDriver::importTextureAsyncS() noexcept {
return mResourceManager.allocHandle<VulkanTexture>();
}
AsyncCallId VulkanDriver::update3DImageAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId VulkanDriver::setVertexBufferObjectAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId VulkanDriver::updateBufferObjectAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId VulkanDriver::updateIndexBufferAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
Handle<HwRenderPrimitive> VulkanDriver::createRenderPrimitiveS() noexcept {
return mResourceManager.allocHandle<VulkanRenderPrimitive>();
}
@@ -1090,6 +1195,11 @@ MemoryMappedBufferHandle VulkanDriver::mapBufferS() noexcept {
return mResourceManager.allocHandle<VulkanMemoryMappedBuffer>();
}
AsyncCallId VulkanDriver::queueCommandAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
void VulkanDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
if (!mmbh) {
return;
@@ -1417,7 +1527,7 @@ bool VulkanDriver::isStereoSupported() {
}
bool VulkanDriver::isParallelShaderCompileSupported() {
return false;
return mContext.shouldUsePipelineCachePrewarming();
}
bool VulkanDriver::isDepthStencilResolveSupported() {
@@ -1437,6 +1547,10 @@ bool VulkanDriver::isDepthClampSupported() {
return mContext.isDepthClampSupported();
}
bool VulkanDriver::isAsynchronousModeEnabled() {
return false;
}
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
switch (workaround) {
case Workaround::SPLIT_EASU: {
@@ -1545,6 +1659,12 @@ void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
vb->setBuffer(bo, index);
}
void VulkanDriver::setVertexBufferObjectAsyncR(AsyncCallId jobId, Handle<HwVertexBuffer> vbh,
uint32_t index, Handle<HwBufferObject> boh, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
@@ -1555,6 +1675,12 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
scheduleDestroy(std::move(p));
}
void VulkanDriver::updateIndexBufferAsyncR(AsyncCallId jobId, Handle<HwIndexBuffer> ibh,
BufferDescriptor&& p, uint32_t byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescriptor&& bd,
uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
@@ -1567,6 +1693,12 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
scheduleDestroy(std::move(bd));
}
void VulkanDriver::updateBufferObjectAsyncR(AsyncCallId jobId, Handle<HwBufferObject> boh,
BufferDescriptor&& bd, uint32_t byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
BufferDescriptor&& bd, uint32_t byteOffset) {
VulkanCommandBuffer& commands = mCommands.get();
@@ -1594,6 +1726,13 @@ void VulkanDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t
scheduleDestroy(std::move(data));
}
void VulkanDriver::update3DImageAsyncR(AsyncCallId jobId, Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t width,
uint32_t height, uint32_t depth, PixelBufferDescriptor&& data, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void VulkanDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
}
@@ -1688,7 +1827,11 @@ void VulkanDriver::generateMipmaps(Handle<HwTexture> th) {
void VulkanDriver::compilePrograms(CompilerPriorityQueue priority,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
if (callback) {
scheduleCallback(handler, user, callback);
if (mContext.shouldUsePipelineCachePrewarming()) {
mPipelineCache.addCachePrewarmCallback(handler, callback, user);
} else {
scheduleCallback(handler, user, callback);
}
}
}
@@ -1696,9 +1839,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
FVK_SYSTRACE_SCOPE();
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
VkExtent2D const extent = rt->getExtent();
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
VulkanCommandBuffer* commandBuffer = rt->isProtected() ?
&mCommands.getProtected() : &mCommands.get();
@@ -1713,9 +1853,16 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
if (sc->isFirstRenderPass()) {
discardStart |= TargetBufferFlags::COLOR;
sc->markFirstRenderPass();
acquireNextSwapchainImage();
}
}
// Note that retrieving the extent must come after the acquireNextSwapchainImage() above;
// otherwise it might be 0.
VkExtent2D const extent = rt->getExtent();
assert_invariant(rt == mDefaultRenderTarget || extent.width > 0 && extent.height > 0);
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
if (rt->hasDepth()) {
auto depth = rt->getDepth();
@@ -1926,20 +2073,13 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
"Vulkan driver does not support distinct draw/read swap chains.");
// Before we can render into the new swapchain, we need to make sure the default render target
// releases its old bound swapchain image.
mDefaultRenderTarget->releaseSwapchain();
resource_ptr<VulkanSwapChain> swapChain =
resource_ptr<VulkanSwapChain>::cast(&mResourceManager, drawSch);
mCurrentSwapChain = swapChain;
bool resized = false;
swapChain->acquire(resized);
if (resized) {
mFramebufferCache.resetFramebuffers();
}
if (UTILS_LIKELY(mDefaultRenderTarget)) {
mDefaultRenderTarget->bindToSwapChain(swapChain);
}
}
void VulkanDriver::commit(Handle<HwSwapChain> sch) {
@@ -2110,6 +2250,14 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
auto dstTarget = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, dst);
auto srcTarget = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, src);
// This is a valid use of the api. We need to make sure that the swapchain image has been
// acquired and associated with the default render target. It's okay to call
// acquireNextSwapchainImage() even if a swapchain image has already been bound to the default
// render target.
if (dstTarget == mDefaultRenderTarget) {
acquireNextSwapchainImage();
}
VkFilter const vkfilter = (filter == SamplerMagFilter::NEAREST) ?
VK_FILTER_NEAREST : VK_FILTER_LINEAR;
@@ -2397,6 +2545,16 @@ void VulkanDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
mQueryManager.endQuery(&(mCommands.get()), vtq);
}
void VulkanDriver::queueCommandAsyncR(AsyncCallId jobId, utils::Invocable<void()>&& command,
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
bool VulkanDriver::cancelAsyncJob(AsyncCallId jobId) {
// TODO: implement this.
return false;
}
void VulkanDriver::debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept {
DriverBase::debugCommandBegin(cmds, synchronous, methodName);
#ifndef NDEBUG
@@ -2431,6 +2589,25 @@ void VulkanDriver::endCommandRecording() {
mDescriptorSetCache.resetCachedState();
}
void VulkanDriver::acquireNextSwapchainImage() {
assert_invariant(mCurrentSwapChain);
assert_invariant(mDefaultRenderTarget);
// Swapchain has already been bound to the default render target. We just return.
if (mDefaultRenderTarget->isSwapchainBound()) {
return;
}
bool resized = false;
mCurrentSwapChain->acquire(resized);
if (resized) {
mFramebufferCache.resetFramebuffers();
}
// Note that ordering this after the above lines is necessary since we set the swapchain image
// to the render target in bindSwapChain().
mDefaultRenderTarget->bindSwapChain(mCurrentSwapChain);
}
// explicit instantiation of the Dispatcher
template class ConcreteDispatcher<VulkanDriver>;
+4 -1
View File
@@ -132,6 +132,8 @@ private:
// Flush the current command buffer and reset the pipeline state.
void endCommandRecording();
void acquireNextSwapchainImage();
VulkanPlatform* mPlatform = nullptr;
fvkmemory::ResourceManager mResourceManager;
@@ -160,7 +162,6 @@ private:
VulkanExternalImageManager mExternalImageManager;
VulkanStreamedImageManager mStreamedImageManager;
// This maps a VulkanSwapchain to a native swapchain. VulkanSwapchain should have a copy of the
// Platform::Swapchain pointer, but queryFrameTimestamps() and queryCompositorTiming() are
// synchronous calls, making access to VulkanSwapchain unsafe (this difference vs other backends
@@ -205,6 +206,8 @@ private:
bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType;
uint8_t const mStereoscopicEyeCount;
backend::AsynchronousMode const mAsynchronousMode;
// setAcquiredImage is a DECL_DRIVER_API_SYNCHRONOUS_N which means we don't necessarily have the
// data to process it at call time. So we store it and process it during updateStreams.
+18 -11
View File
@@ -100,18 +100,18 @@ inline VkShaderStageFlags getVkStage(backend::ShaderStage stage) {
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
BitmaskGroup mask;
for (auto const& binding: layout.bindings) {
switch (binding.type) {
for (auto const& descriptor: layout.descriptors) {
switch (descriptor.type) {
case DescriptorType::UNIFORM_BUFFER: {
if ((binding.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE) {
fromStageFlags(binding.stageFlags, binding.binding, mask.dynamicUbo);
if ((descriptor.flags & DescriptorFlags::DYNAMIC_OFFSET) != DescriptorFlags::NONE) {
fromStageFlags(descriptor.stageFlags, descriptor.binding, mask.dynamicUbo);
} else {
fromStageFlags(binding.stageFlags, binding.binding, mask.ubo);
fromStageFlags(descriptor.stageFlags, descriptor.binding, mask.ubo);
}
break;
}
case DescriptorType::SAMPLER_EXTERNAL:
fromStageFlags(binding.stageFlags, binding.binding, mask.externalSampler);
fromStageFlags(descriptor.stageFlags, descriptor.binding, mask.externalSampler);
UTILS_FALLTHROUGH;
case DescriptorType::SAMPLER_2D_FLOAT:
@@ -139,11 +139,11 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
case DescriptorType::SAMPLER_2D_MS_ARRAY_FLOAT:
case DescriptorType::SAMPLER_2D_MS_ARRAY_INT:
case DescriptorType::SAMPLER_2D_MS_ARRAY_UINT: {
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
fromStageFlags(descriptor.stageFlags, descriptor.binding, mask.sampler);
break;
}
case DescriptorType::INPUT_ATTACHMENT: {
fromStageFlags(binding.stageFlags, binding.binding, mask.inputAttachment);
fromStageFlags(descriptor.stageFlags, descriptor.binding, mask.inputAttachment);
break;
}
case DescriptorType::SHADER_STORAGE_BUFFER:
@@ -387,8 +387,9 @@ VulkanRenderTarget::VulkanRenderTarget()
VulkanRenderTarget::~VulkanRenderTarget() = default;
void VulkanRenderTarget::bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain) {
void VulkanRenderTarget::bindSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain) {
assert_invariant(!mOffscreen);
assert_invariant(!mInfo->colors[0]);
VkExtent2D const extent = swapchain->getExtent();
width = extent.width;
@@ -396,7 +397,8 @@ void VulkanRenderTarget::bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain
mProtected = swapchain->isProtected();
VulkanAttachment color = createSwapchainAttachment(swapchain->getCurrentColor());
mInfo->attachments = {color};
assert_invariant(mInfo->attachments.size() == 0);
mInfo->attachments.push_back(color);
auto& fbkey = mInfo->fbkey;
auto& rpkey = mInfo->rpkey;
@@ -422,6 +424,11 @@ void VulkanRenderTarget::bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain
mInfo->colors.set(0);
}
void VulkanRenderTarget::releaseSwapchain() {
mInfo->colors = {};
mInfo->attachments.clear();
}
VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, fvkmemory::ResourceManager* resourceManager,
VmaAllocator allocator, VulkanCommands* commands, uint32_t width, uint32_t height,
@@ -675,7 +682,7 @@ void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> b
VulkanBufferObject::VulkanBufferObject(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage)
: HwBufferObject(byteCount),
: HwBufferObject(byteCount, false),
bindingType(bindingType),
mBuffer(context, allocator, stagePool, bufferCache, getBufferObjectBinding(bindingType),
usage, byteCount) {}
+8 -2
View File
@@ -366,7 +366,13 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
inline bool isSwapChain() const { return !mOffscreen; }
inline bool isProtected() const { return mProtected; }
void bindToSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain);
void bindSwapChain(fvkmemory::resource_ptr<VulkanSwapChain> swapchain);
void releaseSwapchain();
bool isSwapchainBound() const {
return isSwapChain() && mInfo->colors[0];
}
void emitBarriersBeginRenderPass(VulkanCommandBuffer& commands);
@@ -470,7 +476,7 @@ struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
VulkanIndexBuffer(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint8_t elementSize,
uint32_t indexCount)
: HwIndexBuffer(elementSize, indexCount),
: HwIndexBuffer(elementSize, indexCount, false),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32),
mBuffer(context, allocator, stagePool, bufferCache, VulkanBufferBinding::INDEX,
BufferUsage::STATIC, elementSize * indexCount) {}
@@ -16,6 +16,7 @@
#include "VulkanPipelineCache.h"
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Panic.h>
@@ -36,6 +37,8 @@ namespace filament::backend {
namespace {
using utils::JobSystem;
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
void printPipelineFeedbackInfo(VkPipelineCreationFeedbackCreateInfo const& feedbackInfo) {
VkPipelineCreationFeedback const& pipelineInfo = *feedbackInfo.pPipelineCreationFeedback;
@@ -66,13 +69,26 @@ void printPipelineFeedbackInfo(VkPipelineCreationFeedbackCreateInfo const& feedb
} // namespace
VulkanPipelineCache::VulkanPipelineCache(VkDevice device, VulkanContext const& context)
VulkanPipelineCache::VulkanPipelineCache(DriverBase& driver, VkDevice device, VulkanContext const& context)
: mDevice(device),
mCallbackManager(driver),
mContext(context) {
VkPipelineCacheCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
};
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
if (mContext.shouldUsePipelineCachePrewarming()) {
mCompilerThreadPool.init(
/*threadCount=*/1,
[]() {
JobSystem::setThreadName("CompilerThreadPool");
// This thread should be lower priority than the main thread.
JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY);
}, []() {
// No cleanup required.
});
}
}
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
@@ -87,9 +103,12 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::getOrCreatePipelin
pipeline.lastUsed = mCurrentTime;
return &pipeline;
}
auto ret = createPipeline();
ret->lastUsed = mCurrentTime;
return ret;
PipelineCacheEntry cacheEntry {
.handle = createPipeline(mPipelineRequirements),
.lastUsed = mCurrentTime,
};
assert_invariant(cacheEntry.handle != VK_NULL_HANDLE && "Pipeline handle is VK_NULL_HANDLE");
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
}
void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
@@ -107,70 +126,139 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
}
}
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
assert_invariant(mPipelineRequirements.shaders[0] && "Vertex shader is not bound.");
assert_invariant(mPipelineRequirements.layout && "No pipeline layout specified");
void VulkanPipelineCache::asyncPrewarmCache(const VulkanProgram& program,
VkPipelineLayout layout,
StereoscopicType stereoscopicType,
uint8_t stereoscopicViewCount, CompilerPriorityQueue priority) {
PipelineKey key {
.shaders = {
program.getVertexShader(),
program.getFragmentShader(),
},
// We're using dynamic rendering, so this should be empty.
.renderPass = VK_NULL_HANDLE,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.subpassIndex = 0,
// We're using vertex input dynamic state, so these should be empty.
.vertexAttributes = {},
.vertexBuffers = {},
// Create a reasonable default raster state; we're assuming this is not cached.
.rasterState = {
.cullMode = VK_CULL_MODE_NONE,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.blendEnable = VK_FALSE,
.depthWriteEnable = VK_FALSE,
.alphaToCoverageEnable = VK_FALSE,
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE,
.dstColorBlendFactor = VK_BLEND_FACTOR_ONE,
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE,
.colorWriteMask = 0,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.depthClamp = VK_FALSE,
.colorTargetCount = 1,
.colorBlendOp = BlendEquation::SUBTRACT,
.alphaBlendOp = BlendEquation::SUBTRACT,
.depthCompareOp = SamplerCompareFunc::L,
.depthBiasConstantFactor = 0.f,
.depthBiasSlopeFactor = 0.f,
},
.layout = layout,
};
PipelineDynamicOptions dynamicOptions {
.useDynamicVertexInputState = true,
.useDynamicRenderPasses = true,
.stereoscopicType = stereoscopicType,
.stereoscopicViewCount = stereoscopicViewCount,
};
CallbackManager::Handle cmh = mCallbackManager.get();
auto token = std::make_shared<ProgramToken>();
mCompilerThreadPool.queue(priority, token, [this, key, dynamicOptions, cmh]() mutable {
VkPipeline pipeline = createPipeline(key, dynamicOptions);
mCallbackManager.put(cmh);
// We don't actually need this pipeline, we just wanted to force the driver to cache
// the pipeline's information.
vkDestroyPipeline(mDevice, pipeline, VKALLOC);
});
}
VkPipeline VulkanPipelineCache::createPipeline(
const PipelineKey& key, const PipelineDynamicOptions& dynamicOptions) noexcept {
assert_invariant(key.shaders[0] && "Vertex shader is not bound.");
assert_invariant(key.layout && "No pipeline layout specified");
VkPipelineShaderStageCreateInfo shaderStages[SHADER_MODULE_COUNT];
shaderStages[0] = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = mPipelineRequirements.shaders[0],
.module = key.shaders[0],
.pName = "main",
};
shaderStages[1] = shaderStages[0];
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shaderStages[1].module = mPipelineRequirements.shaders[1];
shaderStages[1].module = key.shaders[1];
bool const hasFragmentShader = shaderStages[1].module != VK_NULL_HANDLE;
VkPipelineColorBlendAttachmentState colorBlendAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT];
VkPipelineColorBlendStateCreateInfo colorBlendState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.attachmentCount = mPipelineRequirements.rasterState.colorTargetCount,
.attachmentCount = key.rasterState.colorTargetCount,
.pAttachments = colorBlendAttachments,
};
// Expand our size-optimized structs into the proper Vk structs.
uint32_t numVertexAttribs = 0;
uint32_t numVertexBuffers = 0;
VkPipelineVertexInputStateCreateInfo vertexInputState;
VkVertexInputAttributeDescription vertexAttributes[VERTEX_ATTRIBUTE_COUNT];
VkVertexInputBindingDescription vertexBuffers[VERTEX_ATTRIBUTE_COUNT];
for (uint32_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
if (mPipelineRequirements.vertexAttributes[i].format > 0) {
vertexAttributes[numVertexAttribs++] = mPipelineRequirements.vertexAttributes[i];
}
if (mPipelineRequirements.vertexBuffers[i].stride > 0) {
vertexBuffers[numVertexBuffers++] = mPipelineRequirements.vertexBuffers[i];
if (!dynamicOptions.useDynamicVertexInputState) {
uint32_t numVertexAttribs = 0;
uint32_t numVertexBuffers = 0;
for (uint32_t i = 0; i < VERTEX_ATTRIBUTE_COUNT; i++) {
if (key.vertexAttributes[i].format > 0) {
vertexAttributes[numVertexAttribs++] = key.vertexAttributes[i];
}
if (key.vertexBuffers[i].stride > 0) {
vertexBuffers[numVertexBuffers++] = key.vertexBuffers[i];
}
}
vertexInputState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = numVertexBuffers,
.pVertexBindingDescriptions = vertexBuffers,
.vertexAttributeDescriptionCount = numVertexAttribs,
.pVertexAttributeDescriptions = vertexAttributes,
};
}
VkPipelineVertexInputStateCreateInfo vertexInputState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = numVertexBuffers,
.pVertexBindingDescriptions = vertexBuffers,
.vertexAttributeDescriptionCount = numVertexAttribs,
.pVertexAttributeDescriptions = vertexAttributes,
};
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = (VkPrimitiveTopology) mPipelineRequirements.topology,
.topology = (VkPrimitiveTopology) key.topology,
};
VkPipelineViewportStateCreateInfo viewportState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.scissorCount = 1,
};
VkDynamicState dynamicStateEnables[] = {
constexpr size_t maxDynamicStates = 3;
size_t numDynamicStates = 2;
VkDynamicState enabledDynamicStates[maxDynamicStates] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
};
if (dynamicOptions.useDynamicVertexInputState) {
enabledDynamicStates[numDynamicStates++] = VK_DYNAMIC_STATE_VERTEX_INPUT_EXT;
}
VkPipelineDynamicStateCreateInfo dynamicState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = 2,
.pDynamicStates = dynamicStateEnables,
.dynamicStateCount = static_cast<uint32_t>(numDynamicStates),
.pDynamicStates = enabledDynamicStates,
};
auto const& raster = mPipelineRequirements.rasterState;
auto const& raster = key.rasterState;
VkPipelineRasterizationStateCreateInfo vkRaster = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.depthClampEnable = raster.depthClamp,
@@ -218,7 +306,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = hasFragmentShader ? SHADER_MODULE_COUNT : 1,
.pStages = shaderStages,
.pVertexInputState = &vertexInputState,
.pVertexInputState = dynamicOptions.useDynamicVertexInputState ? nullptr : &vertexInputState,
.pInputAssemblyState = &inputAssemblyState,
.pViewportState = &viewportState,
.pRasterizationState = &vkRaster,
@@ -226,9 +314,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
.pDepthStencilState = &vkDs,
.pColorBlendState = &colorBlendState,
.pDynamicState = &dynamicState,
.layout = mPipelineRequirements.layout,
.renderPass = mPipelineRequirements.renderPass,
.subpass = mPipelineRequirements.subpassIndex,
.layout = key.layout,
.renderPass = key.renderPass,
.subpass = key.subpassIndex,
};
// There are no color attachments if there is no bound fragment shader. (e.g. shadow map gen)
@@ -252,6 +340,27 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
}
}
VkPipelineRenderingCreateInfoKHR renderingInfo {};
VkFormat pipelineRenderingColorFormats[] = {VK_FORMAT_UNDEFINED};
if (dynamicOptions.useDynamicRenderPasses) {
renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
renderingInfo.pNext = pipelineCreateInfo.pNext;
// Fill values in with empty values where we do not already have values.
renderingInfo.depthAttachmentFormat = VK_FORMAT_UNDEFINED;
renderingInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
// If multiview, create a bitmask with bits representing each enabled view set to 1;
// otherwise, set the view mask to 0.
renderingInfo.viewMask = dynamicOptions.stereoscopicType == StereoscopicType::MULTIVIEW ?
(1 << dynamicOptions.stereoscopicViewCount) - 1 : 0;
if (hasFragmentShader) {
renderingInfo.colorAttachmentCount = 1;
renderingInfo.pColorAttachmentFormats = pipelineRenderingColorFormats;
}
pipelineCreateInfo.pNext = &renderingInfo;
}
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
<< shaderStages[1].module << ")";
@@ -271,11 +380,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
pipelineCreateInfo.pNext = &feedbackInfo;
}
#endif
PipelineCacheEntry cacheEntry = {
.lastUsed = mCurrentTime,
};
VkPipeline pipeline;
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
VKALLOC, &cacheEntry.handle);
VKALLOC, &pipeline);
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
FVK_LOGD << "vkCreateGraphicsPipelines with shaders = (" << shaderStages[0].module << ", "
@@ -289,9 +396,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
assert_invariant(error == VK_SUCCESS);
if (error != VK_SUCCESS) {
FVK_LOGE << "vkCreateGraphicsPipelines error " << error;
return nullptr;
return VK_NULL_HANDLE;
}
return &mPipelines.emplace(mPipelineRequirements, cacheEntry).first.value();
return pipeline;
}
void VulkanPipelineCache::bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept {
@@ -334,6 +441,14 @@ void VulkanPipelineCache::bindVertexArray(VkVertexInputAttributeDescription cons
}
}
void VulkanPipelineCache::addCachePrewarmCallback(CallbackHandler* handler,
const CallbackHandler::Callback callback,
void* user) {
if (callback) {
mCallbackManager.setCallback(handler, callback, user);
}
}
void VulkanPipelineCache::resetBoundPipeline() {
mBoundPipeline = {};
}
@@ -345,6 +460,9 @@ void VulkanPipelineCache::terminate() noexcept {
mPipelines.clear();
resetBoundPipeline();
mCallbackManager.terminate();
mCompilerThreadPool.terminate();
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
}
@@ -17,6 +17,8 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
#include "CallbackManager.h"
#include "CompilerThreadPool.h"
#include "VulkanCommands.h"
#include <backend/DriverEnums.h>
@@ -86,14 +88,38 @@ public:
static_assert(sizeof(RasterState) == 16, "RasterState must not have implicit padding.");
VulkanPipelineCache(VkDevice device, VulkanContext const& context);
/**
* Creates a new instance of a pipeline cache for graphics pipelines.
*
* @param driver The driver this is being instantiated for. This is used only for construction of
* the callback manager, which references the driver for scheduling callbacks.
* @param device The device that the pipelines will be created and run on.
* @param context Information about the current instance of Vulkan, such as supported extensions,
* and enabled features.
*/
VulkanPipelineCache(DriverBase& driver, VkDevice device, VulkanContext const& context);
void bindLayout(VkPipelineLayout layout) noexcept;
// Loads a fake pipeline into memory on a separate thread, with the intent of
// preloading the Vulkan cache with enough information to have a cache hit when
// compiling the pipeline on the main thread at draw time. This is very dependent
// on the implementation of the driver on the current device; it's expected to work
// on devices with VK_EXT_vertex_input_dynamic_state and VK_KHR_dynamic_rendering.
void asyncPrewarmCache(const VulkanProgram& program, VkPipelineLayout layout,
StereoscopicType stereoscopicType, uint8_t stereoscopicViewCount,
CompilerPriorityQueue priority);
// Notifies the callback once all in-flight async cache prewarm jobs are complete.
// This typically signals that it is safe to use a material at draw time without
// hitching.
void addCachePrewarmCallback(CallbackHandler* handler,
const CallbackHandler::Callback callback,
void* user);
// Creates a new pipeline if necessary and binds it using vkCmdBindPipeline.
void bindPipeline(VulkanCommandBuffer* commands);
// Each of the following methods are fast and do not make Vulkan calls.
void bindLayout(VkPipelineLayout layout) noexcept;
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
void bindRasterState(RasterState const& rasterState) noexcept;
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
@@ -164,6 +190,19 @@ private:
VkPipelineLayout layout; // 8 : 304
};
// Provides information about any dynamic state that should be used in creation of the
// pipeline (if supported).
struct PipelineDynamicOptions {
// Requires `VK_EXT_vertex_input_dynamic_state` to enable.
bool useDynamicVertexInputState = false;
// Requires `VK_KHR_dynamic_rendering` to enable.
bool useDynamicRenderPasses = false;
// Only used if `useDynamicRenderPasses` is true.
StereoscopicType stereoscopicType = StereoscopicType::NONE;
// Only used if `stereoscopicType` is `StereoscopicType::MULTIVIEW`.
uint8_t stereoscopicViewCount = 2;
};
static_assert(sizeof(PipelineKey) == 312, "PipelineKey must not have implicit padding.");
using PipelineHashFn = utils::hash::MurmurHashFn<PipelineKey>;
@@ -195,8 +234,15 @@ private:
PipelineMap mPipelines;
// Creates a pipeline, with all optional dynamic pipeline states diabled.
// Note - because PipelineDynamicOptions is defined within VulkanPipelineCache,
// we cannot define the function with a default arg for it explicitly.
inline VkPipeline createPipeline(const PipelineKey& key) noexcept {
return createPipeline(key, {});
}
// These helpers all return unstable pointers that should not be stored.
PipelineCacheEntry* createPipeline() noexcept;
VkPipeline createPipeline(const PipelineKey& key, const PipelineDynamicOptions& dynamicOptions) noexcept;
// Immutable state.
VkDevice mDevice = VK_NULL_HANDLE;
@@ -211,6 +257,14 @@ private:
// Current bindings for the pipeline and descriptor sets.
PipelineKey mBoundPipeline = {};
// Thread pool that allows us to "prewarm" the pipeline cache, reducing draw-time
// pipeline compilation time.
CompilerThreadPool mCompilerThreadPool;
// Callback manager that allows us to notify the frontend when a set of pipelines have
// been prewarmed, signifying that it is safe to compile pipelines at draw time.
CallbackManager mCallbackManager;
[[maybe_unused]] VulkanContext const& mContext;
};
@@ -346,7 +346,7 @@ VulkanTexture::VulkanTexture(VulkanContext const& context, VkDevice device, VmaA
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage tusage,
VulkanStagePool& stagePool)
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth,
TextureFormat::UNUSED, tusage),
TextureFormat::UNUSED, tusage, false),
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
commands, allocator, device, image, memory, format,
fvkutils::getViewType(SamplerType::SAMPLER_2D),
@@ -362,7 +362,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage) {
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage, false) {
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
VkFormat const vkFormat = fvkutils::getVkFormat(tformat);
bool const isProtected = any(tusage & TextureUsage::PROTECTED);
@@ -492,7 +492,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
uint8_t levelCount)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
src->format, src->usage, src->asynchronous),
mState(src->mState) {
mPrimaryViewRange = src->mPrimaryViewRange;
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
@@ -504,7 +504,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage),
src->format, src->usage, src->asynchronous),
mState(src->mState),
mPrimaryViewRange(src->mPrimaryViewRange),
mSwizzle(composeSwizzle(src->mSwizzle, swizzle)) {}
@@ -227,7 +227,11 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
#if defined(__APPLE__)
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME,
VK_KHR_MULTIVIEW_EXTENSION_NAME,
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
// Required for dynamic rendering, enable this too.
VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME,
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME,
@@ -255,188 +259,6 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
return exts;
}
VkInstance createInstance(ExtensionSet const& requiredExts) {
// Create the Vulkan instance.
VkApplicationInfo appInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pEngineName = "Filament",
.apiVersion =
VK_MAKE_API_VERSION(0, FVK_REQUIRED_VERSION_MAJOR, FVK_REQUIRED_VERSION_MINOR, 0),
};
VkInstance instance;
VkInstanceCreateInfo instanceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &appInfo,
};
bool validationFeaturesSupported = false;
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
auto const enabledLayers = getEnabledLayers();
if (!enabledLayers.empty()) {
// If layers are supported, Check if VK_EXT_validation_features is supported.
FixedCapacityVector<VkExtensionProperties> const availableValidationExts
= fvkutils::enumerate(vkEnumerateInstanceExtensionProperties,
"VK_LAYER_KHRONOS_validation");
for (auto const& extProps: availableValidationExts) {
if (!strcmp(extProps.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME)) {
validationFeaturesSupported = true;
break;
}
}
instanceCreateInfo.enabledLayerCount = (uint32_t) enabledLayers.size();
instanceCreateInfo.ppEnabledLayerNames = enabledLayers.data();
} else {
#if defined(__ANDROID__)
FVK_LOGD << "Validation layers are not available; did you set jniLibs in your "
<< "gradle file?";
#else
FVK_LOGD << "Validation layer not available; did you install the Vulkan SDK?\n"
<< "Please ensure that VK_LAYER_PATH is set correctly.";
#endif // __ANDROID__
}
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
// The Platform class can require 1 or 2 instance extensions, plus we'll request at most 6
// instance extensions here in the common code. So that's a max of 8.
constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 8;
char const* ppEnabledExtensions[MAX_INSTANCE_EXTENSION_COUNT];
uint32_t enabledExtensionCount = 0;
if (validationFeaturesSupported) {
ppEnabledExtensions[enabledExtensionCount++] = VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME;
}
// Request platform-specific extensions.
for (auto const& requiredExt: requiredExts) {
assert_invariant(enabledExtensionCount < MAX_INSTANCE_EXTENSION_COUNT);
ppEnabledExtensions[enabledExtensionCount++] = requiredExt.data();
}
instanceCreateInfo.enabledExtensionCount = enabledExtensionCount;
instanceCreateInfo.ppEnabledExtensionNames = ppEnabledExtensions;
if (setContains(requiredExts, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
}
// Validation features
VkValidationFeatureEnableEXT enables[] = {
VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT,
VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT,
};
VkValidationFeaturesEXT features = {
.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
.enabledValidationFeatureCount = sizeof(enables) / sizeof(enables[0]),
.pEnabledValidationFeatures = enables,
};
if (validationFeaturesSupported) {
chainStruct(&instanceCreateInfo, &features);
}
VkResult result = vkCreateInstance(&instanceCreateInfo, VKALLOC, &instance);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Vulkan instance. error=" << static_cast<int32_t>(result);
return instance;
}
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures2 const& features,
VkPhysicalDeviceVulkan11Features const& vk11Features,
uint32_t graphicsQueueFamilyIndex,
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions,
bool requestImageView2DOn3DImage) {
VkDevice device;
float queuePriority[] = {1.0f};
VkDeviceCreateInfo deviceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
};
FixedCapacityVector<const char*> requestExtensions;
requestExtensions.reserve(deviceExtensions.size() + 1);
// TODO: We don't really need this if we only ever expect headless swapchains.
requestExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
for (auto const& ext: deviceExtensions) {
requestExtensions.push_back(ext.data());
}
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
deviceQueueCreateInfo[0] = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = graphicsQueueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority[0],
};
// Protected queue
deviceQueueCreateInfo[1] = deviceQueueCreateInfo[0];
deviceQueueCreateInfo[1].flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
bool const hasProtectedQueue = protectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX;
deviceCreateInfo.queueCreateInfoCount = hasProtectedQueue ? 2 : 1;
deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo;
// We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures = {
.depthClamp = features.features.depthClamp,
.samplerAnisotropy = features.features.samplerAnisotropy,
.textureCompressionETC2 = features.features.textureCompressionETC2,
.textureCompressionBC = features.features.textureCompressionBC,
.shaderClipDistance = features.features.shaderClipDistance,
};
VkPhysicalDeviceFeatures2 enabledFeatures2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.features = enabledFeatures,
};
chainStruct(&deviceCreateInfo, &enabledFeatures2);
VkPhysicalDeviceVulkan11Features enabledVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.multiview = vk11Features.multiview,
#if defined(__ANDROID__)
.samplerYcbcrConversion = vk11Features.samplerYcbcrConversion,
#endif
};
chainStruct(&deviceCreateInfo, &enabledVk11Features);
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
VkPhysicalDevicePortabilitySubsetFeaturesKHR portability = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR,
.imageViewFormatSwizzle = VK_TRUE,
.imageView2DOn3DImage = requestImageView2DOn3DImage ? VK_TRUE : VK_FALSE,
.mutableComparisonSamplers = VK_TRUE,
};
if (setContains(deviceExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
chainStruct(&deviceCreateInfo, &portability);
}
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
.multiview = vk11Features.multiview,
.multiviewGeometryShader = VK_FALSE,
.multiviewTessellationShader = VK_FALSE,
};
if (setContains(deviceExtensions, VK_KHR_MULTIVIEW_EXTENSION_NAME)) {
chainStruct(&deviceCreateInfo, &multiview);
}
VkPhysicalDeviceProtectedMemoryFeatures protectedMemory = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
.protectedMemory = VK_TRUE,
};
if (hasProtectedQueue) {
// Enable protected memory, if requested.
chainStruct(&deviceCreateInfo, &protectedMemory);
}
VkResult result = vkCreateDevice(physicalDevice, &deviceCreateInfo, VKALLOC, &device);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateDevice error=" << static_cast<int32_t>(result);
return device;
}
// This method is used to enable/disable extensions based on external factors (i.e.
// driver/device workarounds).
std::tuple<ExtensionSet, ExtensionSet> pruneExtensions(VkPhysicalDevice device,
@@ -719,11 +541,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
mImpl->mSharedContext = true;
}
VulkanContext& context = mImpl->mContext;
// Pass along relevant driver config (feature flags)
context.mStagingBufferBypassEnabled = driverConfig.vulkanEnableStagingBufferBypass;
ExtensionSet instExts;
// If using a shared context, we do not assume any extensions.
if (!mImpl->mSharedContext) {
@@ -749,7 +566,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
instExts.merge(getRequiredInstanceExtensions());
}
if (mImpl->mInstance == VK_NULL_HANDLE) {
mImpl->mInstance = createInstance(instExts);
createInstance(instExts);
}
assert_invariant(mImpl->mInstance != VK_NULL_HANDLE);
@@ -761,27 +578,29 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
<< "Cannot both share context and indicate GPU preference";
if (mImpl->mPhysicalDevice == VK_NULL_HANDLE) {
mImpl->mPhysicalDevice = selectPhysicalDevice(mImpl->mInstance, pref);
mImpl->mPhysicalDevice = selectVkPhysicalDevice(mImpl->mInstance);
}
assert_invariant(mImpl->mPhysicalDevice != VK_NULL_HANDLE);
printDeviceInfo(mImpl->mInstance, mImpl->mPhysicalDevice);
VkPhysicalDeviceProtectedMemoryFeatures queryProtectedMemoryFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
};
// Note that we're chaining a struct on the stack into a struct in context, which will then be
// passed to the driver. However, this should be ok since the use of
// queryProtectedMemoryFeatures is only in this function.
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPhysicalDeviceVk11Features);
ExtensionSet deviceExts;
// If a shared context is not used, we will use our own provided list; otherwise, we do not
// assume any extensions.
if (!mImpl->mSharedContext) {
deviceExts = getDeviceExtensions(mImpl->mPhysicalDevice);
auto [prunedInstExts, prunedDeviceExts] =
pruneExtensions(mImpl->mPhysicalDevice, driverConfig, instExts, deviceExts);
instExts = prunedInstExts;
deviceExts = prunedDeviceExts;
}
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
vkGetPhysicalDeviceFeatures2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
vkGetPhysicalDeviceMemoryProperties(mImpl->mPhysicalDevice, &context.mMemoryProperties);
// Query all the supported physical device features and enable/disable any feature as needed
queryAndSetDeviceFeatures(driverConfig, instExts, deviceExts, sharedContext);
VulkanContext const& context = mImpl->mContext;
// Initialize the required queues
if (mImpl->mGraphicsQueueFamilyIndex == INVALID_VK_INDEX) {
mImpl->mGraphicsQueueFamilyIndex =
identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice, VK_QUEUE_GRAPHICS_BIT);
@@ -795,10 +614,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
mImpl->mGraphicsQueueIndex = 0;
}
// We know we need to allocate the protected version of the VK objects
context.mProtectedMemorySupported =
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
if (context.mProtectedMemorySupported) {
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
@@ -809,39 +624,25 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
}
}
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
if (driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
}
// TODO: Add support of VK_KHR_global_priority with `driverConfig.gpuContextPriority`
// in VulkanPlatform::createDriver.
ExtensionSet deviceExts;
// If using a shared context, we do not assume any extensions.
if (!mImpl->mSharedContext) {
deviceExts = getDeviceExtensions(mImpl->mPhysicalDevice);
auto [prunedInstExts, prunedDeviceExts] =
pruneExtensions(mImpl->mPhysicalDevice, driverConfig, instExts, deviceExts);
instExts = prunedInstExts;
deviceExts = prunedDeviceExts;
MiscDeviceFeatures requestedFeatures {};
if (setContains(deviceExts, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
requestedFeatures.dynamicRendering =
context.mDynamicRenderingFeatures.dynamicRendering == VK_TRUE;
}
bool requestPortabilitySubsetImageView2DOn3DImage = false;
if (setContains(deviceExts, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
// We are on a non-conformant vulkan implementation so we need to ascertain if the features
// we need are available.
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPortabilitySubsetFeatures);
vkGetPhysicalDeviceFeatures2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
requestPortabilitySubsetImageView2DOn3DImage =
requestedFeatures.imageView2Don3DImage =
context.mPortabilitySubsetFeatures.imageView2DOn3DImage == VK_TRUE;
}
if (mImpl->mDevice == VK_NULL_HANDLE) {
mImpl->mDevice = createLogicalDevice(mImpl->mPhysicalDevice,
context.mPhysicalDeviceFeatures, context.mPhysicalDeviceVk11Features,
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
createLogicalDeviceAndQueues(deviceExts, context.mPhysicalDeviceFeatures.features,
context.mPhysicalDeviceVk11Features, context.mProtectedMemorySupported,
requestedFeatures);
}
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
@@ -865,55 +666,17 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
assert_invariant(mImpl->mProtectedGraphicsQueue != VK_NULL_HANDLE);
}
// Store the extension support in the context
if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mPipelineCreationFeedbackSupported =
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
} else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
context.mDebugMarkersSupported = scontext->debugMarkersSupported;
}
// Check the availability of lazily allocated memory
context.mLazilyAllocatedMemorySupported = false;
// RenderDoc doesn't support lazy allocated memory
if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
++i) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
}
}
context.mIsUnifiedMemoryArchitecture = hasUnifiedMemoryArchitecture(context.mMemoryProperties);
#ifdef NDEBUG
// If we are in release build, we should not have turned on debug extensions
FILAMENT_CHECK_POSTCONDITION(!context.mDebugUtilsSupported && !context.mDebugMarkersSupported)
<< "Debug utils should not be enabled in release build.";
#endif
context.mDepthStencilFormats = findAttachmentDepthStencilFormats(mImpl->mPhysicalDevice);
context.mBlittableDepthStencilFormats =
findBlittableDepthStencilFormats(mImpl->mPhysicalDevice);
context.mFenceExportFlags = getFenceExportFlags();
assert_invariant(context.mDepthStencilFormats.size() > 0);
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
printDepthFormats(mImpl->mPhysicalDevice);
#endif
// Note that `context` is an alias of mImpl->mContext.
return VulkanDriver::create(this, context, driverConfig);
return VulkanDriver::create(this, mImpl->mContext, driverConfig);
}
// This needs to be explictly written for
@@ -1035,4 +798,299 @@ bool VulkanPlatform::isTransientAttachmentSupported() const noexcept {
return mImpl->mContext.isLazilyAllocatedMemorySupported();
}
VkInstance VulkanPlatform::createVkInstance(const VkInstanceCreateInfo& createInfo) noexcept {
VkInstance instance = VK_NULL_HANDLE;
VkResult result = vkCreateInstance(&createInfo, VKALLOC, &instance);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "Unable to create Vulkan instance. error=" << static_cast<int32_t>(result);
return instance;
}
VkPhysicalDevice VulkanPlatform::selectVkPhysicalDevice(VkInstance instance) noexcept {
VulkanPlatform::Customization::GPUPreference const pref = getCustomization().gpu;
return selectPhysicalDevice(mImpl->mInstance, pref);
}
VkDevice VulkanPlatform::createVkDevice(const VkDeviceCreateInfo& createInfo) noexcept {
VkDevice device = VK_NULL_HANDLE;
VkResult result = vkCreateDevice(mImpl->mPhysicalDevice, &createInfo, VKALLOC, &device);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateDevice error=" << static_cast<int32_t>(result);
return device;
}
void VulkanPlatform::createInstance(ExtensionSet const& requiredExts) noexcept {
// Create the Vulkan instance.
VkApplicationInfo appInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pEngineName = "Filament",
.apiVersion =
VK_MAKE_API_VERSION(0, FVK_REQUIRED_VERSION_MAJOR, FVK_REQUIRED_VERSION_MINOR, 0),
};
VkInstanceCreateInfo instanceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &appInfo,
};
bool validationFeaturesSupported = false;
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
auto const enabledLayers = getEnabledLayers();
if (!enabledLayers.empty()) {
// If layers are supported, Check if VK_EXT_validation_features is supported.
FixedCapacityVector<VkExtensionProperties> const availableValidationExts =
fvkutils::enumerate(vkEnumerateInstanceExtensionProperties,
"VK_LAYER_KHRONOS_validation");
for (auto const& extProps: availableValidationExts) {
if (!strcmp(extProps.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME)) {
validationFeaturesSupported = true;
break;
}
}
instanceCreateInfo.enabledLayerCount = (uint32_t) enabledLayers.size();
instanceCreateInfo.ppEnabledLayerNames = enabledLayers.data();
} else {
#if defined(__ANDROID__)
FVK_LOGD << "Validation layers are not available; did you set jniLibs in your "
<< "gradle file?";
#else
FVK_LOGD << "Validation layer not available; did you install the Vulkan SDK?\n"
<< "Please ensure that VK_LAYER_PATH is set correctly.";
#endif // __ANDROID__
}
#endif // FVK_ENABLED(FVK_DEBUG_VALIDATION)
// The Platform class can require 1 or 2 instance extensions, plus we'll request at most 6
// instance extensions here in the common code. So that's a max of 8.
constexpr uint32_t MAX_INSTANCE_EXTENSION_COUNT = 8;
char const* ppEnabledExtensions[MAX_INSTANCE_EXTENSION_COUNT];
uint32_t enabledExtensionCount = 0;
if (validationFeaturesSupported) {
ppEnabledExtensions[enabledExtensionCount++] = VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME;
}
// Request platform-specific extensions.
for (auto const& requiredExt: requiredExts) {
assert_invariant(enabledExtensionCount < MAX_INSTANCE_EXTENSION_COUNT);
ppEnabledExtensions[enabledExtensionCount++] = requiredExt.data();
}
instanceCreateInfo.enabledExtensionCount = enabledExtensionCount;
instanceCreateInfo.ppEnabledExtensionNames = ppEnabledExtensions;
if (setContains(requiredExts, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
}
// Validation features
VkValidationFeatureEnableEXT enables[] = {
VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT,
VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT,
};
VkValidationFeaturesEXT features = {
.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT,
.enabledValidationFeatureCount = sizeof(enables) / sizeof(enables[0]),
.pEnabledValidationFeatures = enables,
};
if (validationFeaturesSupported) {
chainStruct(&instanceCreateInfo, &features);
}
mImpl->mInstance = createVkInstance(instanceCreateInfo);
}
void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& driverConfig,
ExtensionSet const& instExts, ExtensionSet const& deviceExts,
void* sharedContext) noexcept {
VulkanContext& context = mImpl->mContext;
VkPhysicalDeviceProtectedMemoryFeatures queryProtectedMemoryFeatures = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
};
// Note that we're chaining a struct on the stack into a struct in context, which will then be
// passed to the driver. However, this should be ok since the use of
// queryProtectedMemoryFeatures is only in this function.
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPhysicalDeviceVk11Features);
if (setContains(deviceExts, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
chainStruct(&context.mPhysicalDeviceFeatures, &context.mDynamicRenderingFeatures);
}
if (setContains(deviceExts, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
// We are on a non-conformant vulkan implementation so we need to ascertain if the features
// we need are available.
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPortabilitySubsetFeatures);
}
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
// physicalDeviceFeatures.
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
vkGetPhysicalDeviceFeatures2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceFeatures);
vkGetPhysicalDeviceMemoryProperties(mImpl->mPhysicalDevice, &context.mMemoryProperties);
// Store the extension support in the context
if (!mImpl->mSharedContext) {
context.mDebugUtilsSupported =
setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
context.mDebugMarkersSupported =
setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
context.mPipelineCreationFeedbackSupported =
setContains(deviceExts, VK_EXT_PIPELINE_CREATION_FEEDBACK_EXTENSION_NAME);
context.mVertexInputDynamicStateSupported =
setContains(deviceExts, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
} else {
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
context.mDebugMarkersSupported = scontext->debugMarkersSupported;
}
// Pass along relevant driver config (feature flags)
context.mAsyncPipelineCachePrewarmingEnabled = driverConfig.vulkanEnableAsyncPipelineCachePrewarming;
context.mParallelShaderCompileDisabled = driverConfig.disableParallelShaderCompile;
context.mStagingBufferBypassEnabled = driverConfig.vulkanEnableStagingBufferBypass;
// We know we need to allocate the protected version of the VK objects
context.mProtectedMemorySupported =
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
// Only enable shaderClipDistance if we are doing instanced stereoscopic rendering.
if (driverConfig.stereoscopicType != StereoscopicType::INSTANCED) {
context.mPhysicalDeviceFeatures.features.shaderClipDistance = VK_FALSE;
}
// Check the availability of lazily allocated memory
context.mLazilyAllocatedMemorySupported = false;
// RenderDoc doesn't support lazy allocated memory
if constexpr (!FVK_RENDERDOC_CAPTURE_MODE) {
for (uint32_t i = 0, typeCount = context.mMemoryProperties.memoryTypeCount; i < typeCount;
++i) {
VkMemoryType const type = context.mMemoryProperties.memoryTypes[i];
if (type.propertyFlags & VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT) {
context.mLazilyAllocatedMemorySupported = true;
assert_invariant(type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
break;
}
}
}
context.mIsUnifiedMemoryArchitecture = hasUnifiedMemoryArchitecture(context.mMemoryProperties);
context.mDepthStencilFormats = findAttachmentDepthStencilFormats(mImpl->mPhysicalDevice);
assert_invariant(context.mDepthStencilFormats.size() > 0);
context.mBlittableDepthStencilFormats =
findBlittableDepthStencilFormats(mImpl->mPhysicalDevice);
context.mFenceExportFlags = getFenceExportFlags();
}
void VulkanPlatform::createLogicalDeviceAndQueues(const ExtensionSet& deviceExtensions,
VkPhysicalDeviceFeatures const& features,
VkPhysicalDeviceVulkan11Features const& vk11Features, bool createProtectedQueue,
MiscDeviceFeatures const& requestedFeatures) noexcept {
// Identify and select all the required queues
mImpl->mGraphicsQueueFamilyIndex =
identifyGraphicsQueueFamilyIndex(mImpl->mPhysicalDevice, VK_QUEUE_GRAPHICS_BIT);
assert_invariant(mImpl->mGraphicsQueueFamilyIndex != INVALID_VK_INDEX);
mImpl->mGraphicsQueueIndex = 0;
if (createProtectedQueue) {
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
mImpl->mProtectedGraphicsQueueIndex = 0;
}
float queuePriority[] = { 1.0f };
VkDeviceCreateInfo deviceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
};
FixedCapacityVector<const char*> requestExtensions;
requestExtensions.reserve(deviceExtensions.size() + 1);
// TODO: We don't really need this if we only ever expect headless swapchains.
requestExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
for (auto const& ext: deviceExtensions) {
requestExtensions.push_back(ext.data());
}
VkDeviceQueueCreateInfo deviceQueueCreateInfo[2] = {};
deviceQueueCreateInfo[0] = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = mImpl->mGraphicsQueueFamilyIndex,
.queueCount = 1,
.pQueuePriorities = &queuePriority[0],
};
// Protected queue
deviceQueueCreateInfo[1] = deviceQueueCreateInfo[0];
deviceQueueCreateInfo[1].flags = VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT;
bool const hasProtectedQueue = mImpl->mProtectedGraphicsQueueFamilyIndex != INVALID_VK_INDEX;
deviceCreateInfo.queueCreateInfoCount = hasProtectedQueue ? 2 : 1;
deviceCreateInfo.pQueueCreateInfos = deviceQueueCreateInfo;
// We could simply enable all supported features, but since that may have performance
// consequences let's just enable the features we need.
VkPhysicalDeviceFeatures enabledFeatures = {
.depthClamp = features.depthClamp,
.samplerAnisotropy = features.samplerAnisotropy,
.textureCompressionETC2 = features.textureCompressionETC2,
.textureCompressionBC = features.textureCompressionBC,
.shaderClipDistance = features.shaderClipDistance,
};
VkPhysicalDeviceFeatures2 enabledFeatures2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.features = enabledFeatures,
};
chainStruct(&deviceCreateInfo, &enabledFeatures2);
VkPhysicalDeviceVulkan11Features enabledVk11Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.multiview = vk11Features.multiview,
#if defined(__ANDROID__)
.samplerYcbcrConversion = vk11Features.samplerYcbcrConversion,
#endif
};
chainStruct(&deviceCreateInfo, &enabledVk11Features);
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamicRendering = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
.dynamicRendering = requestedFeatures.dynamicRendering ? VK_TRUE : VK_FALSE,
};
if (setContains(deviceExtensions, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME)) {
chainStruct(&deviceCreateInfo, &dynamicRendering);
}
VkPhysicalDevicePortabilitySubsetFeaturesKHR portability = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR,
.imageViewFormatSwizzle = VK_TRUE,
.imageView2DOn3DImage = requestedFeatures.imageView2Don3DImage ? VK_TRUE : VK_FALSE,
.mutableComparisonSamplers = VK_TRUE,
};
if (setContains(deviceExtensions, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) {
chainStruct(&deviceCreateInfo, &portability);
}
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
.multiview = vk11Features.multiview,
.multiviewGeometryShader = VK_FALSE,
.multiviewTessellationShader = VK_FALSE,
};
if (setContains(deviceExtensions, VK_KHR_MULTIVIEW_EXTENSION_NAME)) {
chainStruct(&deviceCreateInfo, &multiview);
}
VkPhysicalDeviceProtectedMemoryFeatures protectedMemory = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES,
.protectedMemory = VK_TRUE,
};
if (hasProtectedQueue) {
// Enable protected memory, if requested.
chainStruct(&deviceCreateInfo, &protectedMemory);
}
mImpl->mDevice = createVkDevice(deviceCreateInfo);
}
} // namespace filament::backend
@@ -44,7 +44,7 @@ namespace {
// updating the buffer.
WebGPUBufferObject::WebGPUBufferObject(wgpu::Device const& device,
const BufferObjectBinding bindingType, const uint32_t byteCount)
: HwBufferObject{ byteCount },
: HwBufferObject{ byteCount, false },
WebGPUBufferBase{ device, wgpu::BufferUsage::CopyDst | getBufferObjectUsage(bindingType),
byteCount, "buffer_object" } {}
@@ -73,15 +73,15 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
}
const unsigned int samplerCount =
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
return DescriptorSetLayoutBinding::isSampler(fEntry.type);
std::count_if(layout.descriptors.begin(), layout.descriptors.end(), [](auto& fEntry) {
return DescriptorSetLayoutDescriptor::isSampler(fEntry.type);
});
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
wEntries.reserve(layout.bindings.size() + samplerCount);
wEntries.reserve(layout.descriptors.size() + samplerCount);
mBindGroupEntries.reserve(wEntries.capacity());
for (auto fEntry: layout.bindings) {
for (auto fEntry: layout.descriptors) {
auto& wEntry = wEntries.emplace_back();
auto& entryInfo = mBindGroupEntries.emplace_back();
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
@@ -198,13 +198,13 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
FWGPU_LOGD << "WebGPUDescriptorSetLayout:";
FWGPU_LOGD << " label: " << label;
FWGPU_LOGD << " wgpu::BindGroupLayout handle: " << mLayout.Get();
FWGPU_LOGD << " Filament bindings (" << layout.bindings.size() << "):";
for (DescriptorSetLayoutBinding const& layoutBinding: layout.bindings) {
FWGPU_LOGD << " binding:" << +layoutBinding.binding
<< " type:" << filamentDescriptorTypeToString(layoutBinding.type)
<< " stageFlags:" << filamentStageFlagsToString(layoutBinding.stageFlags)
<< " flags:" << filamentDescriptorFlagsToString(layoutBinding.flags)
<< " count:" << layoutBinding.count;
FWGPU_LOGD << " Filament bindings (" << layout.descriptors.size() << "):";
for (DescriptorSetLayoutDescriptor const& descriptor: layout.descriptors) {
FWGPU_LOGD << " binding:" << +descriptor.binding
<< " type:" << filamentDescriptorTypeToString(descriptor.type)
<< " stageFlags:" << filamentStageFlagsToString(descriptor.stageFlags)
<< " flags:" << filamentDescriptorFlagsToString(descriptor.flags)
<< " count:" << descriptor.count;
}
#endif
}
+130 -1
View File
@@ -103,7 +103,8 @@ Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverCon
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
const Platform::DriverConfig& driverConfig) noexcept
: mPlatform{ platform },
: DriverBase(driverConfig),
mPlatform{ platform },
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueueManager{ mDevice },
@@ -322,8 +323,36 @@ Handle<HwTexture> WebGPUDriver::createTextureS() noexcept {
return allocHandle<WebGPUTexture>();
}
Handle<HwTexture> WebGPUDriver::createTextureAsyncS() noexcept {
return allocHandle<WebGPUTexture>();
}
Handle<HwTexture> WebGPUDriver::importTextureS() noexcept { return allocHandle<WebGPUTexture>(); }
Handle<HwTexture> WebGPUDriver::importTextureAsyncS() noexcept {
return allocHandle<WebGPUTexture>();
}
AsyncCallId WebGPUDriver::update3DImageAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId WebGPUDriver::setVertexBufferObjectAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId WebGPUDriver::updateBufferObjectAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
AsyncCallId WebGPUDriver::updateIndexBufferAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
return allocHandle<WebGPUProgram>();
}
@@ -346,6 +375,10 @@ Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
return allocHandle<WebGPUIndexBuffer>();
}
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferAsyncS() noexcept {
return allocHandle<WebGPUIndexBuffer>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
return allocHandle<WebGPUTexture>();
}
@@ -354,6 +387,10 @@ Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
return allocHandle<WebGPUBufferObject>();
}
Handle<HwBufferObject> WebGPUDriver::createBufferObjectAsyncS() noexcept {
return allocHandle<WebGPUBufferObject>();
}
Handle<HwRenderTarget> WebGPUDriver::createRenderTargetS() noexcept {
return allocHandle<WebGPURenderTarget>();
}
@@ -378,6 +415,10 @@ Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
return allocHandle<WebGPUTexture>();
}
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleAsyncS() noexcept {
return allocHandle<WebGPUTexture>();
}
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
return allocHandle<WebGPURenderTarget>();
}
@@ -398,6 +439,11 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
return allocHandle<WebGPUTexture>();
}
AsyncCallId WebGPUDriver::queueCommandAsyncS() noexcept {
// TODO: implement this.
return InvalidAsyncCallId;
}
// ------------------------------------------------------------------------------------------------
// Asynchronous resource creation methods (R-methods)
//
@@ -475,6 +521,13 @@ void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> indexBufferHandle,
setDebugTag(indexBufferHandle.getId(), std::move(tag));
}
void WebGPUDriver::createIndexBufferAsyncR(Handle<HwIndexBuffer> indexBufferHandle,
const ElementType elementType, const uint32_t indexCount, const BufferUsage usage,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user,
utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> bufferObjectHandle,
const uint32_t byteCount, const BufferObjectBinding bindingType, const BufferUsage usage,
utils::ImmutableCString&& tag) {
@@ -483,6 +536,13 @@ void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> bufferObjectHandle
setDebugTag(bufferObjectHandle.getId(), std::move(tag));
}
void WebGPUDriver::createBufferObjectAsyncR(Handle<HwBufferObject> bufferObjectHandle,
const uint32_t byteCount, const BufferObjectBinding bindingType, const BufferUsage usage,
CallbackHandler* handler, CallbackHandler::Callback callback, void* user,
utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void WebGPUDriver::createTextureR(Handle<HwTexture> textureHandle, const SamplerType target,
const uint8_t levels, const TextureFormat format, const uint8_t samples,
const uint32_t width, const uint32_t height, const uint32_t depth,
@@ -493,6 +553,15 @@ void WebGPUDriver::createTextureR(Handle<HwTexture> textureHandle, const Sampler
setDebugTag(textureHandle.getId(), std::move(tag));
}
void WebGPUDriver::createTextureAsyncR(Handle<HwTexture> textureHandle, const SamplerType target,
const uint8_t levels, const TextureFormat format, const uint8_t samples,
const uint32_t width, const uint32_t height, const uint32_t depth,
const TextureUsage usage, CallbackHandler* handler, CallbackHandler::Callback callback,
void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
FWGPU_SYSTRACE_SCOPE();
}
void WebGPUDriver::createTextureViewR(Handle<HwTexture> textureHandle,
Handle<HwTexture> sourceTextureHandle, const uint8_t baseLevel, const uint8_t levelCount,
utils::ImmutableCString&& tag) {
@@ -530,6 +599,14 @@ void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> textureHandle,
setDebugTag(textureHandle.getId(), std::move(tag));
}
void WebGPUDriver::createTextureViewSwizzleAsyncR(Handle<HwTexture> textureHandle,
Handle<HwTexture> sourceTextureHandle, const backend::TextureSwizzle r,
const backend::TextureSwizzle g, const backend::TextureSwizzle b,
const backend::TextureSwizzle a, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user, utils::ImmutableCString&& tag) {
// TODO: implement this.
}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> textureHandle,
const backend::SamplerType target, const backend::TextureFormat format,
const uint32_t width, const uint32_t height, const backend::TextureUsage usage,
@@ -559,6 +636,15 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> textureHandle, const intptr_
PANIC_POSTCONDITION("Import WebGPU Texture is not supported");
}
void WebGPUDriver::importTextureAsyncR(Handle<HwTexture> textureHandle, const intptr_t id,
const SamplerType target, const uint8_t levels, const TextureFormat format,
const uint8_t samples, const uint32_t width, const uint32_t height, const uint32_t depth,
const TextureUsage usage, CallbackHandler* handler, CallbackHandler::Callback callback,
void* user, utils::ImmutableCString&& tag) {
PANIC_POSTCONDITION("Import WebGPU Texture is not supported");
// TODO: implement this.
}
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> renderPrimitiveHandle,
Handle<HwVertexBuffer> vertexBufferHandle, Handle<HwIndexBuffer> indexBufferHandle,
const PrimitiveType primitiveType, utils::ImmutableCString&& tag) {
@@ -787,6 +873,10 @@ bool WebGPUDriver::isDepthClampSupported() {
return false;
}
bool WebGPUDriver::isAsynchronousModeEnabled() {
return false;
}
bool WebGPUDriver::isWorkaroundNeeded(Workaround) {
return false;
}
@@ -859,6 +949,13 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateIndexBufferAsyncR(AsyncCallId jobId,
Handle<HwIndexBuffer> indexBufferHandle, BufferDescriptor&& bufferDescriptor,
const uint32_t byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
// make sure command elements (draws, etc.) prior to the buffer update are processed before the
@@ -870,6 +967,12 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateBufferObjectAsyncR(AsyncCallId jobId, Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle)
@@ -890,6 +993,13 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vertexBufferHand
vertexBuffer->getBuffers()[index] = bufferObject->getBuffer();
}
void WebGPUDriver::setVertexBufferObjectAsyncR(AsyncCallId jobId,
Handle<HwVertexBuffer> vertexBufferHandle, const uint32_t index,
Handle<HwBufferObject> bufferObjectHandle, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
// Updates a 3D texture region with pixel data from a buffer.
// This function handles various pixel formats and performs a blit operation if the source
// format needs to be converted to the destination format. For compressed textures, it ensures
@@ -1036,6 +1146,15 @@ void WebGPUDriver::update3DImage(Handle<HwTexture> textureHandle, const uint32_t
scheduleDestroy(std::move(pixelBufferDescriptor));
}
void WebGPUDriver::update3DImageAsyncR(AsyncCallId jobId,
Handle<HwTexture> textureHandle, const uint32_t level,
const uint32_t xoffset, const uint32_t yoffset, const uint32_t zoffset,
const uint32_t width, const uint32_t height, const uint32_t depth,
PixelBufferDescriptor&& pixelBufferDescriptor, CallbackHandler* handler,
CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
void WebGPUDriver::setupExternalImage(void* image) {
//todo
}
@@ -2051,4 +2170,14 @@ void WebGPUDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_
// TODO: MetalDriver::copyToMemoryMappedBuffer
}
void WebGPUDriver::queueCommandAsyncR(AsyncCallId jobId, utils::Invocable<void()>&& command,
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
// TODO: implement this.
}
bool WebGPUDriver::cancelAsyncJob(AsyncCallId jobId) {
// TODO: implement this.
return false;
}
} // namespace filament::backend
@@ -28,7 +28,7 @@ namespace filament::backend {
WebGPUIndexBuffer::WebGPUIndexBuffer(wgpu::Device const& device, const uint8_t elementSize,
const uint32_t indexCount)
: HwIndexBuffer{ elementSize, indexCount },
: HwIndexBuffer{ elementSize, indexCount, false },
WebGPUBufferBase{ device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
elementSize * indexCount, "index_buffer" },
mIndexFormat{ elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32 } {}
@@ -198,7 +198,7 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
const TextureFormat format, const uint8_t samples, const uint32_t width,
const uint32_t height, const uint32_t depth, const TextureUsage usage,
wgpu::Device const& device) noexcept
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage, false },
mViewFormat{ toWGPUTextureFormat(format) },
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
samples, levels) },
@@ -268,7 +268,7 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
const uint8_t levelCount) noexcept
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
src->format, src->usage },
src->format, src->usage, static_cast<bool>(src->asynchronous) },
mViewFormat{ src->mViewFormat },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mWebGPUFormat{ src->mWebGPUFormat },
@@ -312,7 +312,7 @@ WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
WebGPUTexture::WebGPUTexture(const WebGPUTexture* src,
const wgpu::TextureComponentSwizzle nextSwizzle) noexcept
: HwTexture{ src->target, src->levels, src->samples, src->width, src->height, src->depth,
src->format, src->usage},
src->format, src->usage, static_cast<bool>(src->asynchronous) },
mViewFormat{ src->mViewFormat },
mMipmapGenerationStrategy{ src->mMipmapGenerationStrategy },
mWebGPUFormat{ src->mWebGPUFormat },
+2 -2
View File
@@ -24,7 +24,7 @@ namespace test {
using namespace filament::backend;
Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup(cleanup) {
utils::FixedCapacityVector<DescriptorSetLayoutBinding> kLayouts(config.uniforms.size());
utils::FixedCapacityVector<DescriptorSetLayoutDescriptor> kLayouts(config.uniforms.size());
for (unsigned char i = 0; i < config.uniforms.size(); ++i) {
kLayouts[i] = {
config.uniforms[i].type.value_or(DescriptorType::UNIFORM_BUFFER),
@@ -59,7 +59,7 @@ Shader::Shader(DriverApi& api, Cleanup& cleanup, ShaderConfig config) : mCleanup
if (!kLayouts.empty()) {
mDescriptorSetLayout = cleanup.add(
api.createDescriptorSetLayout(DescriptorSetLayout{ .bindings = kLayouts }));
api.createDescriptorSetLayout(DescriptorSetLayout{ .descriptors = kLayouts }));
}
}
+13 -14
View File
@@ -52,17 +52,17 @@ layout(push_constant) uniform Constants {
float triangleScale;
float triangleOffsetX;
float triangleOffsetY;
} pushConstants;
} pushConstantsV;
layout(location = 0) in vec4 mesh_position;
void main() {
if (pushConstants.hideTriangle) {
if (pushConstantsV.hideTriangle) {
// Test that bools are written correctly. All bits must be 0 if the bool is false.
gl_Position = vec4(0.0);
return;
}
gl_Position = vec4(mesh_position.xy * pushConstants.triangleScale +
vec2(pushConstants.triangleOffsetX, pushConstants.triangleOffsetY), 0.0, 1.0);
gl_Position = vec4(mesh_position.xy * pushConstantsV.triangleScale +
vec2(pushConstantsV.triangleOffsetX, pushConstantsV.triangleOffsetY), 0.0, 1.0);
#if defined(TARGET_VULKAN_ENVIRONMENT)
// In Vulkan, clip space is Y-down. In OpenGL and Metal, clip space is Y-up.
gl_Position.y = -gl_Position.y;
@@ -76,12 +76,12 @@ layout(push_constant) uniform Constants {
bool padding; // test correct bool padding
float green;
float blue;
} pushConstants;
} pushConstantsF;
precision mediump int; precision highp float;
layout(location = 0) out vec4 fragColor;
void main() {
fragColor = vec4(pushConstants.red, pushConstants.green, pushConstants.blue, 1.0);
fragColor = vec4(pushConstantsF.red, pushConstantsF.green, pushConstantsF.blue, 1.0);
})";
void initPushConstants() {
@@ -91,20 +91,19 @@ void initPushConstants() {
gVertConstants.reserve(4);
gVertConstants.resize(4);
gVertConstants[pushConstantIndex.TRIANGLE_HIDE] = {"hideTriangle", backend::ConstantType::BOOL};
gVertConstants[pushConstantIndex.TRIANGLE_SCALE] = {"triangleScale", backend::ConstantType::FLOAT};
gVertConstants[pushConstantIndex.TRIANGLE_OFFSET_X] = {"triangleOffsetX", backend::ConstantType::FLOAT};
gVertConstants[pushConstantIndex.TRIANGLE_OFFSET_Y] = {"triangleOffsetY", backend::ConstantType::FLOAT};
gVertConstants[pushConstantIndex.TRIANGLE_HIDE] = {"pushConstantsV.hideTriangle", backend::ConstantType::BOOL};
gVertConstants[pushConstantIndex.TRIANGLE_SCALE] = {"pushConstantsV.triangleScale", backend::ConstantType::FLOAT};
gVertConstants[pushConstantIndex.TRIANGLE_OFFSET_X] = {"pushConstantsV.triangleOffsetX", backend::ConstantType::FLOAT};
gVertConstants[pushConstantIndex.TRIANGLE_OFFSET_Y] = {"pushConstantsV.triangleOffsetY", backend::ConstantType::FLOAT};
gFragConstants.reserve(4);
gFragConstants.resize(4);
gFragConstants[pushConstantIndex.RED] = {"red", backend::ConstantType::FLOAT};
gFragConstants[pushConstantIndex.GREEN] = {"green", backend::ConstantType::FLOAT};
gFragConstants[pushConstantIndex.BLUE] = {"blue", backend::ConstantType::FLOAT};
gFragConstants[pushConstantIndex.RED] = {"pushConstantsF.red", backend::ConstantType::FLOAT};
gFragConstants[pushConstantIndex.GREEN] = {"pushConstantsF.green", backend::ConstantType::FLOAT};
gFragConstants[pushConstantIndex.BLUE] = {"pushConstantsF.blue", backend::ConstantType::FLOAT};
}
TEST_F(BackendTest, PushConstants) {
SKIP_IF(Backend::OPENGL, "see b/453757504");
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "see b/453776664");
SKIP_IF(Backend::WEBGPU, "Push constants not supported on WebGPU");
+50 -2
View File
@@ -27,6 +27,7 @@
#include <utils/Invocable.h>
#include <utils/Slice.h>
#include <functional>
#include <initializer_list>
#include <optional>
@@ -193,6 +194,8 @@ public:
using Driver = backend::Driver;
using GpuContextPriority = backend::Platform::GpuContextPriority;
using AsynchronousMode = backend::AsynchronousMode;
using AsyncCompletionCallback = std::function<void(void* UTILS_NULLABLE)>;
using AsyncCallId = backend::AsyncCallId;
/**
* Config is used to define the memory footprint used by the engine, such as the
@@ -432,6 +435,10 @@ public:
/**
* Asynchronous mode for the engine. Defines how asynchronous operations are handled.
* Note that selecting a non-NONE mode does not guarantee asynchronous methods are
* supported, as the underlying backend or the feature flag may override this configuration.
* Always validate availability via Engine::isAsynchronousModeEnabled() before
* invoking asynchronous methods.
*/
AsynchronousMode asynchronousMode = AsynchronousMode::NONE;
};
@@ -449,7 +456,7 @@ public:
char const* UTILS_NONNULL name; //!< name of the feature flag
char const* UTILS_NONNULL description; //!< short description
bool const* UTILS_NONNULL value; //!< pointer to the value of the flag
bool constant; //!< whether the flag is constant after construction
bool constant = true; //!< whether the flag is constant after construction
};
/**
@@ -754,7 +761,7 @@ public:
*
* @return true if the engine supports asynchronous operation.
*/
bool isAsynchronousOperationSupported() const noexcept;
bool isAsynchronousModeEnabled() const noexcept;
/**
* Retrieves the configuration settings of this Engine.
@@ -1023,6 +1030,47 @@ public:
size_t getRenderTargetCount() const noexcept;
/** @} */
/**
* This asynchronously executes user-defined commands. The commands are queued sequentially
* alongside other asynchronous operations (see Texture, VertexBuffer, and IndexBuffer) and
* guaranteed to be executed in the exact order they were invoked.
*
* Beware of overusing this method. It shares the execution queue with other asynchronous tasks
* like texture updates, so flooding it can delay those critical engine tasks. The recommended
* practice is to use this method for resource preparation, such as asset loading(images/meshes).
* This facilitates an efficient chaining pattern, where subsequent asynchronous operations
* (e.g., creating textures/vertex buffers) can be initiated directly within the completion
* callback.
*
* Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the
* asynchronous call.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* @param command The custom command to be executed.
* @param handler The handler from which `onComplete` is invoked. If null, it's called from the
* main thread.
* @param onComplete The callback function that runs once the command has finished.
* @param user The custom data that will be passed as an argument to the `onComplete`.
* @return A unique identifier for the asynchronous call.
*/
AsyncCallId runCommandAsync(utils::Invocable<void()>&& command,
backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback onComplete,
void* UTILS_NULLABLE user = nullptr);
/**
* Cancel the pending asynchronous call pointed to by `id`, which is retrieved whenever you
* invoke a non-blocking version of method on an object, such as `Texture::setImageAsync` or
* `BufferObject::setBufferAsync`.
*
* @param id The unique identifier for the asynchronous call to be canceled.
* @return Returns true upon successful cancellation. It returns false if the asynchronous
* operation cannot be canceled because it is currently running, has finished, or has previously
* been canceled.
*/
bool cancelAsyncCall(AsyncCallId id);
/**
* Kicks the hardware thread (e.g. the OpenGL, Vulkan or Metal thread) and blocks until
* all commands to this point are executed. Note that does guarantee that the
+65 -1
View File
@@ -28,6 +28,7 @@
#include <utils/compiler.h>
#include <utils/StaticString.h>
#include <functional>
#include <stdint.h>
#include <stddef.h>
@@ -51,6 +52,9 @@ class UTILS_PUBLIC IndexBuffer : public FilamentAPI {
public:
using BufferDescriptor = backend::BufferDescriptor;
using AsyncCompletionCallback =
std::function<void(IndexBuffer* UTILS_NONNULL, void* UTILS_NULLABLE)>;
using AsyncCallId = backend::AsyncCallId;
/**
* Type of the index buffer
@@ -111,6 +115,29 @@ public:
*/
Builder& name(utils::StaticString const& name) noexcept;
/**
* Specifies a callback that will execute once the resource's data has been fully allocated
* within the GPU memory. This enables the resource creation process to be handled
* asynchronously.
*
* Any asynchronous calls made during a resource's asynchronous creation (using this method)
* are safe because they are queued and executed in sequence. However, invoking regular
* methods on the same resource before it's fully ready is unsafe and may cause undefined
* behavior. Users can call the `isCreationComplete()` method for the resource to confirm
* when the resource is ready for regular API calls.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
* @return This Builder, for chaining calls.
*/
Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback = nullptr,
void* UTILS_NULLABLE user = nullptr) noexcept;
/**
* Creates the IndexBuffer object and returns a pointer to it. After creation, the index
* buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer.
@@ -131,7 +158,7 @@ public:
};
/**
* Asynchronously copy-initializes a region of this IndexBuffer from the data provided.
* Copy-initializes a region of this IndexBuffer from the data provided.
*
* @param engine Reference to the filament::Engine to associate this IndexBuffer with.
* @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer.
@@ -141,12 +168,49 @@ public:
*/
void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0);
/**
* An asynchronous version of `setBuffer()`.
* Copy-initializes a region of this IndexBuffer from the data provided.
*
* Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the
* asynchronous call.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* @param engine Reference to the filament::Engine to associate this IndexBuffer with.
* @param buffer A BufferDescriptor representing the data used to initialize the IndexBuffer.
* BufferDescriptor points to raw, untyped data that will be interpreted as
* either 16-bit or 32-bits indices based on the Type of this IndexBuffer.
* @param byteOffset Offset in *bytes* into the IndexBuffer. Must be multiple of 4.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
*
* @return An ID that the caller can use to cancel the operation.
*/
AsyncCallId setBufferAsync(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset,
backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback callback,
void* UTILS_NULLABLE user = nullptr);
/**
* Returns the size of this IndexBuffer in elements.
* @return The number of indices the IndexBuffer holds.
*/
size_t getIndexCount() const noexcept;
/**
* This non-blocking method checks if the resource has finished creation. If the resource
* creation was initiated asynchronously, it will return true only after all related
* asynchronous tasks are complete. If the resource was created normally without using async
* method, it will always return true.
*
* @return Whether the resource is created.
*
* @see Builder::async()
*/
bool isCreationComplete() const noexcept;
protected:
// prevent heap allocation
~IndexBuffer() = default;
+3 -3
View File
@@ -489,16 +489,15 @@ struct MultiSampleAntiAliasingOptions {
* @see setTemporalAntiAliasingOptions()
*/
struct TemporalAntiAliasingOptions {
float filterWidth = 1.0f; //!< reconstruction filter width typically between 1 (sharper) and 2 (smoother)
float filterWidth = 1.0f; //!< @deprecated has no effect.
float feedback = 0.12f; //!< history feedback, between 0 (maximum temporal AA) and 1 (no temporal AA).
float lodBias = -1.0f; //!< texturing lod bias (typically -1 or -2)
float sharpness = 0.0f; //!< post-TAA sharpen, especially useful when upscaling is true.
bool enabled = false; //!< enables or disables temporal anti-aliasing
bool upscaling = false; //!< 4x TAA upscaling. Disables Dynamic Resolution. [BETA]
float upscaling = 1.0f; //!< Upscaling factor. Disables Dynamic Resolution. [BETA]
enum class BoxType : uint8_t {
AABB, //!< use an AABB neighborhood
VARIANCE, //!< use the variance of the neighborhood (not recommended)
AABB_VARIANCE //!< use both AABB and variance
};
@@ -519,6 +518,7 @@ struct TemporalAntiAliasingOptions {
bool filterHistory = true; //!< whether to filter the history buffer
bool filterInput = true; //!< whether to apply the reconstruction filter to the input
bool useYCoCg = false; //!< whether to use the YcoCg color-space for history rejection
bool hdr = true; //!< set to true for HDR content
BoxType boxType = BoxType::AABB; //!< type of color gamut box
BoxClipping boxClipping = BoxClipping::ACCURATE; //!< clipping algorithm
JitterPattern jitterPattern = JitterPattern::HALTON_23_X16; //! Jitter Pattern
+122
View File
@@ -26,10 +26,12 @@
#include <backend/Platform.h>
#include <utils/compiler.h>
#include <utils/Invocable.h>
#include <utils/StaticString.h>
#include <utility>
#include <functional>
#include <stddef.h>
#include <stdint.h>
@@ -88,6 +90,9 @@ public:
using Swizzle = backend::TextureSwizzle; //!< Texture swizzle
using ExternalImageHandle = backend::Platform::ExternalImageHandle;
using ExternalImageHandleRef = backend::Platform::ExternalImageHandleRef;
using AsyncCompletionCallback =
std::function<void(Texture* UTILS_NONNULL, void* UTILS_NULLABLE)>;
using AsyncCallId = backend::AsyncCallId;
/** @return Whether a backend supports a particular format. */
static bool isTextureFormatSupported(Engine& engine, InternalFormat format) noexcept;
@@ -261,6 +266,32 @@ public:
*/
Builder& external() noexcept;
/**
* Specifies a callback that will execute once the resource's data has been fully allocated
* within the GPU memory. This enables the resource creation process to be handled
* asynchronously.
*
* Any asynchronous calls made during a resource's asynchronous creation (using this method)
* are safe because they are queued and executed in sequence. However, invoking regular
* methods on the same resource before it's fully ready is unsafe and may cause undefined
* behavior. Users can call the `isCreationComplete()` method for the resource to confirm
* when the resource is ready for regular API calls.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* This method and the `external()` method are mutually exclusive. You cannot use both
* because external texture's contents are filled later by calling `setExternalImage()`.
*
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
* @return This Builder, for chaining calls.
*/
Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback = nullptr,
void* UTILS_NULLABLE user = nullptr) noexcept;
/**
* Creates the Texture object and returns a pointer to it.
*
@@ -437,6 +468,85 @@ public:
void setImage(Engine& engine, size_t level,
PixelBufferDescriptor&& buffer, const FaceOffsets& faceOffsets) const;
/**
* An asynchronous version of `setImage()`.
* Updates a sub-image of a 3D texture or 2D texture array for a level. Cubemaps are treated
* like a 2D array of six layers.
*
* Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the
* asynchronous call.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* @param engine Engine this texture is associated to.
* @param level Level to set the image for.
* @param xoffset Left offset of the sub-region to update.
* @param yoffset Bottom offset of the sub-region to update.
* @param zoffset Depth offset of the sub-region to update.
* @param width Width of the sub-region to update.
* @param height Height of the sub-region to update.
* @param depth Depth of the sub-region to update.
* @param buffer Client-side buffer containing the image to set.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
*
* @return An ID that the caller can use to cancel the operation.
*
* @attention \p engine must be the instance passed to Builder::build()
* @attention \p level must be less than getLevels().
* @attention \p buffer's Texture::Format must match that of getFormat().
* @attention This Texture instance must use Sampler::SAMPLER_3D, Sampler::SAMPLER_2D_ARRAY
* or Sampler::SAMPLER_CUBEMAP.
*
* @see Builder::sampler()
*/
AsyncCallId setImageAsync(Engine& engine, size_t level,
uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& buffer,
backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback,
void* UTILS_NULLABLE user = nullptr) const;
/**
* inline helper to update a 2D texture asynchronously
*
* @see setImageAsync(Engine& engine, size_t level,
* uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
* uint32_t width, uint32_t height, uint32_t depth,
* PixelBufferDescriptor&& buffer,
* backend::CallbackHandler* UTILS_NULLABLE handler,
* AsyncCompletionCallback callback, void* user)
*/
AsyncCallId setImageAsync(Engine& engine, size_t level, PixelBufferDescriptor&& buffer,
backend::CallbackHandler* UTILS_NULLABLE handler, AsyncCompletionCallback callback,
void* UTILS_NULLABLE user = nullptr) const {
return setImageAsync(engine, level, 0, 0, 0,
uint32_t(getWidth(level)), uint32_t(getHeight(level)), 1, std::move(buffer),
handler, std::move(callback), user);
}
/**
* inline helper to update a 2D texture asynchronously
*
* @see setImageAsync(Engine& engine, size_t level,
* uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
* uint32_t width, uint32_t height, uint32_t depth,
* PixelBufferDescriptor&& buffer,
* backend::CallbackHandler* UTILS_NULLABLE handler,
* AsyncCompletionCallback callback, void* user)
*/
AsyncCallId setImageAsync(Engine& engine, size_t level,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
PixelBufferDescriptor&& buffer,
backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback,
void* UTILS_NULLABLE user = nullptr) const {
return setImageAsync(engine, level, xoffset, yoffset, 0, width, height, 1, std::move(buffer),
handler, std::move(callback), user);
}
/**
* Specify the external image to associate with this Texture. Typically, the external
@@ -547,6 +657,18 @@ public:
*/
void generateMipmaps(Engine& engine) const noexcept;
/**
* This non-blocking method checks if the resource has finished creation. If the resource
* creation was initiated asynchronously, it will return true only after all related
* asynchronous tasks are complete. If the resource was created normally without using async
* method, it will always return true.
*
* @return Whether the resource is created.
*
* @see Builder::async()
*/
bool isCreationComplete() const noexcept;
/** @deprecated */
struct FaceOffsets {
using size_type = size_t;
+98 -1
View File
@@ -28,6 +28,7 @@
#include <utils/compiler.h>
#include <utils/StaticString.h>
#include <functional>
#include <stddef.h>
#include <stdint.h>
@@ -61,6 +62,10 @@ class UTILS_PUBLIC VertexBuffer : public FilamentAPI {
public:
using AttributeType = backend::ElementType;
using BufferDescriptor = backend::BufferDescriptor;
using AsyncCompletionCallback =
std::function<void(VertexBuffer* UTILS_NONNULL, void* UTILS_NULLABLE)>;
using AsyncCallId = backend::AsyncCallId;
class Builder : public BuilderBase<BuilderDetails>, public BuilderNameMixin<Builder> {
friend struct BuilderDetails;
@@ -186,6 +191,29 @@ public:
*/
Builder& name(utils::StaticString const& name) noexcept;
/**
* Specifies a callback that will execute once the resource's data has been fully allocated
* within the GPU memory. This enables the resource creation process to be handled
* asynchronously.
*
* Any asynchronous calls made during a resource's asynchronous creation (using this method)
* are safe because they are queued and executed in sequence. However, invoking regular
* methods on the same resource before it's fully ready is unsafe and may cause undefined
* behavior. Users can call the `isCreationComplete()` method for the resource to confirm
* when the resource is ready for regular API calls.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
* @return This Builder, for chaining calls.
*/
Builder& async(backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback = nullptr,
void* UTILS_NULLABLE user = nullptr) noexcept;
/**
* Creates the VertexBuffer object and returns a pointer to it.
*
@@ -210,7 +238,7 @@ public:
size_t getVertexCount() const noexcept;
/**
* Asynchronously copy-initializes the specified buffer from the given buffer data.
* copy-initializes the specified buffer from the given buffer data.
*
* Do not use this if you called enableBufferObjects() on the Builder.
*
@@ -226,6 +254,36 @@ public:
void setBufferAt(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer,
uint32_t byteOffset = 0);
/**
* An asynchronous version of `setBufferAt()`.
* Asynchronously copy-initializes the specified buffer from the given buffer data.
*
* Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the
* asynchronous call.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* Do not use this if you called enableBufferObjects() on the Builder.
*
* @param engine Reference to the filament::Engine to associate this VertexBuffer with.
* @param bufferIndex Index of the buffer to initialize. Must be between 0
* and Builder::bufferCount() - 1.
* @param buffer A BufferDescriptor representing the data used to initialize the buffer at
* index \p bufferIndex. BufferDescriptor points to raw, untyped data that will
* be copied as-is into the buffer.
* @param byteOffset Offset in *bytes* into the buffer at index \p bufferIndex of this vertex
* buffer set. Must be multiple of 4.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
*
* @return An ID that the caller can use to cancel the operation.
*/
AsyncCallId setBufferAtAsync(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer,
uint32_t byteOffset, backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback, void* UTILS_NULLABLE user = nullptr);
/**
* Swaps in the given buffer object.
*
@@ -239,6 +297,45 @@ public:
void setBufferObjectAt(Engine& engine, uint8_t bufferIndex,
BufferObject const* UTILS_NONNULL bufferObject);
/**
* An asynchronous version of `setBufferObjectAt()`.
* Swaps in the given buffer object.
*
* Users can call the `Engine::cancelAsyncCall()` method with the returned ID to cancel the
* asynchronous call.
*
* To use this method, the engine must be configured for asynchronous operation. Otherwise,
* calling async method will cause the program to terminate.
*
* To use this, you must first call enableBufferObjects() on the Builder.
*
* @param engine Reference to the filament::Engine to associate this VertexBuffer with.
* @param bufferIndex Index of the buffer to initialize. Must be between 0
* and Builder::bufferCount() - 1.
* @param bufferObject The handle to the GPU data that will be used in this buffer slot.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback A function to be called upon the completion of an asynchronous creation.
* @param user The custom data that will be passed as the second argument to the `callback`.
*
* @return An ID that the caller can use to cancel the operation.
*/
AsyncCallId setBufferObjectAtAsync(Engine& engine, uint8_t bufferIndex,
BufferObject const* UTILS_NONNULL bufferObject,
backend::CallbackHandler* UTILS_NULLABLE handler,
AsyncCompletionCallback callback, void* UTILS_NULLABLE user = nullptr);
/**
* This non-blocking method checks if the resource has finished creation. If the resource
* creation was initiated asynchronously, it will return true only after all related
* asynchronous tasks are complete. If the resource was created normally without using async
* method, it will always return true.
*
* @return Whether the resource is created.
*
* @see Builder::async()
*/
bool isCreationComplete() const noexcept;
protected:
// prevent heap allocation
~VertexBuffer() = default;
+12 -3
View File
@@ -16,7 +16,7 @@
#include "details/Engine.h"
#include "ResourceAllocator.h"
#include "TextureCache.h"
#include "details/BufferObject.h"
#include "details/Camera.h"
@@ -349,6 +349,15 @@ size_t Engine::getRenderTargetCount() const noexcept {
return downcast(this)->getRenderTargetCount();
}
AsyncCallId Engine::runCommandAsync(Invocable<void()>&& command, CallbackHandler* handler,
AsyncCompletionCallback onComplete, void* user) {
return downcast(this)->runCommandAsync(std::move(command), handler, std::move(onComplete),
user);
}
bool Engine::cancelAsyncCall(AsyncCallId const id) {
return downcast(this)->cancelAsyncCall(id);
}
void Engine::flushAndWait() {
downcast(this)->flushAndWait();
@@ -455,8 +464,8 @@ bool Engine::isStereoSupported(StereoscopicType) const noexcept {
return downcast(this)->isStereoSupported();
}
bool Engine::isAsynchronousOperationSupported() const noexcept {
return downcast(this)->isAsynchronousOperationSupported();
bool Engine::isAsynchronousModeEnabled() const noexcept {
return downcast(this)->isAsynchronousModeEnabled();
}
size_t Engine::getMaxStereoscopicEyes() noexcept {
@@ -40,17 +40,17 @@ using namespace backend;
size_t HwDescriptorSetLayoutFactory::Parameters::hash() const noexcept {
return hash::murmurSlow(
reinterpret_cast<uint8_t const *>(dsl.bindings.data()),
dsl.bindings.size() * sizeof(DescriptorSetLayoutBinding),
reinterpret_cast<uint8_t const *>(dsl.descriptors.data()),
dsl.descriptors.size() * sizeof(DescriptorSetLayoutDescriptor),
42);
}
bool operator==(HwDescriptorSetLayoutFactory::Parameters const& lhs,
HwDescriptorSetLayoutFactory::Parameters const& rhs) noexcept {
return (lhs.dsl.bindings.size() == rhs.dsl.bindings.size()) &&
return (lhs.dsl.descriptors.size() == rhs.dsl.descriptors.size()) &&
std::equal(
lhs.dsl.bindings.begin(), lhs.dsl.bindings.end(),
rhs.dsl.bindings.begin());
lhs.dsl.descriptors.begin(), lhs.dsl.descriptors.end(),
rhs.dsl.descriptors.begin());
}
// ------------------------------------------------------------------------------------------------
@@ -70,7 +70,7 @@ void HwDescriptorSetLayoutFactory::terminate(DriverApi&) noexcept {
auto HwDescriptorSetLayoutFactory::create(DriverApi& driver,
DescriptorSetLayout dsl) noexcept -> Handle {
std::sort(dsl.bindings.begin(), dsl.bindings.end(),
std::sort(dsl.descriptors.begin(), dsl.descriptors.end(),
[](auto&& lhs, auto&& rhs) {
return lhs.binding < rhs.binding;
});
+11
View File
@@ -25,8 +25,19 @@ void IndexBuffer::setBuffer(Engine& engine,
downcast(this)->setBuffer(downcast(engine), std::move(buffer), byteOffset);
}
backend::AsyncCallId IndexBuffer::setBufferAsync(Engine& engine, BufferDescriptor&& buffer,
uint32_t byteOffset, backend::CallbackHandler* handler,
AsyncCompletionCallback callback, void* user) {
return downcast(this)->setBufferAsync(downcast(engine), std::move(buffer), byteOffset,
handler, std::move(callback), user);
}
size_t IndexBuffer::getIndexCount() const noexcept {
return downcast(this)->getIndexCount();
}
bool IndexBuffer::isCreationComplete() const noexcept {
return downcast(this)->isCreationComplete();
}
} // namespace filament
+13 -14
View File
@@ -467,8 +467,7 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
success = mMaterialParser->getDescriptorBindings(&programDescriptorBindings);
assert_invariant(success);
backend::DescriptorSetLayout descriptorSetLayout;
success = mMaterialParser->getDescriptorSetLayout(&descriptorSetLayout);
success = mMaterialParser->getDescriptorSetLayout(&this->descriptorSetLayoutDescription);
assert_invariant(success);
// get the PER_VIEW descriptor binding info
@@ -477,26 +476,26 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
refractionMode == RefractionMode::SCREEN_SPACE;
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
auto perViewDescriptorSetLayout = descriptor_sets::getPerViewDescriptorSetLayout(
this->perViewDescriptorSetLayoutDescription = descriptor_sets::getPerViewDescriptorSetLayout(
materialDomain, isLit, isSSR, hasFog, false);
auto perViewDescriptorSetLayoutVsm = descriptor_sets::getPerViewDescriptorSetLayout(
this->perViewDescriptorSetLayoutVsmDescription = descriptor_sets::getPerViewDescriptorSetLayout(
materialDomain, isLit, isSSR, hasFog, true);
// set the labels
descriptorSetLayout.label = CString{ name }.append("_perMat");
perViewDescriptorSetLayout.label = CString{ name }.append("_perView");
perViewDescriptorSetLayoutVsm.label = CString{ name }.append("_perViewVsm");
this->descriptorSetLayoutDescription.label = CString{ name }.append("_perMat");
this->perViewDescriptorSetLayoutDescription.label = CString{ name }.append("_perView");
this->perViewDescriptorSetLayoutVsmDescription.label = CString{ name }.append("_perViewVsm");
// get the PER_RENDERABLE and PER_VIEW descriptor binding info
for (auto&& [bindingPoint, descriptorSetLayout] : {
for (auto&& [bindingPoint, dsl] : {
std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE,
descriptor_sets::getPerRenderableLayout() },
std::pair{ DescriptorSetBindingPoints::PER_VIEW,
perViewDescriptorSetLayout }}) {
this->perViewDescriptorSetLayoutDescription }}) {
Program::DescriptorBindingsInfo& descriptors = programDescriptorBindings[+bindingPoint];
descriptors.reserve(descriptorSetLayout.bindings.size());
for (auto const& entry: descriptorSetLayout.bindings) {
descriptors.reserve(dsl.descriptors.size());
for (auto const& entry: dsl.descriptors) {
auto const& name = descriptor_sets::getDescriptorName(bindingPoint, entry.binding);
descriptors.push_back({ name, entry.type, entry.binding });
}
@@ -504,15 +503,15 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
this->descriptorSetLayout = {
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), std::move(descriptorSetLayout) };
engine.getDriverApi(), this->descriptorSetLayoutDescription };
this->perViewDescriptorSetLayout = {
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), std::move(perViewDescriptorSetLayout) };
engine.getDriverApi(), this->perViewDescriptorSetLayoutDescription };
this->perViewDescriptorSetLayoutVsm = {
engine.getDescriptorSetLayoutFactory(),
engine.getDriverApi(), std::move(perViewDescriptorSetLayoutVsm) };
engine.getDriverApi(), this->perViewDescriptorSetLayoutVsmDescription };
}
} // namespace filament
+10 -3
View File
@@ -24,6 +24,7 @@
#include <ds/DescriptorSetLayout.h>
#include <backend/DriverEnums.h>
#include <backend/Program.h>
namespace filament {
@@ -56,10 +57,16 @@ struct MaterialDefinition {
MaterialParser const& getMaterialParser() const noexcept { return *mMaterialParser; }
// Keep track of the definitions of the descriptor set layouts, as these
// may be used by some backends in parallel compilation of programs.
backend::DescriptorSetLayout perViewDescriptorSetLayoutDescription;
backend::DescriptorSetLayout perViewDescriptorSetLayoutVsmDescription;
backend::DescriptorSetLayout descriptorSetLayoutDescription;
// try to order by frequency of use
DescriptorSetLayout perViewDescriptorSetLayout;
DescriptorSetLayout perViewDescriptorSetLayoutVsm;
DescriptorSetLayout descriptorSetLayout;
filament::DescriptorSetLayout perViewDescriptorSetLayout;
filament::DescriptorSetLayout perViewDescriptorSetLayoutVsm;
filament::DescriptorSetLayout descriptorSetLayout;
backend::Program::DescriptorSetInfo programDescriptorBindings;
backend::RasterState rasterState;
+1 -1
View File
@@ -792,7 +792,7 @@ bool ChunkDescriptorSetLayoutInfo::unflatten(Unflattener& unflattener,
if (!unflattener.read(&descriptorCount)) {
return false;
}
auto& descriptors = container->bindings;
auto& descriptors = container->descriptors;
descriptors.reserve(descriptorCount);
for (size_t i = 0; i < descriptorCount; i++) {
uint8_t type;
+52 -67
View File
@@ -599,7 +599,8 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
passBuilder.variant(structureVariant);
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SSAO);
RenderPass const pass{ passBuilder.build(mEngine, driver) };
RenderPass pass{ passBuilder.build(mEngine) };
pass.finalize(mEngine, driver);
auto const out = resources.getRenderPassInfo();
driver.beginRenderPass(out.target, out.params);
pass.getExecutor().execute(mEngine, driver);
@@ -710,7 +711,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::transparentPicking(FrameGrap
passBuilder.variant(pickingVariant);
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::DEPTH);
RenderPass const pass{ passBuilder.build(mEngine, driver) };
RenderPass pass{ passBuilder.build(mEngine) };
pass.finalize(mEngine, driver);
driver.beginRenderPass(target, params);
pass.getExecutor().execute(mEngine, driver);
driver.endRenderPass();
@@ -808,7 +810,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
// generate all our drawing commands, except blended objects.
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
RenderPass const pass{ passBuilder.build(mEngine, driver) };
RenderPass pass{ passBuilder.build(mEngine) };
pass.finalize(mEngine, driver);
driver.beginRenderPass(out.target, out.params);
pass.getExecutor().execute(mEngine, driver);
driver.endRenderPass();
@@ -2530,11 +2533,12 @@ static float4 getVignetteParameters(VignetteOptions const& options,
}
void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
const FColorGrading* colorGrading, ColorGradingConfig const& colorGradingConfig,
VignetteOptions const& vignetteOptions, uint32_t const width, uint32_t const height) noexcept {
FColorGrading const* colorGrading,
ColorGradingConfig const& colorGradingConfig,
VignetteOptions const& vignetteOptions,
uint32_t const width, uint32_t const height) noexcept {
auto& material = getPostProcessMaterial("colorGradingAsSubpass");
FMaterialInstance* const mi =
FMaterialInstance const* const mi =
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
vignetteOptions, width, height);
mi->commit(driver, getUboManager());
@@ -2566,12 +2570,11 @@ void PostProcessManager::colorGradingSubpass(DriverApi& driver,
void PostProcessManager::customResolvePrepareSubpass(DriverApi& driver, CustomResolveOp const op) noexcept {
auto const& material = getPostProcessMaterial("customResolveAsSubpass");
auto ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
auto const ma = material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.customResolve = fixedIndex;
mi->setParameter("direction", op == CustomResolveOp::COMPRESS ? 1.0f : -1.0f),
mi->commit(driver, getUboManager());
material.getMaterial(mEngine);
}
void PostProcessManager::customResolveSubpass(DriverApi& driver) noexcept {
@@ -2624,7 +2627,6 @@ void PostProcessManager::clearAncillaryBuffersPrepare(DriverApi& driver) noexcep
auto [mi, fixedIndex] = mMaterialInstanceManager.getFixedMaterialInstance(ma);
mFixedMaterialInstanceIndex.clearDepth = fixedIndex;
mi->commit(driver, getUboManager());
material.getMaterial(mEngine);
}
void PostProcessManager::clearAncillaryBuffers(DriverApi& driver,
@@ -2909,11 +2911,12 @@ void PostProcessManager::configureTemporalAntiAliasingMaterial(
FMaterial* const ma = getPostProcessMaterial("taa").getMaterial(mEngine);
bool dirty = false;
setConstantParameter(ma, "upscaling", taaOptions.upscaling, dirty);
setConstantParameter(ma, "upscaling", taaOptions.upscaling > 1.0f, dirty);
setConstantParameter(ma, "historyReprojection", taaOptions.historyReprojection, dirty);
setConstantParameter(ma, "filterHistory", taaOptions.filterHistory, dirty);
setConstantParameter(ma, "filterInput", taaOptions.filterInput, dirty);
setConstantParameter(ma, "useYCoCg", taaOptions.useYCoCg, dirty);
setConstantParameter(ma, "hdr", taaOptions.hdr, dirty);
setConstantParameter(ma, "preventFlickering", taaOptions.preventFlickering, dirty);
setConstantParameter(ma, "boxType", int32_t(taaOptions.boxType), dirty);
setConstantParameter(ma, "boxClipping", int32_t(taaOptions.boxClipping), dirty);
@@ -2982,6 +2985,8 @@ FMaterialInstance* PostProcessManager::configureColorGradingMaterial(
FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input,
FrameGraphId<FrameGraphTexture> const depth,
filament::Viewport const& xvp,
filament::Viewport const& vp,
FrameHistory& frameHistory,
FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa,
TemporalAntiAliasingOptions const& taaOptions,
@@ -3011,9 +3016,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
auto const& taaPass = fg.addPass<TAAData>("TAA",
[&](FrameGraph::Builder& builder, auto& data) {
auto desc = fg.getDescriptor(input);
if (taaOptions.upscaling) {
desc.width *= 2;
desc.height *= 2;
if (taaOptions.upscaling > 1.0f) {
desc.width *= taaOptions.upscaling;
desc.height *= taaOptions.upscaling;
}
data.color = builder.sample(input);
data.depth = builder.sample(depth);
@@ -3045,55 +3050,15 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
0, 0, 0, 1
}};
constexpr float2 sampleOffsets[9] = {
{ -1.0f, -1.0f }, { 0.0f, -1.0f }, { 1.0f, -1.0f }, { -1.0f, 0.0f },
{ 0.0f, 0.0f },
{ 1.0f, 0.0f }, { -1.0f, 1.0f }, { 0.0f, 1.0f }, { 1.0f, 1.0f },
};
constexpr float2 subSampleOffsets[4] = {
{ -0.25f, 0.25f },
{ 0.25f, 0.25f },
{ 0.25f, -0.25f },
{ -0.25f, -0.25f }
};
UTILS_UNUSED
auto const lanczos = [](float const x, float const a) -> float {
if (x <= std::numeric_limits<float>::epsilon()) {
return 1.0f;
}
if (std::abs(x) <= a) {
return (a * std::sin(f::PI * x) * std::sin(f::PI * x / a))
/ ((f::PI * f::PI) * (x * x));
}
return 0.0f;
};
float const filterWidth = std::clamp(taaOptions.filterWidth, 1.0f, 2.0f);
float4 sum = 0.0;
float4 weights[9];
// this doesn't get vectorized (probably because of exp()), so don't bother
// unrolling it.
UTILS_NOUNROLL
for (size_t i = 0; i < 9; i++) {
float2 const o = sampleOffsets[i];
for (size_t j = 0; j < 4; j++) {
float2 const subPixelOffset = taaOptions.upscaling ? subSampleOffsets[j] : float2{ 0 };
float2 const d = (o - (current.jitter - subPixelOffset)) / filterWidth;
weights[i][j] = lanczos(length(d), filterWidth);
}
sum += weights[i];
}
for (auto& w : weights) {
w /= sum;
}
auto out = resources.getRenderPassInfo();
auto color = resources.getTexture(data.color);
auto depth = resources.getTexture(data.depth);
auto history = resources.getTexture(data.history);
auto const color = resources.getTexture(data.color);
auto const depth = resources.getTexture(data.depth);
auto const history = resources.getTexture(data.history);
auto const& colorDesc = resources.getDescriptor(data.color);
auto const& historyDesc = resources.getDescriptor(data.history);
auto const& material = getPostProcessMaterial("taa");
PostProcessVariant const variant = colorGradingConfig.translucent ?
@@ -3104,17 +3069,37 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
FMaterialInstance* mi = getMaterialInstance(ma);
mi->setParameter("color", color, SamplerParams{}); // nearest
mi->setParameter("depth", depth, SamplerParams{}); // nearest
mi->setParameter("alpha", taaOptions.feedback);
mi->setParameter("history", history, SamplerParams{
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR
});
mi->setParameter("filterWeights", weights, 9);
mi->setParameter("jitter", current.jitter);
mi->setParameter("alpha", taaOptions.feedback);
// jitter is negated here because the material wants the sample jitter offset
// not the projection matrix offset
mi->setParameter("jitter", -current.jitter);
mi->setParameter("scale", taaOptions.upscaling);
mi->setParameter("reprojection",
mat4f{ historyProjection * inverse(current.projection) } *
normalizedToClip);
mi->setParameter("colorViewport",
float4{
float(xvp.left) / colorDesc.width,
float(xvp.bottom) / colorDesc.height,
float(xvp.width) / colorDesc.width,
float(xvp.height) / colorDesc.height,
});
mi->setParameter("colorResolution",
float4{ colorDesc.width, colorDesc.height,
1.0f / colorDesc.width, 1.0f / colorDesc.height });
mi->setParameter("historyResolution",
float4{ historyDesc.width, historyDesc.height,
1.0f / historyDesc.width, 1.0f / historyDesc.height });
mi->commit(driver, getUboManager());
mi->use(driver);
@@ -3152,7 +3137,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
// an "import".
builder.sideEffect();
data.color = builder.sample(history); // FIXME: an access must be declared for detach(), why?
}, [&current](FrameGraphResources const& resources, auto const& data, auto&) {
}, [&current](FrameGraphResources const& resources, auto const& data) {
resources.detach(data.color, &current.color, &current.desc);
});
@@ -3803,7 +3788,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::vsmMipmapPass(FrameGraph& fg
auto const& depthMipmapPass = fg.addPass<VsmMipData>("VSM Generate Mipmap Pass",
[&](FrameGraph::Builder& builder, auto& data) {
utils::StaticString name = builder.getName(input);
StaticString const name = builder.getName(input);
data.in = builder.sample(input);
auto out = builder.createSubresource(data.in, "Mip level", {
+2
View File
@@ -235,6 +235,8 @@ public:
FrameGraphId<FrameGraphTexture> taa(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> input,
FrameGraphId<FrameGraphTexture> depth,
Viewport const& xvp,
Viewport const& vp,
FrameHistory& frameHistory,
FrameHistoryEntry::TemporalAA FrameHistoryEntry::*pTaa,
TemporalAntiAliasingOptions const& taaOptions,
+111 -135
View File
@@ -16,9 +16,9 @@
#include "RenderPass.h"
#include "compact.h"
#include "RenderPrimitive.h"
#include "ShadowMap.h"
#include "SharedHandle.h"
#include "details/Material.h"
#include "details/MaterialInstance.h"
@@ -32,6 +32,7 @@
#include <filament/MaterialEnums.h>
#include <backend/BufferDescriptor.h>
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
@@ -43,6 +44,7 @@
#include <private/utils/Tracing.h>
#include <utils/JobSystem.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/Range.h>
#include <utils/Slice.h>
@@ -51,8 +53,10 @@
#include <algorithm>
#include <functional>
#include <iterator>
#include <limits>
#include <utility>
#include <vector>
#include <stddef.h>
#include <stdint.h>
@@ -80,21 +84,20 @@ RenderPassBuilder& RenderPassBuilder::customCommand(
return *this;
}
RenderPass RenderPassBuilder::build(FEngine const& engine, DriverApi& driver) const {
RenderPass RenderPassBuilder::build(FEngine const& engine) const {
assert_invariant(mRenderableSoa);
return RenderPass{ engine, driver, *this };
return RenderPass{ engine, *this };
}
// ------------------------------------------------------------------------------------------------
void RenderPass::BufferObjectHandleDeleter::operator()(BufferObjectHandle handle) noexcept {
void RenderPass::BufferObjectHandleDeleter::operator()(BufferObjectHandle handle) const noexcept {
if (handle) { // this is common case
driver.get().destroyBufferObject(handle);
}
}
void RenderPass::DescriptorSetHandleDeleter::operator()(
DescriptorSetHandle handle) noexcept {
void RenderPass::DescriptorSetHandleDeleter::operator()(DescriptorSetHandle handle) const noexcept {
if (handle) { // this is common case
driver.get().destroyDescriptorSet(handle);
}
@@ -102,7 +105,7 @@ void RenderPass::DescriptorSetHandleDeleter::operator()(
// ------------------------------------------------------------------------------------------------
RenderPass::RenderPass(FEngine const& engine, DriverApi& driver,
RenderPass::RenderPass(FEngine const& engine,
RenderPassBuilder const& builder) noexcept
: mRenderableSoa(*builder.mRenderableSoa),
mColorPassDescriptorSet(builder.mColorPassDescriptorSet) {
@@ -163,9 +166,7 @@ RenderPass::RenderPass(FEngine const& engine, DriverApi& driver,
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
}
commandEnd = resize(builder.mArena,
instanceify(driver,
engine.getPerRenderableDescriptorSetLayout().getHandle(),
commandBegin, commandEnd, stereoscopicEyeCount));
instanceify(commandBegin, commandEnd, stereoscopicEyeCount));
}
// these are `const` from this point on...
@@ -250,7 +251,7 @@ void RenderPass::appendCommands(FEngine const& engine,
void RenderPass::appendCustomCommand(Command* commands,
uint8_t channel, Pass pass, CustomCommand custom, uint32_t const order,
Executor::CustomCommandFn command) {
Executor::CustomCommandFn command) const {
assert_invariant((uint64_t(order) << CUSTOM_ORDER_SHIFT) <= CUSTOM_ORDER_MASK);
@@ -283,8 +284,7 @@ RenderPass::Command* RenderPass::sortCommands(
return last;
}
RenderPass::Command* RenderPass::instanceify(DriverApi& driver,
DescriptorSetLayoutHandle perRenderableDescriptorSetLayoutHandle,
RenderPass::Command* RenderPass::instanceify(
Command* curr, Command* const last,
int32_t const eyeCount) const noexcept {
FILAMENT_TRACING_NAME(FILAMENT_TRACING_CATEGORY_FILAMENT, "instanceify");
@@ -298,138 +298,112 @@ RenderPass::Command* RenderPass::instanceify(DriverApi& driver,
// hash of those parameters.
UTILS_UNUSED uint32_t drawCallsSavedCount = 0;
Command* firstSentinel = nullptr;
PerRenderableData const* uboData = nullptr;
PerRenderableData* stagingBuffer = nullptr;
uint32_t stagingBufferSize = 0;
uint32_t instancedPrimitiveOffset = 0;
size_t const count = last - curr;
size_t const count = std::distance(curr, last);
// TODO: for the case of instancing we could actually use 128 instead of 64 instances
constexpr size_t maxInstanceCount = CONFIG_MAX_INSTANCES;
auto equivalent = [](Command const& lhs, Command const& rhs) {
// This predicate must also filter out commands that are not eligible for auto-instancing.
if (UTILS_UNLIKELY(lhs.info.hasSkinning || lhs.info.hasMorphing || lhs.info.instanceCount > 1)) {
return false;
}
if (UTILS_UNLIKELY(rhs.info.hasSkinning || rhs.info.hasMorphing || rhs.info.instanceCount > 1)) {
return false;
}
// Now check for equality
return lhs.info.mi == rhs.info.mi &&
lhs.info.rph == rhs.info.rph &&
lhs.info.vbih == rhs.info.vbih &&
lhs.info.indexOffset == rhs.info.indexOffset &&
lhs.info.indexCount == rhs.info.indexCount &&
lhs.info.rasterState == rhs.info.rasterState;
};
while (curr != last) {
// Currently, if we have skinning or morphing, we can't use auto instancing. This is
// because the morphing/skinning data for comparison is not easily accessible; and also
// because we're assuming that the per-renderable descriptor-set only has the
// OBJECT_UNIFORMS descriptor active (i.e. the skinning/morphing descriptors are unused).
// We also can't use auto-instancing if manual- or hybrid- instancing is used.
// TODO: support auto-instancing for skinning/morphing
Command const* e = curr + 1;
if (UTILS_LIKELY(
!curr->info.hasSkinning && !curr->info.hasMorphing &&
curr->info.instanceCount <= 1))
{
assert_invariant(!curr->info.hasHybridInstancing);
// we can't have nice things! No more than maxInstanceCount due to UBO size limits
e = std::find_if_not(curr, std::min(last, curr + maxInstanceCount),
[lhs = *curr](Command const& rhs) {
// primitives must be identical to be instanced
// Currently, instancing doesn't support skinning/morphing.
return lhs.info.mi == rhs.info.mi &&
lhs.info.rph == rhs.info.rph &&
lhs.info.vbih == rhs.info.vbih &&
lhs.info.indexOffset == rhs.info.indexOffset &&
lhs.info.indexCount == rhs.info.indexCount &&
lhs.info.rasterState == rhs.info.rasterState;
});
auto processor = [this, eyeCount, count,
&uboData, &instancedPrimitiveOffset, &drawCallsSavedCount,
&stagingBuffer = mInstancingStagingBuffer,
&descriptorSetPatch = mInstancingDescriptorSetPatch]
(Command* const dst, Command* const begin, Command* const end) mutable {
uint32_t const instanceCount = std::distance(begin, end);
drawCallsSavedCount += instanceCount - 1;
if (UTILS_UNLIKELY(stagingBuffer.empty())) {
stagingBuffer.resize(count);
descriptorSetPatch.reserve(count);
uboData = mRenderableSoa.data<FScene::UBO>();
assert_invariant(uboData);
}
uint32_t const instanceCount = e - curr;
assert_invariant(instanceCount > 0);
assert_invariant(instanceCount <= CONFIG_MAX_INSTANCES);
if (UTILS_UNLIKELY(instanceCount > 1)) {
drawCallsSavedCount += instanceCount - 1;
// allocate our staging buffer only if needed
if (UTILS_UNLIKELY(!stagingBuffer)) {
// Create a temporary UBO for holding the per-renderable data of each primitive,
// The `curr->info.index` is updated so that this (now instanced) command can
// bind the UBO in the right place (where the per-instance data is).
// The lifetime of this object is the longest of this RenderPass and all its
// executors.
// create a temporary UBO for instancing
mInstancedUboHandle = BufferObjectSharedHandle{
driver.createBufferObject(
count * sizeof(PerRenderableData) + sizeof(PerRenderableUib),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC), driver };
// TODO: use stream inline buffer for small sizes
// TODO: use a pool for larger heap buffers
// buffer large enough for all instances data
stagingBufferSize = count * sizeof(PerRenderableData);
stagingBuffer = static_cast<PerRenderableData*>(malloc(stagingBufferSize));
uboData = mRenderableSoa.data<FScene::UBO>();
assert_invariant(uboData);
// We also need a descriptor-set to hold the custom UBO. This works because
// we currently assume the descriptor-set only needs to hold this UBO in the
// instancing case (it wouldn't be true if we supported skinning/morphing, and
// in this case we would need to preserve the default descriptor-set content).
// This has the same lifetime as the UBO (see above).
mInstancedDescriptorSetHandle = DescriptorSetSharedHandle{
driver.createDescriptorSet(perRenderableDescriptorSetLayoutHandle),
driver
};
driver.updateDescriptorSetBuffer(mInstancedDescriptorSetHandle,
+PerRenderableBindingPoints::OBJECT_UNIFORMS,
mInstancedUboHandle, 0, sizeof(PerRenderableUib));
}
// copy the ubo data to a staging buffer
assert_invariant(instancedPrimitiveOffset + instanceCount
<= stagingBufferSize / sizeof(PerRenderableData));
for (uint32_t i = 0; i < instanceCount; i++) {
stagingBuffer[instancedPrimitiveOffset + i] = uboData[curr[i].info.index];
}
// make the first command instanced
curr[0].info.instanceCount = instanceCount * eyeCount;
curr[0].info.index = instancedPrimitiveOffset;
curr[0].info.dsh = mInstancedDescriptorSetHandle;
instancedPrimitiveOffset += instanceCount;
// cancel commands that are now instances
firstSentinel = !firstSentinel ? curr : firstSentinel;
for (uint32_t i = 1; i < instanceCount; i++) {
curr[i].key = uint64_t(Pass::SENTINEL);
}
assert_invariant(instancedPrimitiveOffset + instanceCount <= stagingBuffer.size());
for (uint32_t i = 0; i < instanceCount; i++) {
stagingBuffer[instancedPrimitiveOffset + i] = uboData[begin[i].info.index];
}
curr = const_cast<Command*>(e);
}
// Modify the command at its final destination
dst->info.instanceCount = instanceCount * eyeCount;
dst->info.index = instancedPrimitiveOffset;
dst->info.dsh = {}; // this will be filled up in finalize
descriptorSetPatch.push_back(dst); // Store the stable iterator
if (UTILS_UNLIKELY(firstSentinel)) {
// DLOG(INFO) << "auto-instancing, saving " << drawCallsSavedCount << " draw calls, out of "
// << count;
// we have instanced primitives
// copy our instanced ubo data
driver.updateBufferObjectUnsynchronized(mInstancedUboHandle, {
stagingBuffer, sizeof(PerRenderableData) * instancedPrimitiveOffset,
+[](void* buffer, size_t, void*) {
free(buffer);
}
}, 0);
instancedPrimitiveOffset += instanceCount;
};
stagingBuffer = nullptr;
// remove all the canceled commands
auto const lastCommand = std::remove_if(firstSentinel, last, [](auto const& command) {
return command.key == uint64_t(Pass::SENTINEL);
});
return lastCommand;
}
assert_invariant(stagingBuffer == nullptr);
return last;
auto const end = compact<CONFIG_MAX_INSTANCES>(curr, last, equivalent, processor);
// if (!mInstancingStagingBuffer.empty()) {
// slog.d << "auto-instancing, saving " << drawCallsSavedCount << " draw calls, out of "
// << count << io::endl;
// }
return end;
}
void RenderPass::finalize(FEngine const& engine, DriverApi& driver) {
mFinalized = true;
if (UTILS_UNLIKELY(!mInstancingStagingBuffer.empty())) {
auto const* p = mInstancingStagingBuffer.data();
size_t const size = sizeof(PerRenderableData) * mInstancingStagingBuffer.size();
// Create a temporary UBO for holding the per-renderable data of each primitive,
// The `curr->info.index` is updated so that this (now instanced) command can
// bind the UBO in the right place (where the per-instance data is).
// The lifetime of this object is the longest of this RenderPass and all its
// executors.
// create a temporary UBO for instancing
mInstancedUboHandle = BufferObjectSharedHandle{
driver.createBufferObject(size + sizeof(PerRenderableUib),
BufferObjectBinding::UNIFORM, BufferUsage::STATIC), driver };
// We also need a descriptor-set to hold the custom UBO. This works because
// we currently assume the descriptor-set only needs to hold this UBO in the
// instancing case (it wouldn't be true if we supported skinning/morphing, and
// in this case we would need to preserve the default descriptor-set content).
// This has the same lifetime as the UBO (see above).
mInstancedDescriptorSetHandle = DescriptorSetSharedHandle{
driver.createDescriptorSet(engine.getPerRenderableDescriptorSetLayout().getHandle()),
driver };
driver.updateDescriptorSetBuffer(mInstancedDescriptorSetHandle,
+PerRenderableBindingPoints::OBJECT_UNIFORMS,
mInstancedUboHandle, 0, sizeof(PerRenderableUib));
driver.updateBufferObjectUnsynchronized(mInstancedUboHandle,
BufferDescriptor::make(p, size,
[data = std::move(mInstancingStagingBuffer)](void*, size_t) {}), 0);
// patch the commands with the descriptor set
for (Command* const curr : mInstancingDescriptorSetPatch) {
assert_invariant(curr[0].info.instanceCount > 1);
assert_invariant(!curr[0].info.dsh);
curr[0].info.dsh = mInstancedDescriptorSetHandle;
}
mInstancingDescriptorSetPatch.clear();
assert_invariant(mInstancingStagingBuffer.empty());
}
}
/* static */
UTILS_ALWAYS_INLINE // This function exists only to make the code more readable. we want it inlined.
inline // and we don't need it in the compilation unit
@@ -581,6 +555,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
auto const* const UTILS_RESTRICT soaWorldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
auto const* const UTILS_RESTRICT soaVisibility = soa.data<FScene::VISIBILITY_STATE>();
auto const* const UTILS_RESTRICT soaSkinningData = soa.data<FScene::SKINNING_STATE>();
auto const* const UTILS_RESTRICT soaPrimitives = soa.data<FScene::PRIMITIVES>();
auto const* const UTILS_RESTRICT soaSkinning = soa.data<FScene::SKINNING_BUFFER>();
auto const* const UTILS_RESTRICT soaMorphing = soa.data<FScene::MORPHING_BUFFER>();
@@ -636,8 +611,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
// calculate the per-primitive face winding order inversion
bool const inverseFrontFaces = viewInverseFrontFaces ^ soaVisibility[i].reversedWindingOrder;
bool const hasMorphing = soaVisibility[i].morphing;
bool const hasSkinning = soaVisibility[i].skinning;
bool const hasMorphing = soaSkinningData[i].morphing;
bool const hasSkinning = soaSkinningData[i].skinning;
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
// if we are already an SSR variant, the SRE bit is already set
@@ -693,7 +668,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
const bool shadowCaster = soaVisibility[i].castShadows & hasShadowing;
const bool writeDepthForShadowCasters = depthContainsShadowCasters & shadowCaster;
Slice<const FRenderPrimitive> primitives = soaPrimitives[i];
Slice<const FRenderPrimitive> const primitives = soaPrimitives[i];
/*
* This is our hot loop. It's written to avoid branches.
* When modifying this code, always ensure it stays efficient.
@@ -1160,6 +1135,7 @@ RenderPass::Executor::Executor(RenderPass const& pass, Command const* b, Command
mHasScissorViewport = mScissor != backend::Viewport{ 0, 0, INT32_MAX, INT32_MAX };
assert_invariant(b >= pass.begin());
assert_invariant(e <= pass.end());
assert_invariant(pass.isFinalized());
}
RenderPass::Executor::Executor() noexcept
+19 -12
View File
@@ -61,6 +61,7 @@ class FMaterialInstance;
class FRenderPrimitive;
class RenderPassBuilder;
class ColorPassDescriptorSet;
struct PerRenderableData;
class RenderPass {
public:
@@ -310,6 +311,10 @@ public:
// allocated commands ARE NOT freed, they're owned by the Arena
~RenderPass() noexcept;
// this must be called before calling getExecutor(), but can't be called from within
// a render pass
void finalize(FEngine const& engine, backend::DriverApi& driver);
// Specifies the viewport for the scissor rectangle, that is, the final scissor rect is
// offset by the viewport's left-top and clipped to the viewport's width/height.
void setScissorViewport(backend::Viewport const viewport) noexcept {
@@ -324,14 +329,14 @@ public:
std::reference_wrapper<backend::DriverApi> driver;
public:
explicit BufferObjectHandleDeleter(backend::DriverApi& driver) noexcept : driver(driver) { }
void operator()(backend::BufferObjectHandle handle) noexcept;
void operator()(backend::BufferObjectHandle handle) const noexcept;
};
class DescriptorSetHandleDeleter {
std::reference_wrapper<backend::DriverApi> driver;
public:
explicit DescriptorSetHandleDeleter(backend::DriverApi& driver) noexcept : driver(driver) { }
void operator()(backend::DescriptorSetHandle handle) noexcept;
void operator()(backend::DescriptorSetHandle handle) const noexcept;
};
using BufferObjectSharedHandle = SharedHandle<
@@ -340,6 +345,8 @@ public:
using DescriptorSetSharedHandle = SharedHandle<
backend::HwDescriptorSet, DescriptorSetHandleDeleter>;
bool isFinalized() const noexcept { return mFinalized; }
/*
* Executor holds the range of commands to execute for a given pass
*/
@@ -409,8 +416,7 @@ public:
private:
friend class FRenderer;
friend class RenderPassBuilder;
RenderPass(FEngine const& engine, backend::DriverApi& driver,
RenderPassBuilder const& builder) noexcept;
RenderPass(FEngine const& engine, RenderPassBuilder const& builder) noexcept;
// This is the main function of this class, this appends commands to the pass using
// the current camera, geometry and flags set. This can be called multiple times if needed.
@@ -427,7 +433,7 @@ private:
// Appends a custom command.
void appendCustomCommand(Command* commands,
uint8_t channel, Pass pass, CustomCommand custom, uint32_t order,
Executor::CustomCommandFn command);
Executor::CustomCommandFn command) const;
static Command* resize(Arena& arena, Command* last) noexcept;
@@ -436,10 +442,7 @@ private:
Command* begin, Command* end) noexcept;
// instanceify commands then trims sentinels
Command* instanceify(backend::DriverApi& driver,
backend::DescriptorSetLayoutHandle perRenderableDescriptorSetLayoutHandle,
Command* begin, Command* end,
int32_t eyeCount) const noexcept;
Command* instanceify(Command* begin, Command* end, int32_t eyeCount) const noexcept;
// We choose the command count per job to minimize JobSystem overhead.
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 128;
@@ -474,8 +477,12 @@ private:
backend::Viewport mScissorViewport{ 0, 0, INT32_MAX, INT32_MAX };
Command const* /* const */ mCommandBegin = nullptr; // Pointer to the first command
Command const* /* const */ mCommandEnd = nullptr; // Pointer to one past the last command
mutable BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
mutable DescriptorSetSharedHandle mInstancedDescriptorSetHandle; // a descriptor-set to hold the ubo
mutable std::vector<PerRenderableData> mInstancingStagingBuffer;
mutable std::vector<Command*> mInstancingDescriptorSetPatch;
BufferObjectSharedHandle mInstancedUboHandle; // ubo for instanced primitives
DescriptorSetSharedHandle mInstancedDescriptorSetHandle; // a descriptor-set to hold the ubo
bool mFinalized = false;
// a vector for our custom commands
using CustomCommandVector = utils::FixedCapacityVector<Executor::CustomCommandFn>;
mutable CustomCommandVector mCustomCommands;
@@ -573,7 +580,7 @@ public:
uint32_t order,
const RenderPass::Executor::CustomCommandFn& command);
RenderPass build(FEngine const& engine, backend::DriverApi& driver) const;
RenderPass build(FEngine const& engine) const;
};
+1
View File
@@ -18,6 +18,7 @@
#include "details/Engine.h"
#include "details/VertexBuffer.h"
#include "details/IndexBuffer.h"
#include "details/Material.h"
using namespace utils;
+1 -1
View File
@@ -16,7 +16,7 @@
#include <filament/Renderer.h>
#include "ResourceAllocator.h"
#include "TextureCache.h"
#include "details/Engine.h"
#include "details/Renderer.h"
+4 -2
View File
@@ -415,13 +415,15 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
renderPassFlags |= RenderPass::HAS_DEPTH_CLAMP;
}
RenderPass const pass = passBuilder
RenderPass pass = passBuilder
.renderFlags(RenderPass::HAS_DEPTH_CLAMP, renderPassFlags)
.camera(cameraInfo.getPosition(), cameraInfo.getForwardVector())
.visibilityMask(entry.visibilityMask)
.geometry(scene->getRenderableData(), entry.range)
.commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW)
.build(engine, driver);
.build(engine);
pass.finalize(engine, driver);
entry.executor = pass.getExecutor();
+14
View File
@@ -58,6 +58,16 @@ void Texture::setImage(Engine& engine, size_t const level,
downcast(this)->setImage(downcast(engine), level, std::move(buffer), faceOffsets);
}
backend::AsyncCallId Texture::setImageAsync(Engine& engine, size_t const level,
uint32_t const xoffset, uint32_t const yoffset, uint32_t const zoffset,
uint32_t const width, uint32_t const height, uint32_t const depth,
PixelBufferDescriptor&& buffer, backend::CallbackHandler* handler,
AsyncCompletionCallback callback, void* user) const {
return downcast(this)->setImageAsync(downcast(engine),
level, xoffset, yoffset, zoffset, width, height, depth, std::move(buffer),
handler, std::move(callback), user);
}
void Texture::setExternalImage(Engine& engine, ExternalImageHandleRef image) noexcept {
downcast(this)->setExternalImage(downcast(engine), image);
}
@@ -78,6 +88,10 @@ void Texture::generateMipmaps(Engine& engine) const noexcept {
downcast(this)->generateMipmaps(downcast(engine));
}
bool Texture::isCreationComplete() const noexcept {
return downcast(this)->isCreationComplete();
}
bool Texture::isTextureFormatSupported(Engine& engine, InternalFormat const format) noexcept {
return FTexture::isTextureFormatSupported(downcast(engine), format);
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
#include "ResourceAllocator.h"
#include "TextureCache.h"
#include <filament/Engine.h>
@@ -56,31 +56,31 @@ using namespace backend;
template<typename K, typename V, typename H>
UTILS_NOINLINE
ResourceAllocator::AssociativeContainer<K, V, H>::AssociativeContainer() {
TextureCache::AssociativeContainer<K, V, H>::AssociativeContainer() {
mContainer.reserve(128);
}
template<typename K, typename V, typename H>
UTILS_NOINLINE
ResourceAllocator::AssociativeContainer<K, V, H>::~AssociativeContainer() noexcept = default;
TextureCache::AssociativeContainer<K, V, H>::~AssociativeContainer() noexcept = default;
template<typename K, typename V, typename H>
UTILS_NOINLINE
typename ResourceAllocator::AssociativeContainer<K, V, H>::iterator
ResourceAllocator::AssociativeContainer<K, V, H>::erase(iterator it) {
typename TextureCache::AssociativeContainer<K, V, H>::iterator
TextureCache::AssociativeContainer<K, V, H>::erase(iterator it) {
return mContainer.erase(it);
}
template<typename K, typename V, typename H>
typename ResourceAllocator::AssociativeContainer<K, V, H>::const_iterator
ResourceAllocator::AssociativeContainer<K, V, H>::find(key_type const& key) const {
typename TextureCache::AssociativeContainer<K, V, H>::const_iterator
TextureCache::AssociativeContainer<K, V, H>::find(key_type const& key) const {
return const_cast<AssociativeContainer*>(this)->find(key);
}
template<typename K, typename V, typename H>
UTILS_NOINLINE
typename ResourceAllocator::AssociativeContainer<K, V, H>::iterator
ResourceAllocator::AssociativeContainer<K, V, H>::find(key_type const& key) {
typename TextureCache::AssociativeContainer<K, V, H>::iterator
TextureCache::AssociativeContainer<K, V, H>::find(key_type const& key) {
return std::find_if(mContainer.begin(), mContainer.end(), [&key](auto const& v) {
return v.first == key;
});
@@ -89,21 +89,21 @@ ResourceAllocator::AssociativeContainer<K, V, H>::find(key_type const& key) {
template<typename K, typename V, typename H>
template<typename... ARGS>
UTILS_NOINLINE
void ResourceAllocator::AssociativeContainer<K, V, H>::emplace(ARGS&& ... args) {
void TextureCache::AssociativeContainer<K, V, H>::emplace(ARGS&& ... args) {
mContainer.emplace_back(std::forward<ARGS>(args)...);
}
// ------------------------------------------------------------------------------------------------
ResourceAllocatorInterface::~ResourceAllocatorInterface() = default;
TextureCacheInterface::~TextureCacheInterface() = default;
// ------------------------------------------------------------------------------------------------
ResourceAllocatorDisposerInterface::~ResourceAllocatorDisposerInterface() = default;
TextureCacheDisposerInterface::~TextureCacheDisposerInterface() = default;
// ------------------------------------------------------------------------------------------------
size_t ResourceAllocator::TextureKey::getSize() const noexcept {
size_t TextureCache::TextureKey::getSize() const noexcept {
size_t const pixelCount = width * height * depth;
size_t size = pixelCount * FTexture::getFormatSize(format);
size_t const s = std::max(uint8_t(1), samples);
@@ -120,24 +120,24 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept {
return size;
}
ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept
TextureCache::TextureCache(Engine::Config const& config, DriverApi& driverApi) noexcept
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
mBackend(driverApi),
mDisposer(std::make_shared<ResourceAllocatorDisposer>(driverApi)) {
mDisposer(std::make_shared<TextureCacheDisposer>(driverApi)) {
}
ResourceAllocator::ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
TextureCache::TextureCache(std::shared_ptr<TextureCacheDisposer> disposer,
Engine::Config const& config, DriverApi& driverApi) noexcept
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
mBackend(driverApi),
mDisposer(std::move(disposer)) {
}
ResourceAllocator::~ResourceAllocator() noexcept {
TextureCache::~TextureCache() noexcept {
assert_invariant(mTextureCache.empty());
}
void ResourceAllocator::terminate() noexcept {
void TextureCache::terminate() noexcept {
auto& textureCache = mTextureCache;
for (auto it = textureCache.begin(); it != textureCache.end();) {
mBackend.destroyTexture(it->second.handle);
@@ -145,7 +145,7 @@ void ResourceAllocator::terminate() noexcept {
}
}
RenderTargetHandle ResourceAllocator::createRenderTarget(StaticString name,
RenderTargetHandle TextureCache::createRenderTarget(StaticString name,
TargetBufferFlags const targetBufferFlags, uint32_t const width, uint32_t const height,
uint8_t const samples, uint8_t const layerCount, MRT const color, TargetBufferInfo const depth,
TargetBufferInfo const stencil) noexcept {
@@ -154,11 +154,11 @@ RenderTargetHandle ResourceAllocator::createRenderTarget(StaticString name,
return handle;
}
void ResourceAllocator::destroyRenderTarget(RenderTargetHandle const h) noexcept {
void TextureCache::destroyRenderTarget(RenderTargetHandle const h) noexcept {
mBackend.destroyRenderTarget(h);
}
TextureHandle ResourceAllocator::createTexture(StaticString name,
TextureHandle TextureCache::createTexture(StaticString name,
SamplerType const target, uint8_t const levels, TextureFormat const format, uint8_t samples,
uint32_t const width, uint32_t const height, uint32_t const depth,
std::array<TextureSwizzle, 4> const swizzle,
@@ -207,7 +207,7 @@ TextureHandle ResourceAllocator::createTexture(StaticString name,
return handle;
}
void ResourceAllocator::destroyTexture(TextureHandle const h) noexcept {
void TextureCache::destroyTexture(TextureHandle const h) noexcept {
auto const key = mDisposer->checkin(h);
if constexpr (mEnabled) {
if (UTILS_LIKELY(key.has_value())) {
@@ -221,11 +221,11 @@ void ResourceAllocator::destroyTexture(TextureHandle const h) noexcept {
}
}
ResourceAllocatorDisposerInterface& ResourceAllocator::getDisposer() noexcept {
TextureCacheDisposerInterface& TextureCache::getDisposer() noexcept {
return *mDisposer;
}
void ResourceAllocator::gc(bool const skippedFrame) noexcept {
void TextureCache::gc(bool const skippedFrame) noexcept {
// this is called regularly -- usually once per frame
// increase our age at each (non-skipped) frame
@@ -289,7 +289,7 @@ void ResourceAllocator::gc(bool const skippedFrame) noexcept {
}
UTILS_NOINLINE
void ResourceAllocator::dump(bool const brief) const noexcept {
void TextureCache::dump(bool const brief) const noexcept {
constexpr float MiB = 1.0f / float(1u << 20u);
DLOG(INFO) << "# entries=" << mTextureCache.size() << ", sz=" << (float) mCacheSize * MiB
<< " MiB"
@@ -305,8 +305,8 @@ void ResourceAllocator::dump(bool const brief) const noexcept {
}
}
ResourceAllocator::CacheContainer::iterator
ResourceAllocator::purge(
TextureCache::CacheContainer::iterator
TextureCache::purge(
CacheContainer::iterator const& pos) {
// DLOG(INFO) << "purging " << pos->second.handle.getId() << ", age=" << pos->second.age;
mBackend.destroyTexture(pos->second.handle);
@@ -316,19 +316,19 @@ ResourceAllocator::purge(
// ------------------------------------------------------------------------------------------------
ResourceAllocatorDisposer::ResourceAllocatorDisposer(DriverApi& driverApi) noexcept
TextureCacheDisposer::TextureCacheDisposer(DriverApi& driverApi) noexcept
: mBackend(driverApi) {
}
ResourceAllocatorDisposer::~ResourceAllocatorDisposer() noexcept {
TextureCacheDisposer::~TextureCacheDisposer() noexcept {
assert_invariant(mInUseTextures.empty());
}
void ResourceAllocatorDisposer::terminate() noexcept {
void TextureCacheDisposer::terminate() noexcept {
assert_invariant(mInUseTextures.empty());
}
void ResourceAllocatorDisposer::destroy(TextureHandle const handle) noexcept {
void TextureCacheDisposer::destroy(TextureHandle const handle) noexcept {
if (handle) {
auto r = checkin(handle);
if (r.has_value()) {
@@ -337,12 +337,12 @@ void ResourceAllocatorDisposer::destroy(TextureHandle const handle) noexcept {
}
}
void ResourceAllocatorDisposer::checkout(TextureHandle handle,
ResourceAllocator::TextureKey key) {
void TextureCacheDisposer::checkout(TextureHandle handle,
TextureCache::TextureKey key) {
mInUseTextures.emplace(handle, key);
}
std::optional<ResourceAllocator::TextureKey> ResourceAllocatorDisposer::checkin(
std::optional<TextureCache::TextureKey> TextureCacheDisposer::checkin(
TextureHandle handle) {
// find the texture in the in-use list (it must be there!)
auto it = mInUseTextures.find(handle);
@@ -40,19 +40,19 @@
namespace filament {
class ResourceAllocatorDisposer;
class TextureCacheDisposer;
// The only reason we use an interface here is for unit-tests, so we can mock this allocator.
// This is not too time-critical, so that's okay.
class ResourceAllocatorDisposerInterface {
class TextureCacheDisposerInterface {
public:
virtual void destroy(backend::TextureHandle handle) noexcept = 0;
protected:
virtual ~ResourceAllocatorDisposerInterface();
virtual ~TextureCacheDisposerInterface();
};
class ResourceAllocatorInterface {
class TextureCacheInterface {
public:
virtual backend::RenderTargetHandle createRenderTarget(utils::StaticString name,
backend::TargetBufferFlags targetBufferFlags,
@@ -74,21 +74,21 @@ public:
virtual void destroyTexture(backend::TextureHandle h) noexcept = 0;
virtual ResourceAllocatorDisposerInterface& getDisposer() noexcept = 0;
virtual TextureCacheDisposerInterface& getDisposer() noexcept = 0;
protected:
virtual ~ResourceAllocatorInterface();
virtual ~TextureCacheInterface();
};
class ResourceAllocator final : public ResourceAllocatorInterface {
class TextureCache final : public TextureCacheInterface {
public:
explicit ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
explicit TextureCache(std::shared_ptr<TextureCacheDisposer> disposer,
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
explicit ResourceAllocator(
explicit TextureCache(
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
~ResourceAllocator() noexcept override;
~TextureCache() noexcept override;
void terminate() noexcept;
@@ -112,7 +112,7 @@ public:
void destroyTexture(backend::TextureHandle h) noexcept override;
ResourceAllocatorDisposerInterface& getDisposer() noexcept override;
TextureCacheDisposerInterface& getDisposer() noexcept override;
void gc(bool skippedFrame = false) noexcept;
@@ -220,30 +220,30 @@ private:
purge(CacheContainer::iterator const& pos);
backend::DriverApi& mBackend;
std::shared_ptr<ResourceAllocatorDisposer> mDisposer;
std::shared_ptr<TextureCacheDisposer> mDisposer;
CacheContainer mTextureCache;
size_t mAge = 0;
uint32_t mCacheSize = 0;
uint32_t mCacheSizeHiWaterMark = 0;
static constexpr bool mEnabled = true;
friend class ResourceAllocatorDisposer;
friend class TextureCacheDisposer;
};
class ResourceAllocatorDisposer final : public ResourceAllocatorDisposerInterface {
using TextureKey = ResourceAllocator::TextureKey;
class TextureCacheDisposer final : public TextureCacheDisposerInterface {
using TextureKey = TextureCache::TextureKey;
public:
explicit ResourceAllocatorDisposer(backend::DriverApi& driverApi) noexcept;
~ResourceAllocatorDisposer() noexcept override;
explicit TextureCacheDisposer(backend::DriverApi& driverApi) noexcept;
~TextureCacheDisposer() noexcept override;
void terminate() noexcept;
void destroy(backend::TextureHandle handle) noexcept override;
private:
friend class ResourceAllocator;
friend class TextureCache;
void checkout(backend::TextureHandle handle, TextureKey key);
std::optional<TextureKey> checkin(backend::TextureHandle handle);
using InUseContainer = ResourceAllocator::AssociativeContainer<backend::TextureHandle, TextureKey>;
using InUseContainer = TextureCache::AssociativeContainer<backend::TextureHandle, TextureKey>;
backend::DriverApi& mBackend;
InUseContainer mInUseTextures;
};

Some files were not shown because too many files have changed in this diff Show More