Compare commits

...
Author SHA1 Message Date
Benjamin Doherty f172cac8f2 Fix web morphing sample 2023-03-15 13:02:50 -07:00
Benjamin Doherty 2a579c460f Merge branch 'rc/1.32.0' into release 2023-03-14 10:46:27 -07:00
Ben Doherty efdc801cff Vulkan: fix stack use-after-free (#6639) 2023-03-14 10:24:18 -07:00
Benjamin Doherty dd654c575a Bump MATERIAL_VERSION to 32 2023-03-13 14:04:13 -07:00
Benjamin Doherty 0c2599b6ca Bump version to 1.32.0 2023-03-09 10:40:39 -08:00
Benjamin Doherty 979d6742e0 Merge branch 'rc/1.31.7' into release 2023-03-09 10:39:41 -08:00
Benjamin Doherty 7cca872af5 Release Filament 1.31.7 2023-03-09 10:39:17 -08:00
Mathias Agopian 3cef1991b2 Minor cleanup of GL backend 2023-03-08 12:49:22 -08:00
Mathias Agopian 4a7a033d04 Fix timer query breakage on webgl 2023-03-08 12:33:11 -08:00
Mathias Agopian 64493d8636 Fix timer query breakage on webgl 2023-03-08 12:06:59 -08:00
Powei Feng df78322bdc android: fix sample-material-builder app name (#6624)
fixes #6622
2023-03-08 11:11:56 -08:00
Ben Doherty 5c2bbcb4a1 Remove compute shader assertion in GLProgram 2023-03-08 10:56:24 -08:00
Ben Doherty f1a967d631 Update web docs and include ktx2 assets (#6616) 2023-03-08 10:55:13 -08:00
Ben Doherty ec48dfa453 Remove compute shader assertion in GLProgram 2023-03-07 23:31:33 -08:00
Deadlock Yoon 5881a13b20 Metal: fix memory leak in readPixels
The PixelBufferDescriptor was not being deallocated properly, which
resulted in the leak. This patch explicitly deletes the
PixelBufferDescriptor at the end of the callback to prevent the leak
.This is necessary as the move constructor does not automatically
deallocate the existing PixelBufferDescriptor.
2023-03-07 11:02:39 -08:00
Mathias Agopian efd9e875db fix a discard flags issue
An attachment would be wrongly discarded if used as read-only: because
it didn't have a dependency to it, the discard flag was set.
This is fixed by setting the discard flag only if a resource is written
and has dependencies to it.

Should fix: #5005
2023-03-06 22:39:11 -08:00
Powei Feng 6f25e8ae5a vulkan: async readPixels (#6605)
- Add a TaskHandler class for process events on the backend
   thread
 - Check fence status when handler runs and copy bits to the client
   when the fence is reached.
2023-03-06 18:03:28 -08:00
Mathias Agopian 449519c1b4 cleanup fog
- update comments and some names
2023-03-06 16:28:31 -08:00
Mathias Agopian e2e5f7cee9 Add way to retrieve the user world-space in materials (#6607)
* Add way to retrieve the user world-space in materials

added `getUserWorldFromWorldMatrix()` and `getUserWorldPosition()` to
retrieve the API-level (user) world position in materials.
Deprecated `getWorldOffset()`

`getWorldOffset` didn't work when an IBL rotation was applied.

* fix large scenes with an ibl rotation

Rotate the IBL around the camera instead of the world so that the camera 
is always at the origin regardless of the rotation.
2023-03-03 21:34:17 -08:00
Powei Feng 9d30233a38 geometry: clean up TangentSpaceMesh (#6595)
- Reordered the 'const var' declarations to 'var const'
 - Use std::vector for data arrays instead of manually managing
   allocations
2023-03-03 15:33:28 -08:00
Ben Doherty d684fc4096 Compute shading_view will full precision (#6611) 2023-03-03 13:48:36 -08:00
Ben Doherty 5f3f3f1b24 Fix NaN parameters in material_sandbox (#6609) 2023-03-03 12:13:06 -08:00
Romain Guy 6aa2a2b89b Update getopt to musl-1.1's implementation (#6610)
* Update getopt to musl-1.1's implementation

Our former implementation was rather ancient and was using unsupported
features of C.

* Fix Windows
2023-03-03 11:56:32 -08:00
Powei Feng 47135164ee vulkan: fix validation BestPractices-vkCmdBeginRenderPass-ClearValueWithoutLoadOpClear (#6608) 2023-03-03 11:14:05 -08:00
Romain Guy 5a75fb9b53 Add new alphaToCoverage material property (#6606)
* Add new alphaToCoverage material property

The alphaToCoverage property lets you enable or disable alpha to coverage
in a material. More importantly it lets you overrides the behavior of
blending: masked which automatically enables alphaToCoverage.

* Update release notes
2023-03-02 12:40:55 -08:00
Mathias Agopian 7933b08e85 fix fog instability with strong falloff
The fog calculation could fail when the falloff was strong and the
camera z position was far from the fog height. The problem was
that the computation was spread between the cpu and gpu by splitting
an exp(), but with certain parameters the two exp() would independently
blow-up.  We fix this by doing the exp() only on the gpu side.
2023-03-01 16:48:16 -08:00
Mathias Agopian a6809b4550 Fix fog's z origin
the fog's z origin was always the camera height instead of being the
ground (or more precisely the value of fogHeight).
2023-03-01 16:48:16 -08:00
Mathias Agopian 8f77308bca fix fog integral computation
1-a/b not the same as (1-a)/b !
2023-03-01 16:48:16 -08:00
Mathias Agopian 65f1e64812 fix fog heigh falloff computation
it was especially wrong when near or at 0.
2023-03-01 16:48:16 -08:00
Mathias Agopian 79942b3bea fog parameters should be calculated in highp 2023-03-01 16:48:16 -08:00
Mathias Agopian 9ee41b9b6a rework again how we manage different versions of GL
try to be more explicit about which configurations are supported,
and use the same pattern everywhere for checking the gl version at
either compile and runtime.
2023-03-01 15:59:04 -08:00
Mathias Agopian 21995e4ac9 clip_control could fail on desktop
This didn't happen in practice, but we would call (null) if the
clip_control extension was available and we were not on GL 4.1.

We "fix" this by not supporting the clip_control extension on desktop.
It doesn't matter because clip_control is core as of 4.5 and is 
present in only 8% of non 4.5 GL implementation, and this doesn't
include any macOS versions.
2023-03-01 15:59:04 -08:00
Mathias Agopian 8dc871c86e remove code for unused extensions 2023-03-01 15:59:04 -08:00
Benjamin Doherty dec903a0ee Bump version to 1.31.7 2023-03-01 11:26:50 -08:00
Benjamin Doherty e9475b322b Merge branch 'rc/1.31.6' into release 2023-03-01 11:24:13 -08:00
Benjamin Doherty 78785a5bca Release Filament 1.31.6 2023-03-01 11:23:37 -08:00
Mathias Agopian 662b4b5ca3 fix typo that could cause a OOB
fortunately this would only happen when using SSBOs (which are not
used currently).
2023-02-27 13:06:25 -08:00
Mathias Agopian bee96d1cf1 abstract OpenGL's GLSync
This small abstraction is (will be) needed because GLES 2.0 doesn't
have sync object, however synchronization is sometimes available
externally, in particular with EGL.

This PR doesn't provide other implementations.
2023-02-27 12:40:21 -08:00
Mathias Agopian 122ebe1ccb More improvements to how we handle extensions (#6591)
- make sure to initialize all extension booleans, we treat them
  as feature flags.
- be more explicit about #define'ing gl tokens, so we can more easily
  catch errors later.
- don't blindly use extension tokens that might not be available
  (e.g.: GL_TEXTURE_EXTERNAL_OES or GL_TEXTURE_CUBE_MAP_ARRAY). It
  would probably cause a spurious gl error.
2023-02-24 16:52:43 -08:00
Mathias Agopian e741e165ae remove unused / no longer used code 2023-02-24 16:50:15 -08:00
Mathias Agopian d38dc24915 Cleanup how we get GL extensions entry-points
- iOS is treated the same way than Android now. The only difference
  is that iOS only provides prototypes and no typedef, whereas 
  Android only provides typedefs and no prototypes.

- Timer queries is core in GL and an extension on all versions of
  GLES. On iOS it's not available at the header level. An additional
  subtlety is that glGetObjectuiv is core in GLES 3.0, so it conflicts
  with the extension.
  So, now we do things correctly:
  - on desktop we use the core methods
  - on ios we ifdef out everything related to timer queries
  - on gles 2.0 and up we use only the extension entry-points
2023-02-24 11:55:22 -08:00
Powei Feng d12612f091 Add UV-based tangent space algorithms (#6572)
Added mikktspace as a third party lib
2023-02-24 09:32:02 -08:00
Mathias Agopian e6d73135c1 fix DEPTH_STENCIL case for fbo attachments
We were not testing for that case properly. This case is taken when 
either:
- depth & stencil textures are the same and not null
- or, only depth is specified but both attachments are requested


Also cleanup the dimension checks in debug builds.
2023-02-23 14:47:17 -08:00
Mathias Agopian 816612cf0a Fix readPixels() for "auto-resolved" MS color buffers
glReadPixel doesn't resolve automatically, but it does with the 
auto-resolve extension, which we're always emulating.
2023-02-23 10:56:08 -08:00
Mathias Agopian 7ca8ba272c don't rely on GL PACK/UNPACK convenience
- some of the convenience are not available in ES2
- it's less efficient
- we can save some PBO space when reading back
  a partial framebuffer.

We also avoid using GL_PACK_ROW_LENGTH which technically is not
a convenience, but in our case we are doing an extra copy anyways, so
we can account for the row-length at that point.
2023-02-23 10:55:46 -08:00
Mathias Agopian d337a6d019 Scene::prepare is now multithreaded 2023-02-22 16:28:40 -08:00
Mathias Agopian f79c4c6080 Improve directional shadow performance with large scenes
- We now call visitScene only once for the directional shadowmap instead of
1 + cascade_count times.

- Don't use visitScene for spot shadows

it was only used to compute the near/far planes, but instead we can
use the radius of the light. This could degrade the quality of the
spot shadows, but this can be corrected by setting a correct radius.

caveat: currently the near is hardcoded to 0.01 units. this should be
user-settable.

- Improve performance of visitScene calls

instead of transforming 8 points of an AABB and finding the min/max,
we transform the AABB and use its minz and maxz. This works for affine
transforms.

This change also cleans-up AABB and Box transform APIs, which also
are now inline.
2023-02-22 16:28:40 -08:00
Mathias Agopian 2e7fc6229b Improve StructureOfArray
- StructureOfArray: don't initialize trivial ctors

We mimic the behavior of std::vector<> here, where a resize() won't
initialize the array if the type is trivially_default_constructible.
This can reveal existing bugs, where we depended on the initialization
to 0.

- StructureOfArray: add push_back(std::tuple<>)

This basically allows us to push_back() a struct of the SoA.

- Make PerRenderableData trivially constructible

this improves performance when we have tons of objects in the scene
because PerRenderableData is used in arrays.
2023-02-22 16:28:40 -08:00
Mathias Agopian fc724575fa Make SYSTRACE_CALL work with SYSTRACE_CONTEXT
SYSTRACE_VALUE now requires a SYSTRACE_CONTEXT.
2023-02-22 16:28:40 -08:00
Mathias Agopian 84716610b8 cleanup: make const everything that can be 2023-02-22 16:28:40 -08:00
Benjamin Doherty d9aead8aac Linux CI: use clang 14 2023-02-21 16:06:48 -08:00
Powei Feng ce148ebeb1 Update linux clang to version 10 (#6556) 2023-02-21 16:00:07 -08:00
Powei Feng e1315fbaa7 Update CI Ubuntu version 2023-02-21 15:57:00 -08:00
Benjamin Doherty 1690549392 Bump version to 1.31.6 2023-02-21 13:18:22 -08:00
Benjamin Doherty c73710e343 Merge branch 'rc/1.31.5' into release 2023-02-21 13:16:47 -08:00
Benjamin Doherty 29983772ed Release Filament 1.31.5 2023-02-21 13:16:09 -08:00
ViktorHeisenberger d100e628c2 Invoke ccache on Win+ninja. There is no support for ENV vars. (#6582) 2023-02-21 09:19:03 -08:00
Mathias Agopian 94f121f37d Add a camera near/far setting in gltf_viewer 2023-02-17 11:16:42 -08:00
Mathias Agopian d9266ba61c fix a main camera far plane culling
we need to use the culling projection matrix for culling!
2023-02-16 15:40:32 -08:00
Ben Doherty 8389056d2d gltfio: Fix crash when a MIME type has no texture provider (#6569) 2023-02-16 14:21:26 -08:00
Powei Feng 26b8cb238a Add build.sh flag for ASAN/UBSAN (#6566) 2023-02-16 13:19:32 -08:00
Powei Feng f8d4690cd8 Update linux-continuous.yml to use ubuntu-22.04 (#6559) 2023-02-14 16:47:02 -08:00
Benjamin Doherty c2e2f80945 Fix RELEASE_NOTES.md 2023-02-14 14:10:40 -08:00
Powei Feng e9efcdf191 -Werror for libfilament and backend (#6547) 2023-02-14 01:15:25 -08:00
Mathias Agopian 8ea159520d workaround our -ffast-math
We use isnan() to detect that the estimation of directional light
parameters from the IBL has failed, however isnan() always returns
false with -ffast-math, which we're using in release builds.
This works around that. The affected code is non essential, performance
is not a concern here.
2023-02-13 21:51:43 -08:00
Mathias Agopian 337e18051a The default render channel is now 2 instead of 0
This also fixes some potential issues with refraction when using
render channels.
2023-02-13 17:31:34 -08:00
Ben Doherty 3c4e094990 Rework how release notes work (#6557) 2023-02-13 14:54:07 -08:00
Powei Feng 71e6f3d037 Update linux clang to version 10 (#6556) 2023-02-13 14:49:45 -08:00
Benjamin Doherty 092a0489da Bump version to 1.31.5 2023-02-13 11:53:24 -08:00
Benjamin Doherty d2690bf75a Release Filament 1.31.4 2023-02-13 11:51:06 -08:00
Ben Doherty 44f002a5a1 gltfio: Fix race condition affecting mobile and web (#6548)
When loading a glTF file on platforms without a filesystem, a client
calls `addResourceData` to populate `ResourceLoader`'s cache with data.
For example, a web client might make HTTP fetch requests to fill in
buffer data. This internal cache of data is stored in
`ResourceLoader::Impl::mUriDataCache`.

This works well, except the cache must persist until after it has been
uploaded to the GPU.

There was already a mechanism (see `uploadUserdata`) in place to ensure
that glTF data persisted until after it had been uploaded to the GPU.
However, this mechanism did not extend to client-provided data. Thus, a
race occured between Filament's driver consuming the buffer and it
getting freed.
2023-02-13 10:44:22 -08:00
Powei Feng f8442c9ec0 vulkan: Remove unused presentQueueFamilyIndex (#6545) 2023-02-10 12:41:00 -08:00
Powei Feng b3513c7d1f geometry: Add TangentSpaceMesh implementations (#6530)
geometry: Add TangentSpaceMesh implementations

Added:
 - Hughes-Moller
 - Frisvad's method
 - Flat shading
2023-02-10 09:34:49 -08:00
Mathias Agopian a82e813333 fix libmath streaming operators 2023-02-09 16:11:30 -08:00
Powei Feng dd246aeee4 vulkan: Assert that graphics-QF is present-QF (#6540) 2023-02-09 13:31:29 -08:00
Ben Doherty 1653d3615b Fix Metal crash due to empty program names (#6537) 2023-02-09 12:11:04 -08:00
Mathias Agopian 4847b00f9e improve SoA a bit
Instead of storing the arrays into an array of void*, we use a 
tuple<> instead. This improves debugging because now the tuple<>
has pointer with the correct types.

It also improves most of the code except `push_back` which now
relies on a hack -- this is the only place where I'm not able to 
resolve the array strictly at compile time, even if in practice it is.
2023-02-08 16:22:00 -08:00
Igor Korobka d613145c1a Fix JNI after minification is applied
UbershaderProvider.getNativeObject() is accessed from AssetLoader.cpp,
so it must be annotated with @UsedByNative("AssetLoader.cpp") to
avoid runtime crashes when minification is applied.

Fixes #5944
2023-02-08 15:29:55 -08:00
ritik619 a788f66e18 Fixed typographic error 2023-02-08 12:01:50 -08:00
Romain Guy 9ca3a7456c Fix JNI 2023-02-08 09:07:59 -08:00
Mathias Agopian 5cec001058 matdbg: direct SPIRV edit now supported 2023-02-03 13:57:44 -08:00
Mathias Agopian 2fb42f5144 matdbg: refresh spirv properly
we now refresh the spirv code properly after it's rebuilt from the
glsl tab.
2023-02-02 11:10:21 -08:00
Mathias Agopian 02c22a668c matdbg: make sure we use the same spirv parameters everywhere 2023-02-02 11:10:02 -08:00
Mathias Agopian a7bf90f5be build.sh -d is now split in two options
`-d` now enables matdbg and adds debugging data, but doesn't affect 
 material optimization

`-g` disables material optimizations


A similar change is done with gradle options. The new proprety
`com.google.android.filament.matnopt` is used to disable material
optimizations.

These options mimic `matc` options.
2023-02-01 23:59:00 -08:00
Mathias Agopian f71e90fae0 deduplicate spirv disassembly code 2023-02-01 21:12:53 -08:00
Mathias Agopian 8afb11128d move UibGenerator and SibGenerator to the shader folder
The shader folder is where we generate the glsl, so this is more
appropriate.
2023-02-01 21:12:53 -08:00
Mathias Agopian 2e0c238cc6 We now require Vulkan 1.1 for the vulkan backend 2023-02-01 21:10:26 -08:00
164 changed files with 6263 additions and 2470 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs:
build-linux:
name: build-linux
runs-on: ubuntu-18.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3.3.0
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-18.04]
os: [macos-latest, ubuntu-22.04]
steps:
- uses: actions/checkout@v3.3.0
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-18.04]
os: [macos-latest, ubuntu-22.04]
steps:
- name: Decide Git ref
@@ -24,3 +24,4 @@ jobs:
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ github.event.pull_request.number }}
release-notes-file: 'NEW_RELEASE_NOTES.md'
+37 -24
View File
@@ -31,6 +31,8 @@ option(FILAMENT_SKIP_SDL2 "Skip dependencies of SDL2, and SDL2" OFF)
option(FILAMENT_LINUX_IS_MOBILE "Treat Linux as Mobile" OFF)
option(FILAMENT_ENABLE_ASAN_UBSAN "Enable Address and Undefined Behavior Sanitizers" OFF)
set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)
@@ -67,25 +69,30 @@ endif()
# ==================================================================================================
find_program(CCACHE_PROGRAM ccache)
if (CCACHE_PROGRAM)
set(C_LAUNCHER "${CCACHE_PROGRAM}")
set(CXX_LAUNCHER "${CCACHE_PROGRAM}")
configure_file(build/launch-c.in launch-c)
configure_file(build/launch-cxx.in launch-cxx)
execute_process(COMMAND chmod a+rx
"${CMAKE_CURRENT_BINARY_DIR}/launch-c"
"${CMAKE_CURRENT_BINARY_DIR}/launch-cxx"
)
if (CMAKE_GENERATOR STREQUAL "Xcode")
set(CMAKE_XCODE_ATTRIBUTE_CC "${CMAKE_CURRENT_BINARY_DIR}/launch-c")
set(CMAKE_XCODE_ATTRIBUTE_CXX "${CMAKE_CURRENT_BINARY_DIR}/launch-cxx")
set(CMAKE_XCODE_ATTRIBUTE_LD "${CMAKE_CURRENT_BINARY_DIR}/launch-c")
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_CURRENT_BINARY_DIR}/launch-cxx")
if (WIN32)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
else()
set(CMAKE_C_COMPILER_LAUNCHER "${CMAKE_CURRENT_BINARY_DIR}/launch-c")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_CURRENT_BINARY_DIR}/launch-cxx")
set(C_LAUNCHER "${CCACHE_PROGRAM}")
set(CXX_LAUNCHER "${CCACHE_PROGRAM}")
configure_file(build/launch-c.in launch-c)
configure_file(build/launch-cxx.in launch-cxx)
execute_process(COMMAND chmod a+rx
"${CMAKE_CURRENT_BINARY_DIR}/launch-c"
"${CMAKE_CURRENT_BINARY_DIR}/launch-cxx"
)
if (CMAKE_GENERATOR STREQUAL "Xcode")
set(CMAKE_XCODE_ATTRIBUTE_CC "${CMAKE_CURRENT_BINARY_DIR}/launch-c")
set(CMAKE_XCODE_ATTRIBUTE_CXX "${CMAKE_CURRENT_BINARY_DIR}/launch-cxx")
set(CMAKE_XCODE_ATTRIBUTE_LD "${CMAKE_CURRENT_BINARY_DIR}/launch-c")
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_CURRENT_BINARY_DIR}/launch-cxx")
else()
set(CMAKE_C_COMPILER_LAUNCHER "${CMAKE_CURRENT_BINARY_DIR}/launch-c")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_CURRENT_BINARY_DIR}/launch-cxx")
endif()
endif()
endif()
@@ -374,10 +381,10 @@ endif()
# ==================================================================================================
# Debug compiler flags
# ==================================================================================================
# ASAN is deactivated for now because:
# -fsanitize=undefined causes extremely long link times
# -fsanitize=address causes a crash with assimp, which we can't explain for now
#set(EXTRA_SANITIZE_OPTIONS "-fsanitize=undefined -fsanitize=address")
if (FILAMENT_ENABLE_ASAN_UBSAN)
set(EXTRA_SANITIZE_OPTIONS "-fsanitize=address -fsanitize=undefined")
endif()
if (NOT MSVC AND NOT WEBGL)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstack-protector")
endif()
@@ -497,9 +504,14 @@ if (FILAMENT_SUPPORTS_METAL)
set(MATC_API_FLAGS ${MATC_API_FLAGS} -a metal)
endif()
# Disable optimizations and enable debug info (preserves names in SPIR-V)
# Enable debug info (preserves names in SPIR-V)
if (FILAMENT_ENABLE_MATDBG)
set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -d)
endif()
# Disable optimizations
if (FILAMENT_DISABLE_MATOPT)
set(MATC_OPT_FLAGS -gd)
set(MATC_OPT_FLAGS ${MATC_OPT_FLAGS} -g)
endif()
set(MATC_BASE_FLAGS ${MATC_API_FLAGS} -p ${MATC_TARGET} ${MATC_OPT_FLAGS})
@@ -675,6 +687,7 @@ add_subdirectory(${EXTERNAL}/robin-map/tnt)
add_subdirectory(${EXTERNAL}/smol-v/tnt)
add_subdirectory(${EXTERNAL}/benchmark/tnt)
add_subdirectory(${EXTERNAL}/meshoptimizer/tnt)
add_subdirectory(${EXTERNAL}/mikktspace)
add_subdirectory(${EXTERNAL}/cgltf/tnt)
add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt)
+9
View File
@@ -0,0 +1,9 @@
# Filament Release Notes log
**If you are merging a PR into main**: please add the release note below, under the *Release notes
for next branch cut* header.
**If you are cherry-picking a commit into an rc/ branch**: add the release note under the
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.31.4'
implementation 'com.google.android.filament:filament-android:1.32.0'
}
```
@@ -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:
```
pod 'Filament', '~> 1.31.4'
pod 'Filament', '~> 1.32.0'
```
### Snapshots
+24 -1
View File
@@ -3,7 +3,30 @@
This file contains one line summaries of commits that are worthy of mentioning in release notes.
A new header is inserted each time a *tag* is created.
## main branch
**Do not edit this file unless you are performing a release or cherry-picking into an rc/ branch.**
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.32.0
- fog: fixed fog height falloff and computation precision on mobile [⚠️ **Recompile Materials**]
- materials: new alphaToCoverage property can be used to control alpha to coverage behavior
- materials: added `getUserWorldFromWorldMatrix()` and `getUserWorldPosition()` to retrieve the
API-level (user) world position in materials. Deprecated `getWorldOffset()`. [⚠️ **Recompile
Materials**]
- engine: fix precision issue with `shading_view` in large scenes
- vulkan: readPixels is now async (#6560)
## v1.31.7
## v1.31.6
- engine: the default render channel is now 2 instead of 0
- gltfio: Fix crash when a MIME type has no texture provider
## v1.31.5
- gltfio: fix potential early freeing of data provided with `ResourceLoader::addResourceData`.
## v1.31.4
+9 -2
View File
@@ -12,7 +12,10 @@
// When set, support for Vulkan will be excluded.
//
// com.google.android.filament.matdbg
// When set, enables matdbg, disables shader optimizations
// When set, enables matdbg
//
// com.google.android.filament.matnopt
// When set, disable shader optimizations.
//
// com.google.android.filament.skip-samples
// Exclude samples from the project. Useful to speed up compilation.
@@ -62,6 +65,10 @@ buildscript {
.gradleProperty("com.google.android.filament.matdbg")
.isPresent()
def matnopt = providers
.gradleProperty("com.google.android.filament.matnopt")
.isPresent()
def abis = ["arm64-v8a", "armeabi-v7a", "x86_64", "x86"]
def newAbis = providers
.gradleProperty("com.google.android.filament.abis")
@@ -108,7 +115,7 @@ buildscript {
"-DFILAMENT_DIST_DIR=${filamentPath}".toString(),
"-DFILAMENT_SUPPORTS_VULKAN=${excludeVulkan ? 'OFF' : 'ON'}".toString(),
"-DFILAMENT_ENABLE_MATDBG=${matdbg ? 'ON' : 'OFF'}".toString(),
"-DFILAMENT_DISABLE_MATOPT=${matdbg ? 'ON' : 'OFF'}".toString()
"-DFILAMENT_DISABLE_MATOPT=${matnopt ? 'ON' : 'OFF'}".toString()
]
ext.cppFlags = [
@@ -250,6 +250,13 @@ Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderMaskThr
builder->maskThreshold(maskThreshold);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderAlphaToCoverage(JNIEnv*,
jclass, jlong nativeBuilder, jboolean enable) {
auto builder = (MaterialBuilder*) nativeBuilder;
builder->alphaToCoverage(enable);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_android_filament_filamat_MaterialBuilder_nMaterialBuilderShadowMultiplier(
JNIEnv*, jclass, jlong nativeBuilder, jboolean shadowMultiplier) {
@@ -360,6 +360,12 @@ public class MaterialBuilder {
return this;
}
@NonNull
public MaterialBuilder alphaToCoverage(boolean enable) {
nMaterialBuilderAlphaToCoverage(mNativeObject, enable);
return this;
}
@NonNull
public MaterialBuilder shadowMultiplier(boolean shadowMultiplier) {
nMaterialBuilderShadowMultiplier(mNativeObject, shadowMultiplier);
@@ -584,6 +590,7 @@ public class MaterialBuilder {
private static native void nMaterialBuilderDepthCulling(long nativeBuilder, boolean enable);
private static native void nMaterialBuilderDoubleSided(long nativeBuilder, boolean doubleSided);
private static native void nMaterialBuilderMaskThreshold(long nativeBuilder, float mode);
private static native void nMaterialBuilderAlphaToCoverage(long nativeBuilder, boolean enable);
private static native void nMaterialBuilderShadowMultiplier(long mNativeObject,
boolean shadowMultiplier);
@@ -153,6 +153,14 @@ Java_com_google_android_filament_Material_nIsDoubleSided(JNIEnv*, jclass,
return (jboolean) material->isDoubleSided();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_Material_nIsAlphaToCoverageEnabled(JNIEnv*, jclass,
jlong nativeMaterial) {
Material* material = (Material*) nativeMaterial;
return (jboolean) material->isAlphaToCoverageEnabled();
}
extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_Material_nGetMaskThreshold(JNIEnv*, jclass,
@@ -457,7 +457,6 @@ extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetMaskThreshold(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nGetMaskThreshold()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->getMaskThreshold();
}
@@ -466,7 +465,6 @@ extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetSpecularAntiAliasingVariance(JNIEnv* env,
jclass clazz, jlong nativeMaterialInstance) {
// TODO: implement nGetSpecularAntiAliasingVariance()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->getSpecularAntiAliasingVariance();
}
@@ -475,7 +473,6 @@ extern "C"
JNIEXPORT jfloat JNICALL
Java_com_google_android_filament_MaterialInstance_nGetSpecularAntiAliasingThreshold(JNIEnv* env,
jclass clazz, jlong nativeMaterialInstance) {
// TODO: implement nGetSpecularAntiAliasingThreshold()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->getSpecularAntiAliasingThreshold();
}
@@ -484,7 +481,6 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsDoubleSided(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsDoubleSided()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isDoubleSided();
}
@@ -493,7 +489,6 @@ extern "C"
JNIEXPORT jint JNICALL
Java_com_google_android_filament_MaterialInstance_nGetCullingMode(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nGetCullingMode()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return (jint)instance->getCullingMode();
}
@@ -502,7 +497,6 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsColorWriteEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsColorWriteEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isColorWriteEnabled();
}
@@ -511,7 +505,6 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsDepthWriteEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsDepthWriteEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isDepthWriteEnabled();
}
@@ -520,7 +513,6 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsStencilWriteEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsStencilWriteEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isStencilWriteEnabled();
}
@@ -529,7 +521,6 @@ extern "C"
JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_MaterialInstance_nIsDepthCullingEnabled(JNIEnv* env, jclass clazz,
jlong nativeMaterialInstance) {
// TODO: implement nIsDepthCullingEnabled()
MaterialInstance* instance = (MaterialInstance*)nativeMaterialInstance;
return instance->isDepthCullingEnabled();
}
@@ -503,6 +503,17 @@ public class Material {
return nIsDoubleSided(getNativeObject());
}
/**
* Indicates whether instances of this material will use alpha to coverage.
*
* @see
* <a href="https://google.github.io/filament/Materials.html#materialdefinitions/materialblock/rasterization:alphatocoverage">
* Rasterization: alphaToCoverage</a>
*/
public boolean isAlphaToCoverageEnabled() {
return nIsAlphaToCoverageEnabled(getNativeObject());
}
/**
* Returns the alpha mask threshold used when the blending mode is set to masked.
*
@@ -915,6 +926,7 @@ public class Material {
private static native boolean nIsDepthWriteEnabled(long nativeMaterial);
private static native boolean nIsDepthCullingEnabled(long nativeMaterial);
private static native boolean nIsDoubleSided(long nativeMaterial);
private static native boolean nIsAlphaToCoverageEnabled(long nativeMaterial);
private static native float nGetMaskThreshold(long nativeMaterial);
private static native float nGetSpecularAntiAliasingVariance(long nativeMaterial);
private static native float nGetSpecularAntiAliasingThreshold(long nativeMaterial);
@@ -307,11 +307,16 @@ public class RenderableManager {
/**
* Set the channel this renderable is associated to. There can be 4 channels.
* All renderables in a given channel are rendered together, regardless of anything else.
* They are sorted as usual withing a channel.
* Channels work similarly to priorities, except that they enforce the strongest ordering.
*
* @param channel clamped to the range [0..3], defaults to 0.
* <p>All renderables in a given channel are rendered together, regardless of anything else.
* They are sorted as usual within a channel.</p>
* <p>Channels work similarly to priorities, except that they enforce the strongest
* ordering.</p>
*
* <p>Channels 0 and 1 may not have render primitives using a material with `refractionType`
* set to `screenspace`.</p>
*
* @param channel clamped to the range [0..3], defaults to 2.
*
* @return Builder reference for chaining calls.
*
@@ -276,7 +276,7 @@ public class TextureSampler {
}
/**
* Sets the wrapping mode in the t (depth) direction.
* Sets the wrapping mode in the r (depth) direction.
* @param mode wrapping mode
*/
public void setWrapModeR(WrapMode mode) {
@@ -166,6 +166,8 @@ Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv
const jfieldID cameraAperture = env->GetFieldID(klass, "cameraAperture", "F");
const jfieldID cameraSpeed = env->GetFieldID(klass, "cameraSpeed", "F");
const jfieldID cameraISO = env->GetFieldID(klass, "cameraISO", "F");
const jfieldID cameraNear = env->GetFieldID(klass, "cameraNear", "F");
const jfieldID cameraFar = env->GetFieldID(klass, "cameraFar", "F");
const jfieldID groundShadowStrength = env->GetFieldID(klass, "groundShadowStrength", "F");
const jfieldID groundPlaneEnabled = env->GetFieldID(klass, "groundPlaneEnabled", "Z");
const jfieldID skyboxEnabled = env->GetFieldID(klass, "skyboxEnabled", "Z");
@@ -177,6 +179,8 @@ Java_com_google_android_filament_utils_AutomationEngine_nGetViewerOptions(JNIEnv
env->SetFloatField(result, cameraAperture, options.cameraAperture);
env->SetFloatField(result, cameraSpeed, options.cameraSpeed);
env->SetFloatField(result, cameraISO, options.cameraISO);
env->SetFloatField(result, cameraNear, options.cameraNear);
env->SetFloatField(result, cameraFar, options.cameraFar);
env->SetFloatField(result, groundShadowStrength, options.groundShadowStrength);
env->SetBooleanField(result, groundPlaneEnabled, options.groundPlaneEnabled);
env->SetBooleanField(result, skyboxEnabled, options.skyboxEnabled);
@@ -97,6 +97,8 @@ public class AutomationEngine {
public float cameraAperture = 16.0f;
public float cameraSpeed = 125.0f;
public float cameraISO = 100.0f;
public float cameraNear = 0.1f;
public float cameraFar = 100.0f;
public float groundShadowStrength = 0.75f;
public boolean groundPlaneEnabled = false;
public boolean skyboxEnabled = true;
@@ -27,8 +27,8 @@ import com.google.android.filament.gltfio.*
import kotlinx.coroutines.*
import java.nio.Buffer
private const val kNearPlane = 0.05 // 5 cm
private const val kFarPlane = 1000.0 // 1 km
private const val kNearPlane = 0.05f // 5 cm
private const val kFarPlane = 1000.0f // 1 km
private const val kAperture = 16f
private const val kShutterSpeed = 1f / 125f
private const val kSensitivity = 100f
@@ -80,6 +80,18 @@ class ModelViewer(
updateCameraProjection()
}
var cameraNear = kNearPlane
set(value) {
field = value
updateCameraProjection()
}
var cameraFar = kFarPlane
set(value) {
field = value
updateCameraProjection()
}
val scene: Scene
val view: View
val camera: Camera
@@ -368,7 +380,8 @@ class ModelViewer(
val width = view.viewport.width
val height = view.viewport.height
val aspect = width.toDouble() / height.toDouble()
camera.setLensProjection(cameraFocalLength.toDouble(), aspect, kNearPlane, kFarPlane)
camera.setLensProjection(cameraFocalLength.toDouble(), aspect,
cameraNear.toDouble(), cameraFar.toDouble())
}
inner class SurfaceCallback : UiHelper.RendererCallback {
@@ -28,47 +28,81 @@ import com.google.android.filament.proguard.UsedByNative;
@UsedByNative("AssetLoader.cpp")
public interface MaterialProvider {
/**
* MaterialKey specifies the requirements for a requested glTF material.
* The provider creates Filament materials that fulfill these requirements.
*/
@UsedByNative("MaterialKey.cpp")
public static class MaterialKey {
@UsedByNative("MaterialKey.cpp")
public boolean doubleSided;
@UsedByNative("MaterialKey.cpp")
public boolean unlit;
@UsedByNative("MaterialKey.cpp")
public boolean hasVertexColors;
@UsedByNative("MaterialKey.cpp")
public boolean hasBaseColorTexture;
@UsedByNative("MaterialKey.cpp")
public boolean hasNormalTexture;
@UsedByNative("MaterialKey.cpp")
public boolean hasOcclusionTexture;
@UsedByNative("MaterialKey.cpp")
public boolean hasEmissiveTexture;
@UsedByNative("MaterialKey.cpp")
public boolean useSpecularGlossiness;
@UsedByNative("MaterialKey.cpp")
public int alphaMode; // 0 = OPAQUE, 1 = MASK, 2 = BLEND
@UsedByNative("MaterialKey.cpp")
public boolean enableDiagnostics;
@UsedByNative("MaterialKey.cpp")
public boolean hasMetallicRoughnessTexture; // piggybacks with specularRoughness
@UsedByNative("MaterialKey.cpp")
public int metallicRoughnessUV; // piggybacks with specularRoughness
@UsedByNative("MaterialKey.cpp")
public int baseColorUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasClearCoatTexture;
@UsedByNative("MaterialKey.cpp")
public int clearCoatUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasClearCoatRoughnessTexture;
@UsedByNative("MaterialKey.cpp")
public int clearCoatRoughnessUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasClearCoatNormalTexture;
@UsedByNative("MaterialKey.cpp")
public int clearCoatNormalUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasClearCoat;
@UsedByNative("MaterialKey.cpp")
public boolean hasTransmission;
@UsedByNative("MaterialKey.cpp")
public boolean hasTextureTransforms;
@UsedByNative("MaterialKey.cpp")
public int emissiveUV;
@UsedByNative("MaterialKey.cpp")
public int aoUV;
@UsedByNative("MaterialKey.cpp")
public int normalUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasTransmissionTexture;
@UsedByNative("MaterialKey.cpp")
public int transmissionUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasSheenColorTexture;
@UsedByNative("MaterialKey.cpp")
public int sheenColorUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasSheenRoughnessTexture;
@UsedByNative("MaterialKey.cpp")
public int sheenRoughnessUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasVolumeThicknessTexture;
@UsedByNative("MaterialKey.cpp")
public int volumeThicknessUV;
@UsedByNative("MaterialKey.cpp")
public boolean hasSheen;
@UsedByNative("MaterialKey.cpp")
public boolean hasIOR;
public MaterialKey() {}
@@ -20,6 +20,7 @@ import com.google.android.filament.Engine;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.Material;
import com.google.android.filament.VertexBuffer;
import com.google.android.filament.proguard.UsedByNative;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -94,6 +95,7 @@ public class UbershaderProvider implements MaterialProvider {
nDestroyMaterials(mNativeObject);
}
@UsedByNative("AssetLoader.cpp")
public long getNativeObject() {
return mNativeObject;
}
+3 -2
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.31.4
VERSION_NAME=1.32.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -22,4 +22,5 @@ android.useAndroidX=true
com.google.android.filament.tools-dir=../../../out/release/filament
com.google.android.filament.dist-dir=../out/android-release/filament
com.google.android.filament.abis=all
#com.google.android.filament.matdbg=true
#com.google.android.filament.matdbg
#com.google.android.filament.matnopt
@@ -356,6 +356,8 @@ class MainActivity : Activity() {
automation.applySettings(modelViewer.engine, json, viewerContent)
modelViewer.view.colorGrading = automation.getColorGrading(modelViewer.engine)
modelViewer.cameraFocalLength = automation.viewerOptions.cameraFocalLength
modelViewer.cameraNear = automation.viewerOptions.cameraNear
modelViewer.cameraFar = automation.viewerOptions.cameraFar
updateRootTransform()
}
@@ -1,3 +1,3 @@
<resources>
<string name="app_name">Image-Based Lighting</string>
<string name="app_name">Material Builder</string>
</resources>
+26 -3
View File
@@ -22,9 +22,11 @@ function print_help {
echo " All (and only) git-ignored files under android/ are deleted."
echo " This is sometimes needed instead of -c (which still misses some clean steps)."
echo " -d"
echo " Enable matdbg and disable material optimization."
echo " Enable matdbg."
echo " -f"
echo " Always invoke CMake before incremental builds."
echo " -g"
echo " Disable material optimization."
echo " -i"
echo " Install build output"
echo " -m"
@@ -56,6 +58,9 @@ function print_help {
echo " When building for Android, also build select sample APKs."
echo " sampleN is an Android sample, e.g., sample-gltf-viewer."
echo " This automatically performs a partial desktop build and install."
echo " -b"
echo " Enable Address and Undefined Behavior Sanitizers (asan/ubsan) for debugging."
echo " This is only for the desktop build."
echo ""
echo "Build types:"
echo " release"
@@ -162,6 +167,11 @@ EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
MATDBG_GRADLE_OPTION=""
MATOPT_OPTION=""
MATOPT_GRADLE_OPTION=""
ASAN_UBSAN_OPTION=""
IOS_BUILD_SIMULATOR=false
BUILD_UNIVERSAL_LIBRARIES=false
@@ -221,6 +231,8 @@ function build_desktop_target {
${SWIFTSHADER_OPTION} \
${EGL_ON_LINUX_OPTION} \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${ASAN_UBSAN_OPTION} \
${deployment_target} \
${architectures} \
../..
@@ -347,6 +359,7 @@ function build_android_target {
-DCMAKE_INSTALL_PREFIX="../android-${lc_target}/filament" \
-DCMAKE_TOOLCHAIN_FILE="../../build/toolchain-${arch}-linux-android.cmake" \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
${VULKAN_ANDROID_OPTION} \
../..
fi
@@ -463,6 +476,7 @@ function build_android {
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
${VULKAN_ANDROID_GRADLE_OPTION} \
${MATDBG_GRADLE_OPTION} \
${MATOPT_GRADLE_OPTION} \
:filament-android:assembleDebug \
:gltfio-android:assembleDebug \
:filament-utils-android:assembleDebug
@@ -510,6 +524,7 @@ function build_android {
-Pcom.google.android.filament.abis=${ABI_GRADLE_OPTION} \
${VULKAN_ANDROID_GRADLE_OPTION} \
${MATDBG_GRADLE_OPTION} \
${MATOPT_GRADLE_OPTION} \
:filament-android:assembleRelease \
:gltfio-android:assembleRelease \
:filament-utils-android:assembleRelease
@@ -576,6 +591,7 @@ function build_ios_target {
-DIOS=1 \
-DCMAKE_TOOLCHAIN_FILE=../../third_party/clang/iOS.cmake \
${MATDBG_OPTION} \
${MATOPT_OPTION} \
../..
fi
@@ -740,7 +756,7 @@ function run_tests {
pushd "$(dirname "$0")" > /dev/null
while getopts ":hacCfijmp:q:uvslwtedk:" opt; do
while getopts ":hacCfijmp:q:uvslwtedk:b" opt; do
case ${opt} in
h)
print_help
@@ -758,12 +774,16 @@ while getopts ":hacCfijmp:q:uvslwtedk:" opt; do
;;
d)
PRINT_MATDBG_HELP=true
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=ON, -DFILAMENT_DISABLE_MATOPT=ON, -DFILAMENT_BUILD_FILAMAT=ON"
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=ON, -DFILAMENT_BUILD_FILAMAT=ON"
MATDBG_GRADLE_OPTION="-Pcom.google.android.filament.matdbg"
;;
f)
ISSUE_CMAKE_ALWAYS=true
;;
g)
MATOPT_OPTION="-DFILAMENT_DISABLE_MATOPT=ON"
MATOPT_GRADLE_OPTION="-Pcom.google.android.filament.matnopt"
;;
i)
INSTALL_COMMAND=install
;;
@@ -863,6 +883,9 @@ while getopts ":hacCfijmp:q:uvslwtedk:" opt; do
BUILD_ANDROID_SAMPLES=true
ANDROID_SAMPLES=$(echo "${OPTARG}" | tr ',' '\n')
;;
b) ASAN_UBSAN_OPTION="-DFILAMENT_ENABLE_ASAN_UBSAN=ON"
echo "Enabled ASAN/UBSAN"
;;
\?)
echo "Invalid option: -${OPTARG}" >&2
echo ""
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash
# version of clang we want to use
GITHUB_CLANG_VERSION=8
GITHUB_CLANG_VERSION=14
# version of CMake to use instead of the default one
CMAKE_VERSION=3.19.5
# version of ninja to use
+48 -8
View File
@@ -1362,6 +1362,11 @@ Description
ALPHA_TO_COVERAGE is enabled for non-translucent views. See the maskThreshold section for more
information.
!!! Note
When `blending` is set to `masked`, alpha to coverage is automatically enabled for the material.
If this behavior is undesirable, refer to the Rasterization: alphaToCoverage section to turn
alpha to coverage off using the `alphaToCoverage` property.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
blending : transparent
@@ -1602,6 +1607,37 @@ fragment {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Rasterization: alphaToCoverage
Type
: `boolean`
Value
: `true` or `false`. Defaults to `false`.
Description
: Enables or disables alpha to coverage. When alpha to coverage is enabled, the coverage of
fragment is derived from its alpha. This property is only meaningful when MSAA is enabled.
Note: setting `blending` to `masked` automatically enables alpha to coverage. If this is not
desired, you can override this behavior by setting alpha to coverage to false as in the
example below.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
material {
name : "Alpha to coverage",
shadingModel : lit,
blending : masked,
alphaToCoverage : false
}
fragment {
void material(inout MaterialInputs material) {
prepareMaterial(material);
material.baseColor = materialParams.albedo;
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Lighting: reflections
Type
@@ -1910,8 +1946,9 @@ struct MaterialVertexInputs {
!!! TIP: worldPosition
To achieve good precision, the `worldPosition` coordinate in the vertex shader is shifted by the
camera position. To get the true world-space position, users can add this to
`getWorldOffset()`.
camera position. To get the true world-space position, users can use
`getUserWorldPosition()`, however be aware that the true world-position might not
be able to fit in a `float` or might be represented with severely reduced precision.
!!! TIP: UV attributes
By default the vertex shader of a material will flip the Y coordinate of the UV attributes
@@ -2264,8 +2301,9 @@ type aliases:
Name | Type | Description
:-----------------------------------|:--------:|:------------------------------------
**getResolution()** | float4 | Dimensions of the view's effective (physical) viewport in pixels: `width`, `height`, `1 / width`, `1 / height`. This might be different from `View::getViewport()` for instance because of added rendering guard-bands.
**getWorldCameraPosition()** | float3 | Position of the camera/eye in world space
**getWorldOffset()** | float3 | The shift required to obtain API-level world space
**getWorldCameraPosition()** | float3 | Position of the camera/eye in world space (see note below)
**getWorldOffset()** | float3 | [deprecated] The shift required to obtain API-level world space. Use getUserWorldPosition() instead
**getUserWorldFromWorldMatrix()** | float4x4 | Matrix that converts from world space to API-level (user) world space.
**getTime()** | float | Current time as a remainder of 1 second. Yields a value between 0 and 1
**getUserTime()** | float4 | Current time in seconds: `time`, `(double)time - time`, `0`, `0`
**getUserTimeMode(float m)** | float | Current time modulo m in seconds
@@ -2275,7 +2313,7 @@ type aliases:
!!! TIP: world space
To achieve good precision, the "world space" in Filament's shading system does not necessarily
match the API-level world space. To obtain the position of the API-level camera, custom
materials can add `getWorldOffset()` to `getWorldCameraPosition()`.
materials can use `getUserWorldFromWorldMatrix()` to transform `getWorldCameraPosition()`.
### Vertex only
@@ -2297,6 +2335,7 @@ The following APIs are only available from the fragment block:
:---------------------------------------|:--------:|:------------------------------------
**getWorldTangentFrame()** | float3x3 | Matrix containing in each column the `tangent` (`frame[0]`), `bi-tangent` (`frame[1]`) and `normal` (`frame[2]`) of the vertex in world space. If the material does not compute a tangent space normal for bump mapping or if the shading is not anisotropic, only the `normal` is valid in this matrix.
**getWorldPosition()** | float3 | Position of the fragment in world space (see note below about world-space)
**getUserWorldPosition()** | float3 | Position of the fragment in API-level (user) world-space (see note below about world-space)
**getWorldViewVector()** | float3 | Normalized vector in world space from the fragment position to the eye
**getWorldNormalVector()** | float3 | Normalized normal in world space, after bump mapping (must be used after `prepareMaterial()`)
**getWorldGeometricNormalVector()** | float3 | Normalized normal in world space, before bump mapping (can be used before `prepareMaterial()`)
@@ -2313,9 +2352,10 @@ The following APIs are only available from the fragment block:
**ycbcrToRgb(float, float2)** | float3 | Converts a luminance and CbCr pair to a sRGB color
**uvToRenderTargetUV(float2)** | float2 | Transforms a UV coordinate to allow sampling from a `RenderTarget` attachment
!!! TIP: world space
To obtain API-level world space coordinates, custom materials should add `getWorldOffset()` to
`getWorldPosition()` (et al).
!!! TIP: world-space
To obtain API-level world-space coordinates, custom materials should use `getUserWorldPosition()`
or use `getUserWorldFromWorldMatrix()`. Note that API-level world-space coordinates should
never or rarely be used because they may not fit in a float3 or have severely reduced precision.
!!! TIP: sampling from render targets
When sampling from a `filament::Texture` that is attached to a `filament::RenderTarget` for
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+145 -145
View File
@@ -44,7 +44,7 @@ fragment {
</pre></div>
<p>Next, invoke <code>matc</code> as follows.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>matc -a opengl -p mobile -o plastic.filamat plastic.mat
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>matc<span style="color: #bbbbbb"> </span>-a<span style="color: #bbbbbb"> </span>opengl<span style="color: #bbbbbb"> </span>-p<span style="color: #bbbbbb"> </span>mobile<span style="color: #bbbbbb"> </span>-o<span style="color: #bbbbbb"> </span>plastic.filamat<span style="color: #bbbbbb"> </span>plastic.mat
</pre></div>
<p>You should now have a material archive in your working directory, which we&#x27;ll use later in the
@@ -53,7 +53,7 @@ tutorial.</p>
<p>Next we&#x27;ll use Filament&#x27;s <code>cmgen</code> tool to consume a HDR environment map in latlong format, and
produce two cubemap files: a mipmapped IBL and a blurry skybox.</p>
<p>Download <a href="//github.com/google/filament/blob/main/third_party/environments/pillars_2k.hdr">pillars_2k.hdr</a>, then invoke the following command in your terminal.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>cmgen -x pillars_2k --format<span style="color: #666666">=</span>ktx --size<span style="color: #666666">=256</span> --extract-blur<span style="color: #666666">=0</span>.1 pillars_2k.hdr
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>cmgen<span style="color: #bbbbbb"> </span>-x<span style="color: #bbbbbb"> </span>pillars_2k<span style="color: #bbbbbb"> </span>--format<span style="color: #666666">=</span>ktx<span style="color: #bbbbbb"> </span>--size<span style="color: #666666">=256</span><span style="color: #bbbbbb"> </span>--extract-blur<span style="color: #666666">=0</span>.1<span style="color: #bbbbbb"> </span>pillars_2k.hdr
</pre></div>
<p>You should now have a <code>pillars_2k</code> folder containing a couple KTX files for the IBL and skybox, as
@@ -61,81 +61,81 @@ well as a text file with spherical harmonics coefficients. You can discard the t
IBL KTX contains these coefficients in its metadata.</p>
<h2>Create JavaScript</h2>
<p>Next, create <code>redball.js</code> with the following content.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>environ<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;pillars_2k&#39;</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibl_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_ibl.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_skybox.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamat_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;plastic.filamat&#39;</span><span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>environ<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;pillars_2k&#39;</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibl_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_ibl.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_skybox.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamat_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;plastic.filamat&#39;</span>
Filament.init([<span style="color: #bbbbbb"> </span>filamat_url,<span style="color: #bbbbbb"> </span>ibl_url,<span style="color: #bbbbbb"> </span>sky_url<span style="color: #bbbbbb"> </span>],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// Create some global aliases to enums for convenience.</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.VertexAttribute<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexAttribute;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.AttributeType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer$AttributeType;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.PrimitiveType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.RenderableManager$PrimitiveType;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.IndexType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndexBuffer$IndexType;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.Fov<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Camera$Fov;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.LightType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.LightManager$Type;<span style="color: #bbbbbb"></span>
Filament.init([<span style="color: #bbbbbb"> </span>filamat_url,<span style="color: #bbbbbb"> </span>ibl_url,<span style="color: #bbbbbb"> </span>sky_url<span style="color: #bbbbbb"> </span>],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// Create some global aliases to enums for convenience.</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.VertexAttribute<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexAttribute;
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.AttributeType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer$AttributeType;
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.PrimitiveType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.RenderableManager$PrimitiveType;
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.IndexType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndexBuffer$IndexType;
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.Fov<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Camera$Fov;
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.LightType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.LightManager$Type;
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// Obtain the canvas DOM object and pass it to the App.</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">document</span>.getElementsByTagName(<span style="color: #BA2121">&#39;canvas&#39;</span>)[<span style="color: #666666">0</span>];<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.app<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>App(canvas);<span style="color: #bbbbbb"></span>
}<span style="color: #bbbbbb"> </span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// Obtain the canvas DOM object and pass it to the App.</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">document</span>.getElementsByTagName(<span style="color: #BA2121">&#39;canvas&#39;</span>)[<span style="color: #666666">0</span>];
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.app<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>App(canvas);
}<span style="color: #bbbbbb"> </span>);
<span style="color: #008000; font-weight: bold">class</span><span style="color: #bbbbbb"> </span>App<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">constructor</span>(canvas)<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>canvas;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Engine.create(canvas);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>scene<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createScene();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">class</span><span style="color: #bbbbbb"> </span>App<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">constructor</span>(canvas)<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>canvas;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Engine.create(canvas);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>scene<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createScene();
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create material</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create sphere</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create lights</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create IBL</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create skybox</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create material</span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create sphere</span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create lights</span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create IBL</span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create skybox</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.swapChain<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createSwapChain();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createRenderer();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createCamera(Filament.EntityManager.get().create());<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createView();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setCamera(<span style="color: #008000; font-weight: bold">this</span>.camera);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setScene(scene);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render.bind(<span style="color: #008000; font-weight: bold">this</span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize.bind(<span style="color: #008000; font-weight: bold">this</span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.addEventListener(<span style="color: #BA2121">&#39;resize&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.swapChain<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createSwapChain();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createRenderer();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createCamera(Filament.EntityManager.get().create());
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createView();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setCamera(<span style="color: #008000; font-weight: bold">this</span>.camera);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setScene(scene);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render.bind(<span style="color: #008000; font-weight: bold">this</span>);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize.bind(<span style="color: #008000; font-weight: bold">this</span>);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.addEventListener(<span style="color: #BA2121">&#39;resize&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>render()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>eye<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">4</span>],<span style="color: #bbbbbb"> </span>center<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>],<span style="color: #bbbbbb"> </span>up<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>];<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>radians<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Date</span>.now()<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">10000</span>;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>vec3.rotateY(eye,<span style="color: #bbbbbb"> </span>eye,<span style="color: #bbbbbb"> </span>center,<span style="color: #bbbbbb"> </span>radians);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.lookAt(eye,<span style="color: #bbbbbb"> </span>center,<span style="color: #bbbbbb"> </span>up);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer.render(<span style="color: #008000; font-weight: bold">this</span>.swapChain,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>render()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>eye<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">4</span>],<span style="color: #bbbbbb"> </span>center<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>],<span style="color: #bbbbbb"> </span>up<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>];
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>radians<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Date</span>.now()<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">10000</span>;
<span style="color: #bbbbbb"> </span>vec3.rotateY(eye,<span style="color: #bbbbbb"> </span>eye,<span style="color: #bbbbbb"> </span>center,<span style="color: #bbbbbb"> </span>radians);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.lookAt(eye,<span style="color: #bbbbbb"> </span>center,<span style="color: #bbbbbb"> </span>up);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer.render(<span style="color: #008000; font-weight: bold">this</span>.swapChain,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>resize()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>dpr<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.devicePixelRatio;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerWidth<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerHeight<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setViewport([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>width,<span style="color: #bbbbbb"> </span>height]);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.setProjectionFov(<span style="color: #666666">45</span>,<span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span>height,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">10.0</span>,<span style="color: #bbbbbb"> </span>Fov.VERTICAL);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>resize()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>dpr<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.devicePixelRatio;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerWidth<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerHeight<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setViewport([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>width,<span style="color: #bbbbbb"> </span>height]);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.setProjectionFov(<span style="color: #666666">45</span>,<span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span>height,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">10.0</span>,<span style="color: #bbbbbb"> </span>Fov.VERTICAL);
<span style="color: #bbbbbb"> </span>}
}
</pre></div>
<p>The above boilerplate should be familiar to you from the previous tutorial, although it loads in a
new set of assets. We also added some animation to the camera.</p>
<p>Next let&#x27;s create a material instance from the package that we built at the beginning the tutorial.
Replace the <strong>create material</strong> comment with the following snippet.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>material<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createMaterial(filamat_url);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>matinstance<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>material.createInstance();<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>material<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createMaterial(filamat_url);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>matinstance<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>material.createInstance();
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>red<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0.8</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.0</span>];<span style="color: #bbbbbb"></span>
matinstance.setColor3Parameter(<span style="color: #BA2121">&#39;baseColor&#39;</span>,<span style="color: #bbbbbb"> </span>Filament.RgbType.sRGB,<span style="color: #bbbbbb"> </span>red);<span style="color: #bbbbbb"></span>
matinstance.setFloatParameter(<span style="color: #BA2121">&#39;roughness&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.5</span>);<span style="color: #bbbbbb"></span>
matinstance.setFloatParameter(<span style="color: #BA2121">&#39;clearCoat&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>);<span style="color: #bbbbbb"></span>
matinstance.setFloatParameter(<span style="color: #BA2121">&#39;clearCoatRoughness&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.3</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>red<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0.8</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.0</span>];
matinstance.setColor3Parameter(<span style="color: #BA2121">&#39;baseColor&#39;</span>,<span style="color: #bbbbbb"> </span>Filament.RgbType.sRGB,<span style="color: #bbbbbb"> </span>red);
matinstance.setFloatParameter(<span style="color: #BA2121">&#39;roughness&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.5</span>);
matinstance.setFloatParameter(<span style="color: #BA2121">&#39;clearCoat&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>);
matinstance.setFloatParameter(<span style="color: #BA2121">&#39;clearCoatRoughness&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.3</span>);
</pre></div>
<p>The next step is to create a renderable for the sphere. To help with this, we&#x27;ll use the <code>IcoSphere</code>
@@ -149,33 +149,33 @@ as quaternions.</li>
</ul>
<p>Let&#x27;s go ahead use these arrays to build the vertex buffer and index buffer. Replace <strong>create
sphere</strong> with the following snippet.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>renderable<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();<span style="color: #bbbbbb"></span>
scene.addEntity(renderable);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>renderable<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();
scene.addEntity(renderable);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>icosphere<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.IcoSphere(<span style="color: #666666">5</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>icosphere<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.IcoSphere(<span style="color: #666666">5</span>);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>vb<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.vertexCount(icosphere.vertices.length<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.bufferCount(<span style="color: #666666">2</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.POSITION,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>AttributeType.FLOAT3,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.TANGENTS,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>AttributeType.SHORT4,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.normalized(VertexAttribute.TANGENTS)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>vb<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer.Builder()
<span style="color: #bbbbbb"> </span>.vertexCount(icosphere.vertices.length<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>)
<span style="color: #bbbbbb"> </span>.bufferCount(<span style="color: #666666">2</span>)
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.POSITION,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>AttributeType.FLOAT3,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>)
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.TANGENTS,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>AttributeType.SHORT4,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>)
<span style="color: #bbbbbb"> </span>.normalized(VertexAttribute.TANGENTS)
<span style="color: #bbbbbb"> </span>.build(engine);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ib<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndexBuffer.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.indexCount(icosphere.triangles.length)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.bufferType(IndexType.USHORT)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ib<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndexBuffer.Builder()
<span style="color: #bbbbbb"> </span>.indexCount(icosphere.triangles.length)
<span style="color: #bbbbbb"> </span>.bufferType(IndexType.USHORT)
<span style="color: #bbbbbb"> </span>.build(engine);
vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>icosphere.vertices);<span style="color: #bbbbbb"></span>
vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>icosphere.tangents);<span style="color: #bbbbbb"></span>
ib.setBuffer(engine,<span style="color: #bbbbbb"> </span>icosphere.triangles);<span style="color: #bbbbbb"></span>
vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>icosphere.vertices);
vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>icosphere.tangents);
ib.setBuffer(engine,<span style="color: #bbbbbb"> </span>icosphere.triangles);
Filament.RenderableManager.Builder(<span style="color: #666666">1</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.boundingBox({<span style="color: #bbbbbb"> </span>center<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>],<span style="color: #bbbbbb"> </span>halfExtent<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>]<span style="color: #bbbbbb"> </span>})<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.material(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>matinstance)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.geometry(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>PrimitiveType.TRIANGLES,<span style="color: #bbbbbb"> </span>vb,<span style="color: #bbbbbb"> </span>ib)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span>renderable);<span style="color: #bbbbbb"></span>
Filament.RenderableManager.Builder(<span style="color: #666666">1</span>)
<span style="color: #bbbbbb"> </span>.boundingBox({<span style="color: #bbbbbb"> </span>center<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>],<span style="color: #bbbbbb"> </span>halfExtent<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>]<span style="color: #bbbbbb"> </span>})
<span style="color: #bbbbbb"> </span>.material(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>matinstance)
<span style="color: #bbbbbb"> </span>.geometry(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>PrimitiveType.TRIANGLES,<span style="color: #bbbbbb"> </span>vb,<span style="color: #bbbbbb"> </span>ib)
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span>renderable);
</pre></div>
<p>At this point, the app is rendering a sphere, but it is black so it doesn&#x27;t show up. To prove that
@@ -185,94 +185,94 @@ did in the first tutorial.</p>
<p>In this section we will create some directional light sources, as well as an image-based light (IBL)
defined by one of the KTX files we built at the start of the demo. First, replace the <strong>create
lights</strong> comment with the following snippet.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sunlight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();<span style="color: #bbbbbb"></span>
scene.addEntity(sunlight);<span style="color: #bbbbbb"></span>
Filament.LightManager.Builder(LightType.SUN)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.color([<span style="color: #666666">0.98</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.92</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.89</span>])<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.intensity(<span style="color: #666666">110000.0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.direction([<span style="color: #666666">0.6</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-0.8</span>])<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.sunAngularRadius(<span style="color: #666666">1.9</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.sunHaloSize(<span style="color: #666666">10.0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.sunHaloFalloff(<span style="color: #666666">80.0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span>sunlight);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sunlight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();
scene.addEntity(sunlight);
Filament.LightManager.Builder(LightType.SUN)
<span style="color: #bbbbbb"> </span>.color([<span style="color: #666666">0.98</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.92</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.89</span>])
<span style="color: #bbbbbb"> </span>.intensity(<span style="color: #666666">110000.0</span>)
<span style="color: #bbbbbb"> </span>.direction([<span style="color: #666666">0.6</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-0.8</span>])
<span style="color: #bbbbbb"> </span>.sunAngularRadius(<span style="color: #666666">1.9</span>)
<span style="color: #bbbbbb"> </span>.sunHaloSize(<span style="color: #666666">10.0</span>)
<span style="color: #bbbbbb"> </span>.sunHaloFalloff(<span style="color: #666666">80.0</span>)
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span>sunlight);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>backlight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();<span style="color: #bbbbbb"></span>
scene.addEntity(backlight);<span style="color: #bbbbbb"></span>
Filament.LightManager.Builder(LightType.DIRECTIONAL)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.direction([<span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>])<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.intensity(<span style="color: #666666">50000.0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span>backlight);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>backlight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();
scene.addEntity(backlight);
Filament.LightManager.Builder(LightType.DIRECTIONAL)
<span style="color: #bbbbbb"> </span>.direction([<span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>])
<span style="color: #bbbbbb"> </span>.intensity(<span style="color: #666666">50000.0</span>)
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span>backlight);
</pre></div>
<p>The <code>SUN</code> light source is similar to the <code>DIRECTIONAL</code> light source, but has some extra
parameters because Filament will automatically draw a disk into the skybox.</p>
<p>Next we need to create an <code>IndirectLight</code> object from the KTX IBL. One way of doing this is the
following (don&#x27;t type this out, there&#x27;s an easier way).</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>format<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelDataFormat.RGB;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>datatype<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelDataType.UINT_10F_11F_11F_REV;<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>format<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelDataFormat.RGB;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>datatype<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelDataType.UINT_10F_11F_11F_REV;
<span style="color: #3D7B7B; font-style: italic">// Create a Texture object for the mipmapped cubemap.</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibl_package<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Buffer(Filament.assets[ibl_url]);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>iblktx<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.Ktx1Bundle(ibl_package);<span style="color: #bbbbbb"></span>
<span style="color: #3D7B7B; font-style: italic">// Create a Texture object for the mipmapped cubemap.</span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibl_package<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Buffer(Filament.assets[ibl_url]);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>iblktx<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.Ktx1Bundle(ibl_package);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibltex<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Texture.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.width(iblktx.info().pixelWidth)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.height(iblktx.info().pixelHeight)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.levels(iblktx.getNumMipLevels())<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.format(Filament.Texture$InternalFormat.RGBA8)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibltex<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Texture.Builder()
<span style="color: #bbbbbb"> </span>.width(iblktx.info().pixelWidth)
<span style="color: #bbbbbb"> </span>.height(iblktx.info().pixelHeight)
<span style="color: #bbbbbb"> </span>.levels(iblktx.getNumMipLevels())
<span style="color: #bbbbbb"> </span>.sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
<span style="color: #bbbbbb"> </span>.format(Filament.Texture$InternalFormat.RGBA8)
<span style="color: #bbbbbb"> </span>.build(engine);
<span style="color: #008000; font-weight: bold">for</span><span style="color: #bbbbbb"> </span>(<span style="color: #008000; font-weight: bold">let</span><span style="color: #bbbbbb"> </span>level<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>;<span style="color: #bbbbbb"> </span>level<span style="color: #bbbbbb"> </span><span style="color: #666666">&lt;</span><span style="color: #bbbbbb"> </span>iblktx.getNumMipLevels();<span style="color: #bbbbbb"> </span><span style="color: #666666">++</span>level)<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>uint8array<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>iblktx.getCubeBlob(level).getBytes();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>pixelbuffer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelBuffer(uint8array,<span style="color: #bbbbbb"> </span>format,<span style="color: #bbbbbb"> </span>datatype);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>ibltex.setImageCube(engine,<span style="color: #bbbbbb"> </span>level,<span style="color: #bbbbbb"> </span>pixelbuffer);<span style="color: #bbbbbb"></span>
}<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">for</span><span style="color: #bbbbbb"> </span>(<span style="color: #008000; font-weight: bold">let</span><span style="color: #bbbbbb"> </span>level<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>;<span style="color: #bbbbbb"> </span>level<span style="color: #bbbbbb"> </span><span style="color: #666666">&lt;</span><span style="color: #bbbbbb"> </span>iblktx.getNumMipLevels();<span style="color: #bbbbbb"> </span><span style="color: #666666">++</span>level)<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>uint8array<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>iblktx.getCubeBlob(level).getBytes();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>pixelbuffer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelBuffer(uint8array,<span style="color: #bbbbbb"> </span>format,<span style="color: #bbbbbb"> </span>datatype);
<span style="color: #bbbbbb"> </span>ibltex.setImageCube(engine,<span style="color: #bbbbbb"> </span>level,<span style="color: #bbbbbb"> </span>pixelbuffer);
}
<span style="color: #3D7B7B; font-style: italic">// Parse the spherical harmonics metadata.</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>shstring<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>iblktx.getMetadata(<span style="color: #BA2121">&#39;sh&#39;</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>shfloats<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>shstring.split(<span style="color: #A45A77">/\s/</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">9</span><span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>).map(<span style="color: #008000">parseFloat</span>);<span style="color: #bbbbbb"></span>
<span style="color: #3D7B7B; font-style: italic">// Parse the spherical harmonics metadata.</span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>shstring<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>iblktx.getMetadata(<span style="color: #BA2121">&#39;sh&#39;</span>);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>shfloats<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>shstring.split(<span style="color: #A45A77">/\s/</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">9</span><span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>).map(<span style="color: #008000">parseFloat</span>);
<span style="color: #3D7B7B; font-style: italic">// Build the IBL object and insert it into the scene.</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>indirectLight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndirectLight.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.reflections(ibltex)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.irradianceSh(<span style="color: #666666">3</span>,<span style="color: #bbbbbb"> </span>shfloats)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.intensity(<span style="color: #666666">50000.0</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<span style="color: #3D7B7B; font-style: italic">// Build the IBL object and insert it into the scene.</span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>indirectLight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndirectLight.Builder()
<span style="color: #bbbbbb"> </span>.reflections(ibltex)
<span style="color: #bbbbbb"> </span>.irradianceSh(<span style="color: #666666">3</span>,<span style="color: #bbbbbb"> </span>shfloats)
<span style="color: #bbbbbb"> </span>.intensity(<span style="color: #666666">50000.0</span>)
<span style="color: #bbbbbb"> </span>.build(engine);
scene.setIndirectLight(indirectLight);<span style="color: #bbbbbb"></span>
scene.setIndirectLight(indirectLight);
</pre></div>
<p>Filament provides a JavaScript utility to make this simpler,
simply replace the <strong>create IBL</strong> comment with the following snippet.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>indirectLight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createIblFromKtx1(ibl_url);<span style="color: #bbbbbb"></span>
indirectLight.setIntensity(<span style="color: #666666">50000</span>);<span style="color: #bbbbbb"></span>
scene.setIndirectLight(indirectLight);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>indirectLight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createIblFromKtx1(ibl_url);
indirectLight.setIntensity(<span style="color: #666666">50000</span>);
scene.setIndirectLight(indirectLight);
</pre></div>
<h2>Add background</h2>
<p>At this point you can run the demo and you should see a red plastic ball against a black background.
Without a skybox, the reflections on the ball are not representative of its surroundings.
Here&#x27;s one way to create a texture for the skybox:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_package<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Buffer(Filament.assets[sky_url]);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>skyktx<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.Ktx1Bundle(sky_package);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>skytex<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Texture.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.width(skyktx.info().pixelWidth)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.height(skyktx.info().pixelHeight)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.levels(<span style="color: #666666">1</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.format(Filament.Texture$InternalFormat.RGBA8)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_package<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Buffer(Filament.assets[sky_url]);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>skyktx<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.Ktx1Bundle(sky_package);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>skytex<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Texture.Builder()
<span style="color: #bbbbbb"> </span>.width(skyktx.info().pixelWidth)
<span style="color: #bbbbbb"> </span>.height(skyktx.info().pixelHeight)
<span style="color: #bbbbbb"> </span>.levels(<span style="color: #666666">1</span>)
<span style="color: #bbbbbb"> </span>.sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
<span style="color: #bbbbbb"> </span>.format(Filament.Texture$InternalFormat.RGBA8)
<span style="color: #bbbbbb"> </span>.build(engine);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>uint8array<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>skyktx.getCubeBlob(<span style="color: #666666">0</span>).getBytes();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>pixelbuffer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelBuffer(uint8array,<span style="color: #bbbbbb"> </span>format,<span style="color: #bbbbbb"> </span>datatype);<span style="color: #bbbbbb"></span>
skytex.setImageCube(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>pixelbuffer);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>uint8array<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>skyktx.getCubeBlob(<span style="color: #666666">0</span>).getBytes();
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>pixelbuffer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.PixelBuffer(uint8array,<span style="color: #bbbbbb"> </span>format,<span style="color: #bbbbbb"> </span>datatype);
skytex.setImageCube(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>pixelbuffer);
</pre></div>
<p>Filament provides a Javascript utility to make this easier.
Replace <strong>create skybox</strong> with the following.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>skybox<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createSkyFromKtx1(sky_url);<span style="color: #bbbbbb"></span>
scene.setSkybox(skybox);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>skybox<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createSkyFromKtx1(sky_url);
scene.setSkybox(skybox);
</pre></div>
<p>That&#x27;s it, we now have a shiny red ball floating in an environment! The complete JavaScript file is
+108 -108
View File
@@ -15,7 +15,7 @@ we&#x27;ll also be using <code>filamesh</code> and <code>mipgen</code>.</p>
<p>Filament does not have an asset loading system, but it does provide a binary mesh format
called <code>filamesh</code> for simple use cases. Let&#x27;s create a compressed filamesh file for suzanne by
converting <a href="https://github.com/google/filament/blob/main/assets/models/monkey/monkey.obj">this OBJ file</a>:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>filamesh --compress monkey.obj suzanne.filamesh
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>filamesh<span style="color: #bbbbbb"> </span>--compress<span style="color: #bbbbbb"> </span>monkey.obj<span style="color: #bbbbbb"> </span>suzanne.filamesh
</pre></div>
<h2>Create mipmapped textures</h2>
@@ -23,26 +23,26 @@ converting <a href="https://github.com/google/filament/blob/main/assets/models/m
non-compressed variants for each texture, since not all platforms support the same compression
formats. First copy over the PNG files from the <a href="https://github.com/google/filament/blob/main/assets/models/monkey">monkey folder</a>, then do:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic"># Create mipmaps for base color and two compressed variants.</span>
mipgen albedo.png albedo.ktx
mipgen --compression<span style="color: #666666">=</span>astc_fast_ldr_4x4 albedo.png albedo_astc.ktx
mipgen --compression<span style="color: #666666">=</span>s3tc_rgb_dxt1 albedo.png albedo_s3tc_srgb.ktx
mipgen<span style="color: #bbbbbb"> </span>albedo.png<span style="color: #bbbbbb"> </span>albedo.ktx
mipgen<span style="color: #bbbbbb"> </span>--compression<span style="color: #666666">=</span>astc_fast_ldr_4x4<span style="color: #bbbbbb"> </span>albedo.png<span style="color: #bbbbbb"> </span>albedo_astc.ktx
mipgen<span style="color: #bbbbbb"> </span>--compression<span style="color: #666666">=</span>s3tc_rgb_dxt1<span style="color: #bbbbbb"> </span>albedo.png<span style="color: #bbbbbb"> </span>albedo_s3tc_srgb.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the normal map and a compressed variant.</span>
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear normal.png normal.ktx
mipgen --strip-alpha --kernel<span style="color: #666666">=</span>NORMALS --linear --compression<span style="color: #666666">=</span>etc_rgb8_normalxyz_40 <span style="color: #AA5D1F; font-weight: bold">\</span>
normal.png normal_etc.ktx
mipgen<span style="color: #bbbbbb"> </span>--strip-alpha<span style="color: #bbbbbb"> </span>--kernel<span style="color: #666666">=</span>NORMALS<span style="color: #bbbbbb"> </span>--linear<span style="color: #bbbbbb"> </span>normal.png<span style="color: #bbbbbb"> </span>normal.ktx
mipgen<span style="color: #bbbbbb"> </span>--strip-alpha<span style="color: #bbbbbb"> </span>--kernel<span style="color: #666666">=</span>NORMALS<span style="color: #bbbbbb"> </span>--linear<span style="color: #bbbbbb"> </span>--compression<span style="color: #666666">=</span>etc_rgb8_normalxyz_40<span style="color: #bbbbbb"> </span><span style="color: #AA5D1F; font-weight: bold">\</span>
<span style="color: #bbbbbb"> </span>normal.png<span style="color: #bbbbbb"> </span>normal_etc.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component roughness map and a compressed variant.</span>
mipgen --grayscale roughness.png roughness.ktx
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 roughness.png roughness_etc.ktx
mipgen<span style="color: #bbbbbb"> </span>--grayscale<span style="color: #bbbbbb"> </span>roughness.png<span style="color: #bbbbbb"> </span>roughness.ktx
mipgen<span style="color: #bbbbbb"> </span>--grayscale<span style="color: #bbbbbb"> </span>--compression<span style="color: #666666">=</span>etc_r11_numeric_40<span style="color: #bbbbbb"> </span>roughness.png<span style="color: #bbbbbb"> </span>roughness_etc.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component metallic map and a compressed variant.</span>
mipgen --grayscale metallic.png metallic.ktx
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 metallic.png metallic_etc.ktx
mipgen<span style="color: #bbbbbb"> </span>--grayscale<span style="color: #bbbbbb"> </span>metallic.png<span style="color: #bbbbbb"> </span>metallic.ktx
mipgen<span style="color: #bbbbbb"> </span>--grayscale<span style="color: #bbbbbb"> </span>--compression<span style="color: #666666">=</span>etc_r11_numeric_40<span style="color: #bbbbbb"> </span>metallic.png<span style="color: #bbbbbb"> </span>metallic_etc.ktx
<span style="color: #3D7B7B; font-style: italic"># Create mipmaps for the single-component occlusion map and a compressed variant.</span>
mipgen --grayscale ao.png ao.ktx
mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_numeric_40 ao.png ao_etc.ktx
mipgen<span style="color: #bbbbbb"> </span>--grayscale<span style="color: #bbbbbb"> </span>ao.png<span style="color: #bbbbbb"> </span>ao.ktx
mipgen<span style="color: #bbbbbb"> </span>--grayscale<span style="color: #bbbbbb"> </span>--compression<span style="color: #666666">=</span>etc_r11_numeric_40<span style="color: #bbbbbb"> </span>ao.png<span style="color: #bbbbbb"> </span>ao_etc.ktx
</pre></div>
<p>For more information on mipgen&#x27;s arguments and supported formats, do <code>mipgen --help</code>.</p>
@@ -50,12 +50,12 @@ mipgen --grayscale --compression<span style="color: #666666">=</span>etc_r11_num
<h2>Bake environment map</h2>
<p>Much like the <a href="tutorial_redball.html">previous tutorial</a> we need to use Filament&#x27;s <code>cmgen</code> tool to produce cubemap files.</p>
<p>Download <a href="//github.com/google/filament/blob/main/third_party/environments/venetian_crossroads_2k.hdr">venetian_crossroads_2k.hdr</a>, then invoke the following commands in your terminal.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>cmgen -x . --format<span style="color: #666666">=</span>ktx --size<span style="color: #666666">=64</span> --extract-blur<span style="color: #666666">=0</span>.1 venetian_crossroads_2k.hdr
<span style="color: #008000">cd</span> venetian* ; mv venetian*_ibl.ktx venetian_crossroads_2k_skybox_tiny.ktx ; <span style="color: #008000">cd</span> -
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>cmgen<span style="color: #bbbbbb"> </span>-x<span style="color: #bbbbbb"> </span>.<span style="color: #bbbbbb"> </span>--format<span style="color: #666666">=</span>ktx<span style="color: #bbbbbb"> </span>--size<span style="color: #666666">=64</span><span style="color: #bbbbbb"> </span>--extract-blur<span style="color: #666666">=0</span>.1<span style="color: #bbbbbb"> </span>venetian_crossroads_2k.hdr
<span style="color: #008000">cd</span><span style="color: #bbbbbb"> </span>venetian*<span style="color: #bbbbbb"> </span>;<span style="color: #bbbbbb"> </span>mv<span style="color: #bbbbbb"> </span>venetian*_ibl.ktx<span style="color: #bbbbbb"> </span>venetian_crossroads_2k_skybox_tiny.ktx<span style="color: #bbbbbb"> </span>;<span style="color: #bbbbbb"> </span><span style="color: #008000">cd</span><span style="color: #bbbbbb"> </span>-
cmgen -x . --format<span style="color: #666666">=</span>ktx --size<span style="color: #666666">=256</span> --extract-blur<span style="color: #666666">=0</span>.1 venetian_crossroads_2k.hdr
cmgen -x . --format<span style="color: #666666">=</span>ktx --size<span style="color: #666666">=256</span> --extract-blur<span style="color: #666666">=0</span>.1 venetian_crossroads_2k.hdr
cmgen -x . --format<span style="color: #666666">=</span>ktx --size<span style="color: #666666">=256</span> --extract-blur<span style="color: #666666">=0</span>.1 venetian_crossroads_2k.hdr
cmgen<span style="color: #bbbbbb"> </span>-x<span style="color: #bbbbbb"> </span>.<span style="color: #bbbbbb"> </span>--format<span style="color: #666666">=</span>ktx<span style="color: #bbbbbb"> </span>--size<span style="color: #666666">=256</span><span style="color: #bbbbbb"> </span>--extract-blur<span style="color: #666666">=0</span>.1<span style="color: #bbbbbb"> </span>venetian_crossroads_2k.hdr
cmgen<span style="color: #bbbbbb"> </span>-x<span style="color: #bbbbbb"> </span>.<span style="color: #bbbbbb"> </span>--format<span style="color: #666666">=</span>ktx<span style="color: #bbbbbb"> </span>--size<span style="color: #666666">=256</span><span style="color: #bbbbbb"> </span>--extract-blur<span style="color: #666666">=0</span>.1<span style="color: #bbbbbb"> </span>venetian_crossroads_2k.hdr
cmgen<span style="color: #bbbbbb"> </span>-x<span style="color: #bbbbbb"> </span>.<span style="color: #bbbbbb"> </span>--format<span style="color: #666666">=</span>ktx<span style="color: #bbbbbb"> </span>--size<span style="color: #666666">=256</span><span style="color: #bbbbbb"> </span>--extract-blur<span style="color: #666666">=0</span>.1<span style="color: #bbbbbb"> </span>venetian_crossroads_2k.hdr
</pre></div>
<h2>Define textured material</h2>
@@ -91,7 +91,7 @@ fragment {
</pre></div>
<p>Next, invoke <code>matc</code> as follows.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>matc -a opengl -p mobile -o textured.filamat textured.mat
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>matc<span style="color: #bbbbbb"> </span>-a<span style="color: #bbbbbb"> </span>opengl<span style="color: #bbbbbb"> </span>-p<span style="color: #bbbbbb"> </span>mobile<span style="color: #bbbbbb"> </span>-o<span style="color: #bbbbbb"> </span>textured.filamat<span style="color: #bbbbbb"> </span>textured.mat
</pre></div>
<p>You should now have a material archive in your working directory. For the suzanne asset, the normal
@@ -101,62 +101,62 @@ materials, consult the official document describing the <a href="https://google.
<p>Create a text file called <code>suzanne.html</code> and copy over the HTML that we used in the <a href="tutorial_redball.html">previous
tutorial</a>. Change the last script tag from <code>redball.js</code> to <code>suzanne.js</code>. Next, create <code>suzanne.js</code>
with the following content.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic">// TODO: declare asset URLs</span><span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic">// TODO: declare asset URLs</span>
Filament.init([<span style="color: #bbbbbb"> </span>filamat_url,<span style="color: #bbbbbb"> </span>filamesh_url,<span style="color: #bbbbbb"> </span>sky_small_url,<span style="color: #bbbbbb"> </span>ibl_url<span style="color: #bbbbbb"> </span>],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.app<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>App(<span style="color: #008000">document</span>.getElementsByTagName(<span style="color: #BA2121">&#39;canvas&#39;</span>)[<span style="color: #666666">0</span>]);<span style="color: #bbbbbb"></span>
});<span style="color: #bbbbbb"></span>
Filament.init([<span style="color: #bbbbbb"> </span>filamat_url,<span style="color: #bbbbbb"> </span>filamesh_url,<span style="color: #bbbbbb"> </span>sky_small_url,<span style="color: #bbbbbb"> </span>ibl_url<span style="color: #bbbbbb"> </span>],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.app<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>App(<span style="color: #008000">document</span>.getElementsByTagName(<span style="color: #BA2121">&#39;canvas&#39;</span>)[<span style="color: #666666">0</span>]);
});
<span style="color: #008000; font-weight: bold">class</span><span style="color: #bbbbbb"> </span>App<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">constructor</span>(canvas)<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>canvas;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Engine.create(canvas);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.scene<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createScene();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">class</span><span style="color: #bbbbbb"> </span>App<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">constructor</span>(canvas)<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>canvas;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Engine.create(canvas);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.scene<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createScene();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>material<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createMaterial(filamat_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>material.createInstance();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>material<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createMaterial(filamat_url);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>material.createInstance();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamesh<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.loadFilamesh(filamesh_url,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.suzanne<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>filamesh.renderable;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamesh<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.loadFilamesh(filamesh_url,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.suzanne<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>filamesh.renderable;
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create sky box and IBL</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: initialize gltumble</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: fetch larger assets</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create sky box and IBL</span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: initialize gltumble</span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: fetch larger assets</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.swapChain<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createSwapChain();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createRenderer();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createCamera(Filament.EntityManager.get().create());<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createView();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setCamera(<span style="color: #008000; font-weight: bold">this</span>.camera);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setScene(<span style="color: #008000; font-weight: bold">this</span>.scene);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render.bind(<span style="color: #008000; font-weight: bold">this</span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize.bind(<span style="color: #008000; font-weight: bold">this</span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.addEventListener(<span style="color: #BA2121">&#39;resize&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.swapChain<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createSwapChain();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createRenderer();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createCamera(Filament.EntityManager.get().create());
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createView();
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setCamera(<span style="color: #008000; font-weight: bold">this</span>.camera);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setScene(<span style="color: #008000; font-weight: bold">this</span>.scene);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render.bind(<span style="color: #008000; font-weight: bold">this</span>);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize.bind(<span style="color: #008000; font-weight: bold">this</span>);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.addEventListener(<span style="color: #BA2121">&#39;resize&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>eye<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">4</span>],<span style="color: #bbbbbb"> </span>center<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>],<span style="color: #bbbbbb"> </span>up<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>];<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.lookAt(eye,<span style="color: #bbbbbb"> </span>center,<span style="color: #bbbbbb"> </span>up);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>eye<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">4</span>],<span style="color: #bbbbbb"> </span>center<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>],<span style="color: #bbbbbb"> </span>up<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>];
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.lookAt(eye,<span style="color: #bbbbbb"> </span>center,<span style="color: #bbbbbb"> </span>up);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize();<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize();
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>render()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: apply gltumble matrix</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer.render(<span style="color: #008000; font-weight: bold">this</span>.swapChain,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>render()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: apply gltumble matrix</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.renderer.render(<span style="color: #008000; font-weight: bold">this</span>.swapChain,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>resize()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>dpr<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.devicePixelRatio;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerWidth<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerHeight<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setViewport([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>width,<span style="color: #bbbbbb"> </span>height]);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>resize()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>dpr<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.devicePixelRatio;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerWidth<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerHeight<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view.setViewport([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>width,<span style="color: #bbbbbb"> </span>height]);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>aspect<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span>height;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>Fov<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Camera$Fov,<span style="color: #bbbbbb"> </span>fov<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>aspect<span style="color: #bbbbbb"> </span><span style="color: #666666">&lt;</span><span style="color: #bbbbbb"> </span><span style="color: #666666">1</span><span style="color: #bbbbbb"> </span><span style="color: #666666">?</span><span style="color: #bbbbbb"> </span>Fov.HORIZONTAL<span style="color: #bbbbbb"> </span><span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>Fov.VERTICAL;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.setProjectionFov(<span style="color: #666666">45</span>,<span style="color: #bbbbbb"> </span>aspect,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">10.0</span>,<span style="color: #bbbbbb"> </span>fov);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>aspect<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span>height;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>Fov<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Camera$Fov,<span style="color: #bbbbbb"> </span>fov<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>aspect<span style="color: #bbbbbb"> </span><span style="color: #666666">&lt;</span><span style="color: #bbbbbb"> </span><span style="color: #666666">1</span><span style="color: #bbbbbb"> </span><span style="color: #666666">?</span><span style="color: #bbbbbb"> </span>Fov.HORIZONTAL<span style="color: #bbbbbb"> </span><span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>Fov.VERTICAL;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.camera.setProjectionFov(<span style="color: #666666">45</span>,<span style="color: #bbbbbb"> </span>aspect,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">10.0</span>,<span style="color: #bbbbbb"> </span>fov);
<span style="color: #bbbbbb"> </span>}
}
</pre></div>
<p>Our app will only require a subset of assets to be present for <code>App</code> construction. We&#x27;ll download
@@ -172,29 +172,29 @@ string -- which might be empty.</p>
<p>In our case, we know that our web server will have <code>astc</code> and <code>s3tc</code> variants for albedo, and <code>etc</code>
variants for the other textures. The uncompressed variants (empty string) are always available as a
last resort. Go ahead and replace the <strong>declare asset URLs</strong> comment with the following snippet.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo_suffix<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.getSupportedFormatSuffix(<span style="color: #BA2121">&#39;astc s3tc_srgb&#39;</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>texture_suffix<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.getSupportedFormatSuffix(<span style="color: #BA2121">&#39;etc&#39;</span>);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo_suffix<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.getSupportedFormatSuffix(<span style="color: #BA2121">&#39;astc s3tc_srgb&#39;</span>);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>texture_suffix<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.getSupportedFormatSuffix(<span style="color: #BA2121">&#39;etc&#39;</span>);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>environ<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;venetian_crossroads_2k&#39;</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibl_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_ibl.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_small_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_skybox_tiny.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_large_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_skybox.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`albedo</span><span style="color: #A45A77; font-weight: bold">${</span>albedo_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`ao</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`metallic</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`normal</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`roughness</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamat_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;textured.filamat&#39;</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamesh_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;suzanne.filamesh&#39;</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>environ<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;venetian_crossroads_2k&#39;</span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ibl_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_ibl.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_small_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_skybox_tiny.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sky_large_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">/</span><span style="color: #A45A77; font-weight: bold">${</span>environ<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">_skybox.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`albedo</span><span style="color: #A45A77; font-weight: bold">${</span>albedo_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`ao</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`metallic</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`normal</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">`roughness</span><span style="color: #A45A77; font-weight: bold">${</span>texture_suffix<span style="color: #A45A77; font-weight: bold">}</span><span style="color: #BA2121">.ktx`</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamat_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;textured.filamat&#39;</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>filamesh_url<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #BA2121">&#39;suzanne.filamesh&#39;</span>;
</pre></div>
<h2>Create skybox and IBL</h2>
<p>Next, let&#x27;s create the low-resolution skybox and IBL in the <code>App</code> constructor.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.skybox<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createSkyFromKtx1(sky_small_url);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.scene.setSkybox(<span style="color: #008000; font-weight: bold">this</span>.skybox);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.indirectLight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createIblFromKtx1(ibl_url);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.indirectLight.setIntensity(<span style="color: #666666">100000</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.scene.setIndirectLight(<span style="color: #008000; font-weight: bold">this</span>.indirectLight);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.skybox<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createSkyFromKtx1(sky_small_url);
<span style="color: #008000; font-weight: bold">this</span>.scene.setSkybox(<span style="color: #008000; font-weight: bold">this</span>.skybox);
<span style="color: #008000; font-weight: bold">this</span>.indirectLight<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createIblFromKtx1(ibl_url);
<span style="color: #008000; font-weight: bold">this</span>.indirectLight.setIntensity(<span style="color: #666666">100000</span>);
<span style="color: #008000; font-weight: bold">this</span>.scene.setIndirectLight(<span style="color: #008000; font-weight: bold">this</span>.indirectLight);
</pre></div>
<p>This allows users to see a reasonable background fairly quickly, before larger assets have finished
@@ -206,31 +206,31 @@ when the assets have finished downloading.</p>
<p>In our callback, we&#x27;ll make several <code>setTextureParameter</code> calls on the material instance, then we&#x27;ll
recreate the skybox using a higher-resolution texture. As a last step we unhide the renderable that
was created in the app constructor.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>Filament.fetch([sky_large_url,<span style="color: #bbbbbb"> </span>albedo_url,<span style="color: #bbbbbb"> </span>roughness_url,<span style="color: #bbbbbb"> </span>metallic_url,<span style="color: #bbbbbb"> </span>normal_url,<span style="color: #bbbbbb"> </span>ao_url],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(albedo_url,<span style="color: #bbbbbb"> </span>{srgb<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(roughness_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(metallic_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(normal_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(ao_url);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>Filament.fetch([sky_large_url,<span style="color: #bbbbbb"> </span>albedo_url,<span style="color: #bbbbbb"> </span>roughness_url,<span style="color: #bbbbbb"> </span>metallic_url,<span style="color: #bbbbbb"> </span>normal_url,<span style="color: #bbbbbb"> </span>ao_url],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>albedo<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(albedo_url,<span style="color: #bbbbbb"> </span>{srgb<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>roughness<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(roughness_url);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>metallic<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(metallic_url);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>normal<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(normal_url);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>ao<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createTextureFromKtx1(ao_url);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sampler<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.TextureSampler(<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>Filament.MinFilter.LINEAR_MIPMAP_LINEAR,<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>Filament.MagFilter.LINEAR,<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>Filament.WrapMode.CLAMP_TO_EDGE);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>sampler<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Filament.TextureSampler(
<span style="color: #bbbbbb"> </span>Filament.MinFilter.LINEAR_MIPMAP_LINEAR,
<span style="color: #bbbbbb"> </span>Filament.MagFilter.LINEAR,
<span style="color: #bbbbbb"> </span>Filament.WrapMode.CLAMP_TO_EDGE);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;albedo&#39;</span>,<span style="color: #bbbbbb"> </span>albedo,<span style="color: #bbbbbb"> </span>sampler);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;roughness&#39;</span>,<span style="color: #bbbbbb"> </span>roughness,<span style="color: #bbbbbb"> </span>sampler);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;metallic&#39;</span>,<span style="color: #bbbbbb"> </span>metallic,<span style="color: #bbbbbb"> </span>sampler);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;normal&#39;</span>,<span style="color: #bbbbbb"> </span>normal,<span style="color: #bbbbbb"> </span>sampler);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;ao&#39;</span>,<span style="color: #bbbbbb"> </span>ao,<span style="color: #bbbbbb"> </span>sampler);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;albedo&#39;</span>,<span style="color: #bbbbbb"> </span>albedo,<span style="color: #bbbbbb"> </span>sampler);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;roughness&#39;</span>,<span style="color: #bbbbbb"> </span>roughness,<span style="color: #bbbbbb"> </span>sampler);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;metallic&#39;</span>,<span style="color: #bbbbbb"> </span>metallic,<span style="color: #bbbbbb"> </span>sampler);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;normal&#39;</span>,<span style="color: #bbbbbb"> </span>normal,<span style="color: #bbbbbb"> </span>sampler);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.matinstance.setTextureParameter(<span style="color: #BA2121">&#39;ao&#39;</span>,<span style="color: #bbbbbb"> </span>ao,<span style="color: #bbbbbb"> </span>sampler);
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// Replace low-res skybox with high-res skybox.</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.destroySkybox(<span style="color: #008000; font-weight: bold">this</span>.skybox);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.skybox<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createSkyFromKtx1(sky_large_url);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.scene.setSkybox(<span style="color: #008000; font-weight: bold">this</span>.skybox);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// Replace low-res skybox with high-res skybox.</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.destroySkybox(<span style="color: #008000; font-weight: bold">this</span>.skybox);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.skybox<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.createSkyFromKtx1(sky_large_url);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.scene.setSkybox(<span style="color: #008000; font-weight: bold">this</span>.skybox);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.scene.addEntity(<span style="color: #008000; font-weight: bold">this</span>.suzanne);<span style="color: #bbbbbb"></span>
});<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.scene.addEntity(<span style="color: #008000; font-weight: bold">this</span>.suzanne);
});
</pre></div>
<h2>Introduce trackball rotation</h2>
@@ -241,13 +241,13 @@ listens for drag events and computes a rotation matrix.</p>
<p>Next, replace the <strong>initialize gltumble</strong> and <strong>apply gltumble matrix</strong> comments with the following
two code snippets.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.trackball<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Trackball(canvas,<span style="color: #bbbbbb"> </span>{startSpin<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #666666">0.035</span>});<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.trackball<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>Trackball(canvas,<span style="color: #bbbbbb"> </span>{startSpin<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #666666">0.035</span>});
</pre></div>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>tcm<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.getTransformManager();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>inst<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>tcm.getInstance(<span style="color: #008000; font-weight: bold">this</span>.suzanne);<span style="color: #bbbbbb"></span>
tcm.setTransform(inst,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.trackball.getMatrix());<span style="color: #bbbbbb"></span>
inst.<span style="color: #AA22FF; font-weight: bold">delete</span>();<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>tcm<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.getTransformManager();
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>inst<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>tcm.getInstance(<span style="color: #008000; font-weight: bold">this</span>.suzanne);
tcm.setTransform(inst,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.trackball.getMatrix());
inst.<span style="color: #AA22FF; font-weight: bold">delete</span>();
</pre></div>
<p>That&#x27;s it, we now have a fast-loading interactive demo. The complete JavaScript file is available
+87 -87
View File
@@ -22,9 +22,9 @@ a mobile-friendly page with a full-screen canvas.</p>
&lt;<span style="color: #008000; font-weight: bold">title</span>&gt;Filament Tutorial&lt;/<span style="color: #008000; font-weight: bold">title</span>&gt;
&lt;<span style="color: #008000; font-weight: bold">meta</span> <span style="color: #687822">charset</span><span style="color: #666666">=</span><span style="color: #BA2121">&quot;utf-8&quot;</span>&gt;
&lt;<span style="color: #008000; font-weight: bold">meta</span> <span style="color: #687822">name</span><span style="color: #666666">=</span><span style="color: #BA2121">&quot;viewport&quot;</span> <span style="color: #687822">content</span><span style="color: #666666">=</span><span style="color: #BA2121">&quot;width=device-width,user-scalable=no,initial-scale=1&quot;</span>&gt;
&lt;<span style="color: #008000; font-weight: bold">style</span>&gt;<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">body</span><span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">margin</span>:<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>;<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">overflow</span>:<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">hidden</span>;<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">canvas</span><span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"> </span>touch-action:<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">none</span>;<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">width</span>:<span style="color: #bbbbbb"> </span><span style="color: #666666">100</span><span style="color: #B00040">%</span>;<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">height</span>:<span style="color: #bbbbbb"> </span><span style="color: #666666">100</span><span style="color: #B00040">%</span>;<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
&lt;<span style="color: #008000; font-weight: bold">style</span>&gt;
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">body</span><span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">margin</span>:<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>;<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">overflow</span>:<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">hidden</span>;<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">canvas</span><span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"> </span>touch-action:<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">none</span>;<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">width</span>:<span style="color: #bbbbbb"> </span><span style="color: #666666">100</span><span style="color: #B00040">%</span>;<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">height</span>:<span style="color: #bbbbbb"> </span><span style="color: #666666">100</span><span style="color: #B00040">%</span>;<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>&lt;/<span style="color: #008000; font-weight: bold">style</span>&gt;
&lt;/<span style="color: #008000; font-weight: bold">head</span>&gt;
&lt;<span style="color: #008000; font-weight: bold">body</span>&gt;
@@ -48,24 +48,24 @@ a mobile-friendly page with a full-screen canvas.</p>
<li><code>triangle.js</code> will contain your application code.</li>
</ul>
<p>Go ahead and create <code>triangle.js</code> with the following content.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">class</span><span style="color: #bbbbbb"> </span>App<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">constructor</span>()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create entities</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render.bind(<span style="color: #008000; font-weight: bold">this</span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize.bind(<span style="color: #008000; font-weight: bold">this</span>);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.addEventListener(<span style="color: #BA2121">&#39;resize&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>render()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: render scene</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>resize()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: adjust viewport and canvas</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"></span>
}<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">class</span><span style="color: #bbbbbb"> </span>App<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">constructor</span>()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: create entities</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.render.bind(<span style="color: #008000; font-weight: bold">this</span>);
<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize.bind(<span style="color: #008000; font-weight: bold">this</span>);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.addEventListener(<span style="color: #BA2121">&#39;resize&#39;</span>,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.resize);
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>render()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: render scene</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
<span style="color: #bbbbbb"> </span>}
<span style="color: #bbbbbb"> </span>resize()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: adjust viewport and canvas</span>
<span style="color: #bbbbbb"> </span>}
}
Filament.init([<span style="color: #BA2121">&#39;triangle.filamat&#39;</span>],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.app<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>App()<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"> </span>);<span style="color: #bbbbbb"></span>
Filament.init([<span style="color: #BA2121">&#39;triangle.filamat&#39;</span>],<span style="color: #bbbbbb"> </span>()<span style="color: #bbbbbb"> </span>=&gt;<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.app<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span>App()<span style="color: #bbbbbb"> </span>}<span style="color: #bbbbbb"> </span>);
</pre></div>
<p>The two calls to <code>bind()</code> allow us to pass instance methods as callbacks for animation and resize
@@ -81,9 +81,9 @@ that define a PBR material. We&#x27;ll learn more about material packages in the
<h2>Spawn a local server</h2>
<p>Because of CORS restrictions, your web app cannot fetch the material package directly from the
file system. One way around this is to create a temporary server using Python or node:</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>python3 -m http.server <span style="color: #3D7B7B; font-style: italic"># Python 3</span>
python -m SimpleHTTPServer <span style="color: #3D7B7B; font-style: italic"># Python 2.7</span>
npx http-server -p <span style="color: #666666">8000</span> <span style="color: #3D7B7B; font-style: italic"># nodejs</span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>python3<span style="color: #bbbbbb"> </span>-m<span style="color: #bbbbbb"> </span>http.server<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic"># Python 3</span>
python<span style="color: #bbbbbb"> </span>-m<span style="color: #bbbbbb"> </span>SimpleHTTPServer<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic"># Python 2.7</span>
npx<span style="color: #bbbbbb"> </span>http-server<span style="color: #bbbbbb"> </span>-p<span style="color: #bbbbbb"> </span><span style="color: #666666">8000</span><span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic"># nodejs</span>
</pre></div>
<p>To see if this works, navigate to <a href="http://localhost:8000">http://localhost:8000</a> and check if you
@@ -93,8 +93,8 @@ with the correct MIME type.</p>
<h2>Create the Engine and Scene</h2>
<p>We now have a basic skeleton that can respond to paint and resize events. Let&#x27;s start adding
Filament objects to the app. Insert the following code into the top of the app constructor.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">document</span>.getElementsByTagName(<span style="color: #BA2121">&#39;canvas&#39;</span>)[<span style="color: #666666">0</span>];<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Engine.create(<span style="color: #008000; font-weight: bold">this</span>.canvas);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.canvas<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">document</span>.getElementsByTagName(<span style="color: #BA2121">&#39;canvas&#39;</span>)[<span style="color: #666666">0</span>];
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Engine.create(<span style="color: #008000; font-weight: bold">this</span>.canvas);
</pre></div>
<p>The above snippet creates the <code>Engine</code> by passing it a canvas DOM object. The engine needs the
@@ -102,9 +102,9 @@ canvas in order to create a WebGL 2.0 context in its contructor.</p>
<p>The engine is a factory for many Filament entities, including <code>Scene</code>, which is a flat container of
entities. Let&#x27;s go ahead and create a scene, then add a blank entity called <code>triangle</code> into the
scene.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.scene<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createScene();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.triangle<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.scene.addEntity(<span style="color: #008000; font-weight: bold">this</span>.triangle);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.scene<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createScene();
<span style="color: #008000; font-weight: bold">this</span>.triangle<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.EntityManager.get().create();
<span style="color: #008000; font-weight: bold">this</span>.scene.addEntity(<span style="color: #008000; font-weight: bold">this</span>.triangle);
</pre></div>
<p>Filament uses an <a href="//en.wikipedia.org/wiki/Entity-component-system">Entity-Component System</a>.
@@ -114,28 +114,28 @@ calls.</p>
<h2>Construct typed arrays</h2>
<p>Next we&#x27;ll create two typed arrays: a positions array with XY coordinates for each vertex, and a
colors array with a 32-bit word for each vertex.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>TRIANGLE_POSITIONS<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Float32Array</span>([<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.cos(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">2</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.sin(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">2</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.cos(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">4</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.sin(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">4</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),<span style="color: #bbbbbb"></span>
]);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>TRIANGLE_POSITIONS<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Float32Array</span>([
<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,
<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.cos(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">2</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.sin(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">2</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),
<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.cos(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">4</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),<span style="color: #bbbbbb"> </span><span style="color: #008000">Math</span>.sin(<span style="color: #008000">Math</span>.PI<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span><span style="color: #666666">4</span><span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">3</span>),
]);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>TRIANGLE_COLORS<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Uint32Array</span>([<span style="color: #666666">0xffff0000</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0xff00ff00</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0xff0000ff</span>]);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>TRIANGLE_COLORS<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Uint32Array</span>([<span style="color: #666666">0xffff0000</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0xff00ff00</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0xff0000ff</span>]);
</pre></div>
<p>Next we&#x27;ll use the positions and colors buffers to create a single <code>VertexBuffer</code> object.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>VertexAttribute<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexAttribute;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>AttributeType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer$AttributeType;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.vb<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.vertexCount(<span style="color: #666666">3</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.bufferCount(<span style="color: #666666">2</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.POSITION,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>AttributeType.FLOAT2,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">8</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.COLOR,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>AttributeType.UBYTE4,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">4</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.normalized(VertexAttribute.COLOR)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>VertexAttribute<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexAttribute;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>AttributeType<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer$AttributeType;
<span style="color: #008000; font-weight: bold">this</span>.vb<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.VertexBuffer.Builder()
<span style="color: #bbbbbb"> </span>.vertexCount(<span style="color: #666666">3</span>)
<span style="color: #bbbbbb"> </span>.bufferCount(<span style="color: #666666">2</span>)
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.POSITION,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>AttributeType.FLOAT2,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">8</span>)
<span style="color: #bbbbbb"> </span>.attribute(VertexAttribute.COLOR,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>AttributeType.UBYTE4,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">4</span>)
<span style="color: #bbbbbb"> </span>.normalized(VertexAttribute.COLOR)
<span style="color: #bbbbbb"> </span>.build(engine);
<span style="color: #008000; font-weight: bold">this</span>.vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>TRIANGLE_POSITIONS);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>TRIANGLE_COLORS);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>TRIANGLE_POSITIONS);
<span style="color: #008000; font-weight: bold">this</span>.vb.setBufferAt(engine,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span>TRIANGLE_COLORS);
</pre></div>
<p>The above snippet first creates aliases for two enum types, then constructs the vertex buffer using
@@ -149,12 +149,12 @@ attribute. Alternatively, we could have interleaved or concatenated these attrib
buffer slot.</p>
<p>Next we&#x27;ll construct an index buffer. The index buffer for our triangle is trivial: it simply holds
the integers 0,1,2.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.ib<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndexBuffer.Builder()<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.indexCount(<span style="color: #666666">3</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.bufferType(Filament.IndexBuffer$IndexType.USHORT)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.ib<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.IndexBuffer.Builder()
<span style="color: #bbbbbb"> </span>.indexCount(<span style="color: #666666">3</span>)
<span style="color: #bbbbbb"> </span>.bufferType(Filament.IndexBuffer$IndexType.USHORT)
<span style="color: #bbbbbb"> </span>.build(engine);
<span style="color: #008000; font-weight: bold">this</span>.ib.setBuffer(engine,<span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Uint16Array</span>([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">2</span>]));<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.ib.setBuffer(engine,<span style="color: #bbbbbb"> </span><span style="color: #AA22FF; font-weight: bold">new</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Uint16Array</span>([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">2</span>]));
</pre></div>
<p>Note that constructing an index buffer is similar to constructing a vertex buffer, but it only has
@@ -167,29 +167,29 @@ parameters, and they can be bound to renderables. We&#x27;ll learn more about ma
next tutorial.</p>
<p>After extracting the material instance, we can finally create a renderable component for the
triangle by setting up a bounding box and passing in the vertex and index buffers.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>mat<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createMaterial(<span style="color: #BA2121">&#39;triangle.filamat&#39;</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>matinst<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>mat.getDefaultInstance();<span style="color: #bbbbbb"></span>
Filament.RenderableManager.Builder(<span style="color: #666666">1</span>)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.boundingBox({<span style="color: #bbbbbb"> </span>center<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>],<span style="color: #bbbbbb"> </span>halfExtent<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>]<span style="color: #bbbbbb"> </span>})<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.material(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>matinst)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.geometry(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>Filament.RenderableManager$PrimitiveType.TRIANGLES,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.vb,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.ib)<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.triangle);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>mat<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createMaterial(<span style="color: #BA2121">&#39;triangle.filamat&#39;</span>);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>matinst<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>mat.getDefaultInstance();
Filament.RenderableManager.Builder(<span style="color: #666666">1</span>)
<span style="color: #bbbbbb"> </span>.boundingBox({<span style="color: #bbbbbb"> </span>center<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>],<span style="color: #bbbbbb"> </span>halfExtent<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>]<span style="color: #bbbbbb"> </span>})
<span style="color: #bbbbbb"> </span>.material(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>matinst)
<span style="color: #bbbbbb"> </span>.geometry(<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>Filament.RenderableManager$PrimitiveType.TRIANGLES,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.vb,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.ib)
<span style="color: #bbbbbb"> </span>.build(engine,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.triangle);
</pre></div>
<p>Next let&#x27;s wrap up the initialization routine by creating the swap chain, renderer, camera, and
view.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.swapChain<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createSwapChain();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.renderer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createRenderer();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.camera<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createCamera(Filament.EntityManager.get().create());<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.view<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createView();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.view.setCamera(<span style="color: #008000; font-weight: bold">this</span>.camera);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.view.setScene(<span style="color: #008000; font-weight: bold">this</span>.scene);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">this</span>.swapChain<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createSwapChain();
<span style="color: #008000; font-weight: bold">this</span>.renderer<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createRenderer();
<span style="color: #008000; font-weight: bold">this</span>.camera<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createCamera(Filament.EntityManager.get().create());
<span style="color: #008000; font-weight: bold">this</span>.view<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>engine.createView();
<span style="color: #008000; font-weight: bold">this</span>.view.setCamera(<span style="color: #008000; font-weight: bold">this</span>.camera);
<span style="color: #008000; font-weight: bold">this</span>.view.setScene(<span style="color: #008000; font-weight: bold">this</span>.scene);
<span style="color: #3D7B7B; font-style: italic">// Set up a blue-green background:</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.renderer.setClearOptions({clearColor<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.2</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>],<span style="color: #bbbbbb"> </span>clear<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});<span style="color: #bbbbbb"></span>
<span style="color: #3D7B7B; font-style: italic">// Set up a blue-green background:</span>
<span style="color: #008000; font-weight: bold">this</span>.renderer.setClearOptions({clearColor<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span>[<span style="color: #666666">0.0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0.2</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1.0</span>],<span style="color: #bbbbbb"> </span>clear<span style="color: #666666">:</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">true</span>});
<span style="color: #3D7B7B; font-style: italic">// Adjust the initial viewport:</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.resize();<span style="color: #bbbbbb"></span>
<span style="color: #3D7B7B; font-style: italic">// Adjust the initial viewport:</span>
<span style="color: #008000; font-weight: bold">this</span>.resize();
</pre></div>
<p>At this point, we&#x27;re done creating all Filament entities, and the code should run without errors.
@@ -197,24 +197,24 @@ However the canvas is still blank!</p>
<h2>Render and resize handlers</h2>
<p>Recall that our App class has a skeletal render method, which the browser calls every time it needs
to repaint. Often this is 60 times a second.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>render()<span style="color: #bbbbbb"> </span>{<span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: render scene</span><span style="color: #bbbbbb"></span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);<span style="color: #bbbbbb"></span>
}<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span>render()<span style="color: #bbbbbb"> </span>{
<span style="color: #bbbbbb"> </span><span style="color: #3D7B7B; font-style: italic">// TODO: render scene</span>
<span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.requestAnimationFrame(<span style="color: #008000; font-weight: bold">this</span>.render);
}
</pre></div>
<p>Let&#x27;s flesh this out by rotating the triangle and invoking the Filament renderer. Add the following
code to the top of the render method.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic">// Rotate the triangle.</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>radians<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Date</span>.now()<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">1000</span>;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>transform<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>mat4.fromRotation(mat4.create(),<span style="color: #bbbbbb"> </span>radians,<span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>]);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>tcm<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.getTransformManager();<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>inst<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>tcm.getInstance(<span style="color: #008000; font-weight: bold">this</span>.triangle);<span style="color: #bbbbbb"></span>
tcm.setTransform(inst,<span style="color: #bbbbbb"> </span>transform);<span style="color: #bbbbbb"></span>
inst.<span style="color: #AA22FF; font-weight: bold">delete</span>();<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #3D7B7B; font-style: italic">// Rotate the triangle.</span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>radians<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">Date</span>.now()<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span><span style="color: #666666">1000</span>;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>transform<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>mat4.fromRotation(mat4.create(),<span style="color: #bbbbbb"> </span>radians,<span style="color: #bbbbbb"> </span>[<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>]);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>tcm<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.engine.getTransformManager();
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>inst<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>tcm.getInstance(<span style="color: #008000; font-weight: bold">this</span>.triangle);
tcm.setTransform(inst,<span style="color: #bbbbbb"> </span>transform);
inst.<span style="color: #AA22FF; font-weight: bold">delete</span>();
<span style="color: #3D7B7B; font-style: italic">// Render the frame.</span><span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.renderer.render(<span style="color: #008000; font-weight: bold">this</span>.swapChain,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view);<span style="color: #bbbbbb"></span>
<span style="color: #3D7B7B; font-style: italic">// Render the frame.</span>
<span style="color: #008000; font-weight: bold">this</span>.renderer.render(<span style="color: #008000; font-weight: bold">this</span>.swapChain,<span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.view);
</pre></div>
<p>The first half of our render method obtains the transform component of the triangle entity and uses
@@ -225,14 +225,14 @@ that it wants to skip a frame, hence the <code>if</code> statement.</p>
<p>One last step. Add the following code to the resize method. This adjusts the resolution of the
rendering surface when the window size changes, taking <code>devicePixelRatio</code> into account for high-DPI
displays. It also adjusts the camera frustum accordingly.</p>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>dpr<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.devicePixelRatio;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerWidth<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerHeight<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.view.setViewport([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>width,<span style="color: #bbbbbb"> </span>height]);<span style="color: #bbbbbb"></span>
<div class="highlight" style="background: #f8f8f8"><pre style="line-height: 125%;"><span></span><span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>dpr<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.devicePixelRatio;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.width<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerWidth<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000; font-weight: bold">this</span>.canvas.height<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span><span style="color: #008000">window</span>.innerHeight<span style="color: #bbbbbb"> </span><span style="color: #666666">*</span><span style="color: #bbbbbb"> </span>dpr;
<span style="color: #008000; font-weight: bold">this</span>.view.setViewport([<span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span>width,<span style="color: #bbbbbb"> </span>height]);
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>aspect<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span>height;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>Projection<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Camera$Projection;<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">this</span>.camera.setProjection(Projection.ORTHO,<span style="color: #bbbbbb"> </span><span style="color: #666666">-</span>aspect,<span style="color: #bbbbbb"> </span>aspect,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>);<span style="color: #bbbbbb"></span>
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>aspect<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>width<span style="color: #bbbbbb"> </span><span style="color: #666666">/</span><span style="color: #bbbbbb"> </span>height;
<span style="color: #008000; font-weight: bold">const</span><span style="color: #bbbbbb"> </span>Projection<span style="color: #bbbbbb"> </span><span style="color: #666666">=</span><span style="color: #bbbbbb"> </span>Filament.Camera$Projection;
<span style="color: #008000; font-weight: bold">this</span>.camera.setProjection(Projection.ORTHO,<span style="color: #bbbbbb"> </span><span style="color: #666666">-</span>aspect,<span style="color: #bbbbbb"> </span>aspect,<span style="color: #bbbbbb"> </span><span style="color: #666666">-1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">0</span>,<span style="color: #bbbbbb"> </span><span style="color: #666666">1</span>);
</pre></div>
<p>You should now have a spinning triangle! The completed JavaScript is available
+1 -1
View File
@@ -50,7 +50,6 @@ set(PUBLIC_HDRS
set(SRCS
src/AtlasAllocator.cpp
src/Box.cpp
src/BufferObject.cpp
src/Camera.cpp
src/Color.cpp
@@ -589,6 +588,7 @@ else()
-Wgnu-conditional-omitted-operand
-Wweak-vtables -Wnon-virtual-dtor -Wclass-varargs -Wimplicit-fallthrough
-Wover-aligned
-Werror
)
endif()
+11 -4
View File
@@ -114,7 +114,7 @@ endif()
# ==================================================================================================
if (FILAMENT_SUPPORTS_METAL)
set(METAL_SRCS
set(METAL_OBJC_SRCS
src/metal/MetalBlitter.mm
src/metal/MetalBuffer.mm
src/metal/MetalBufferPool.mm
@@ -124,18 +124,21 @@ if (FILAMENT_SUPPORTS_METAL)
src/metal/MetalExternalImage.mm
src/metal/MetalHandles.mm
src/metal/MetalPlatform.mm
src/metal/MetalResourceTracker.cpp
src/metal/MetalState.mm
src/metal/MetalTimerQuery.mm
src/metal/MetalUtils.mm
)
list(APPEND SRCS ${METAL_SRCS})
set(METAL_CPP_SRCS
src/metal/MetalResourceTracker.cpp
)
list(APPEND SRCS ${METAL_OBJC_SRCS} ${METAL_CPP_SRCS})
if (IOS)
# Objective-C++ sources need an additional compiler flag on iOS to disable exceptions.
set_property(SOURCE
${METAL_SRCS}
${METAL_OBJC_SRCS}
src/opengl/platforms/PlatformCocoaTouchGL.mm
src/opengl/platforms/CocoaTouchExternalImage.mm
src/opengl/platforms/PlatformCocoaGL.mm
@@ -176,12 +179,15 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanPipelineCache.cpp
src/vulkan/VulkanPipelineCache.h
src/vulkan/VulkanPlatform.cpp
src/vulkan/VulkanReadPixels.cpp
src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanSamplerCache.cpp
src/vulkan/VulkanSamplerCache.h
src/vulkan/VulkanStagePool.cpp
src/vulkan/VulkanStagePool.h
src/vulkan/VulkanSwapChain.cpp
src/vulkan/VulkanSwapChain.h
src/vulkan/VulkanTaskHandler.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanUtility.cpp
@@ -350,6 +356,7 @@ else()
-Wgnu-conditional-omitted-operand
-Wweak-vtables -Wnon-virtual-dtor -Wclass-varargs -Wimplicit-fallthrough
-Wover-aligned
-Werror
)
endif()
@@ -279,8 +279,8 @@ public:
break;
}
size_t bpr = bpp * stride;
size_t bprAligned = (bpr + (alignment - 1)) & (~alignment + 1);
size_t const bpr = bpp * stride;
size_t const bprAligned = (bpr + (alignment - 1)) & (~alignment + 1);
return bprAligned * height;
}
+1
View File
@@ -75,6 +75,7 @@ CommandStream::CommandStream(Driver& driver, CircularBuffer& buffer) noexcept
void CommandStream::execute(void* buffer) {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
Profiler profiler;
@@ -1287,6 +1287,7 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
fromRegion:srcRegion
mipmapLevel:0];
scheduleDestroy(std::move(*p));
delete p;
}];
}
+3 -1
View File
@@ -391,7 +391,9 @@ MetalProgram::MetalProgram(id<MTLDevice> device, const Program& program) noexcep
id<MTLFunction> function = [library newFunctionWithName:@"main0"
constantValues:constants
error:&error];
function.label = @(program.getName().c_str());
if (!program.getName().empty()) {
function.label = @(program.getName().c_str());
}
assert_invariant(function);
*shaderFunctions[i] = function;
}
+6 -1
View File
@@ -120,7 +120,12 @@ constexpr inline GLenum getBufferBindingType(BufferObjectBinding bindingType) no
case BufferObjectBinding::UNIFORM:
return GL_UNIFORM_BUFFER;
case BufferObjectBinding::SHADER_STORAGE:
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
return GL_SHADER_STORAGE_BUFFER;
#else
utils::panic(__func__, __FILE__, __LINE__, "SHADER_STORAGE not supported");
return 0x90D2; // just to return something
#endif
}
}
@@ -418,7 +423,7 @@ constexpr /* inline */ GLenum getInternalFormat(TextureFormat format) noexcept {
case TextureFormat::RGBA32I: return GL_RGBA32I;
// compressed formats
#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_4_3) || defined(GL_ARB_ES3_compatibility)
#if defined(GL_ES_VERSION_3_0) || defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_ARB_ES3_compatibility)
case TextureFormat::EAC_R11: return GL_COMPRESSED_R11_EAC;
case TextureFormat::EAC_R11_SIGNED: return GL_COMPRESSED_SIGNED_R11_EAC;
case TextureFormat::EAC_RG11: return GL_COMPRESSED_RG11_EAC;
+118 -75
View File
@@ -16,6 +16,10 @@
#include "OpenGLContext.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <utility>
// change to true to display all GL extensions in the console on start-up
#define DEBUG_PRINT_EXTENSIONS false
@@ -24,17 +28,17 @@ using namespace utils;
namespace filament::backend {
bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
char const* version = (char const*)glGetString(GL_VERSION);
// This works on all versions of GLES
int const n = version ? sscanf(version, "OpenGL ES %d.%d", major, minor) : 0;
return n == 2;
} else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// OpenGL version
glGetIntegerv(GL_MAJOR_VERSION, major);
glGetIntegerv(GL_MINOR_VERSION, minor);
return (glGetError() == GL_NO_ERROR);
}
#ifdef BACKEND_OPENGL_VERSION_GLES
char const* version = (char const*)glGetString(GL_VERSION);
// This works on all versions of GLES
int const n = version ? sscanf(version, "OpenGL ES %d.%d", major, minor) : 0;
return n == 2;
#else
// OpenGL version
glGetIntegerv(GL_MAJOR_VERSION, major);
glGetIntegerv(GL_MINOR_VERSION, minor);
return (glGetError() == GL_NO_ERROR);
#endif
}
OpenGLContext::OpenGLContext() noexcept {
@@ -69,47 +73,48 @@ OpenGLContext::OpenGLContext() noexcept {
constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
initExtensionsGLES();
if (state.major == 3) {
assert_invariant(gets.max_texture_image_units >= 16);
assert_invariant(gets.max_combined_texture_image_units >= 32);
if (state.minor >= 1) {
features.multisample_texture = true;
// figure out our feature level
if (ext.EXT_texture_cube_map_array) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
gets.max_combined_texture_image_units >=
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
}
}
}
}
} else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// OpenGL version
initExtensionsGL();
if (state.major == 4) {
assert_invariant(state.minor >= 1);
mShaderModel = ShaderModel::DESKTOP;
if (state.minor >= 3) {
// cubemap arrays are available as of OpenGL 4.0
#ifdef BACKEND_OPENGL_VERSION_GLES
initExtensionsGLES();
if (state.major == 3) {
// Runtime OpenGL version is ES 3.x
assert_invariant(gets.max_texture_image_units >= 16);
assert_invariant(gets.max_combined_texture_image_units >= 32);
if (state.minor >= 1) {
features.multisample_texture = true;
// figure out our feature level
if (ext.EXT_texture_cube_map_array) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
// figure out our feature level
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
gets.max_combined_texture_image_units >=
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
}
}
features.multisample_texture = true;
}
// feedback loops are allowed on GL desktop as long as writes are disabled
bugs.allow_read_only_ancillary_feedback_loop = true;
assert_invariant(gets.max_texture_image_units >= 16);
assert_invariant(gets.max_combined_texture_image_units >= 32);
}
#else
initExtensionsGL();
if (state.major == 4) {
assert_invariant(state.minor >= 1);
mShaderModel = ShaderModel::DESKTOP;
if (state.minor >= 3) {
// cubemap arrays are available as of OpenGL 4.0
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
// figure out our feature level
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
gets.max_combined_texture_image_units >=
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
}
}
features.multisample_texture = true;
}
// feedback loops are allowed on GL desktop as long as writes are disabled
bugs.allow_read_only_ancillary_feedback_loop = true;
assert_invariant(gets.max_texture_image_units >= 16);
assert_invariant(gets.max_combined_texture_image_units >= 32);
#endif
#ifdef GL_EXT_texture_filter_anisotropic
if (ext.EXT_texture_filter_anisotropic) {
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
@@ -330,7 +335,7 @@ void OpenGLContext::setDefaultState() noexcept {
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
// In OpenGL ES, these flags do not exist because they are always on.
#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
#ifdef BACKEND_OPENGL_VERSION_GL
glEnable(GL_PROGRAM_POINT_SIZE);
enable(GL_PROGRAM_POINT_SIZE);
#endif
@@ -344,13 +349,17 @@ void OpenGLContext::setDefaultState() noexcept {
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
#endif
#if defined(GL_EXT_clip_control) || defined(GL_ARB_clip_control) || defined(GL_VERSION_4_5)
if (ext.EXT_clip_control) {
#if defined(BACKEND_OPENGL_VERSION_GL)
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
}
#elif defined(GL_EXT_clip_control)
glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
#endif
}
}
#ifdef BACKEND_OPENGL_VERSION_GLES
void OpenGLContext::initExtensionsGLES() noexcept {
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
@@ -385,7 +394,6 @@ void OpenGLContext::initExtensionsGLES() noexcept {
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext.QCOM_tiled_rendering = exts.has("GL_QCOM_tiled_rendering"sv);
ext.WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
ext.WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
ext.WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
@@ -395,6 +403,10 @@ void OpenGLContext::initExtensionsGLES() noexcept {
}
}
#endif // BACKEND_OPENGL_VERSION_GLES
#ifdef BACKEND_OPENGL_VERSION_GL
void OpenGLContext::initExtensionsGL() noexcept {
GLUtils::unordered_string_set exts;
GLint n = 0;
@@ -414,25 +426,34 @@ void OpenGLContext::initExtensionsGL() noexcept {
auto minor = state.minor;
ext.APPLE_color_buffer_packed_float = true; // Assumes core profile.
ext.ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv) || (major == 4 && minor >= 2);
ext.EXT_clip_control = exts.has("GL_ARB_clip_control"sv) || (major == 4 && minor >= 5);
ext.EXT_clip_control = (major == 4 && minor >= 5);
ext.EXT_color_buffer_float = true; // Assumes core profile.
ext.EXT_color_buffer_half_float = true; // Assumes core profile.
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext.EXT_disjoint_timer_query = true;
ext.EXT_multisampled_render_to_texture = false;
ext.EXT_multisampled_render_to_texture2 = false;
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext.EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext.EXT_texture_cube_map_array = true;
ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
ext.EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
ext.KHR_debug = major >= 4 && minor >= 3;
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext.OES_EGL_image_external_essl3 = false;
ext.WEBGL_compressed_texture_etc = false;
ext.WEBGL_compressed_texture_s3tc = false;
ext.WEBGL_compressed_texture_s3tc_srgb = false;
}
#endif // BACKEND_OPENGL_VERSION_GL
void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept {
if (target == GL_ELEMENT_ARRAY_BUFFER) {
constexpr size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
@@ -465,13 +486,8 @@ void OpenGLContext::pixelStore(GLenum pname, GLint param) noexcept {
switch (pname) {
case GL_PACK_ALIGNMENT: pcur = &state.pack.alignment; break;
case GL_PACK_ROW_LENGTH: pcur = &state.pack.row_length; break;
case GL_PACK_SKIP_PIXELS: pcur = &state.pack.skip_pixels; break; // convenience
case GL_PACK_SKIP_ROWS: pcur = &state.pack.skip_row; break; // convenience
case GL_UNPACK_ALIGNMENT: pcur = &state.unpack.alignment; break;
case GL_UNPACK_ROW_LENGTH: pcur = &state.unpack.row_length; break;
case GL_UNPACK_SKIP_PIXELS: pcur = &state.unpack.skip_pixels; break; // convenience
case GL_UNPACK_SKIP_ROWS: pcur = &state.unpack.skip_row; break; // convenience
default:
goto default_case;
}
@@ -618,12 +634,10 @@ void OpenGLContext::resetState() noexcept {
GLenum const bufferTargets[] = {
GL_UNIFORM_BUFFER,
GL_TRANSFORM_FEEDBACK_BUFFER,
#if !defined(__EMSCRIPTEN__)
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
GL_SHADER_STORAGE_BUFFER,
#endif
GL_ARRAY_BUFFER,
GL_COPY_READ_BUFFER,
GL_COPY_WRITE_BUFFER,
GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_PACK_BUFFER,
GL_PIXEL_UNPACK_BUFFER,
@@ -646,23 +660,30 @@ void OpenGLContext::resetState() noexcept {
// Reset state.textures to its default state to avoid the complexity and error-prone
// nature of resetting the GL state to its existing state
state.textures = {};
const GLuint textureTargets[] = {
GL_TEXTURE_2D,
GL_TEXTURE_2D_ARRAY,
GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_3D,
const std::pair<GLuint, bool> textureTargets[] = {
{ GL_TEXTURE_2D, true },
{ GL_TEXTURE_2D_ARRAY, true },
{ GL_TEXTURE_CUBE_MAP, true },
{ GL_TEXTURE_3D, true },
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
{ GL_TEXTURE_2D_MULTISAMPLE, true },
#endif
#if !defined(__EMSCRIPTEN__)
GL_TEXTURE_2D_MULTISAMPLE,
GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_CUBE_MAP_ARRAY,
#if defined(GL_OES_EGL_image_external)
{ GL_TEXTURE_EXTERNAL_OES, ext.OES_EGL_image_external_essl3 },
#endif
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_texture_cube_map_array)
{ GL_TEXTURE_CUBE_MAP_ARRAY, ext.EXT_texture_cube_map_array },
#endif
#endif
};
for (GLint unit = 0; unit < gets.max_combined_texture_image_units; ++unit) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindSampler(unit, 0);
for (auto const target : textureTargets) {
glBindTexture(target, 0);
for (auto [target, available] : textureTargets) {
if (available) {
glBindTexture(target, 0);
}
}
}
glActiveTexture(GL_TEXTURE0 + state.textures.active);
@@ -670,14 +691,10 @@ void OpenGLContext::resetState() noexcept {
// state.unpack
glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpack.alignment);
glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpack.row_length);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, state.unpack.skip_pixels);
glPixelStorei(GL_UNPACK_SKIP_ROWS, state.unpack.skip_row);
// state.pack
glPixelStorei(GL_PACK_ALIGNMENT, state.pack.alignment);
glPixelStorei(GL_PACK_ROW_LENGTH, state.pack.row_length);
glPixelStorei(GL_PACK_SKIP_PIXELS, state.pack.skip_pixels);
glPixelStorei(GL_PACK_SKIP_ROWS, state.pack.skip_row);
glPixelStorei(GL_PACK_ROW_LENGTH, 0); // we rely on GL_PACK_ROW_LENGTH being zero
// state.window
glScissor(
@@ -696,4 +713,30 @@ void OpenGLContext::resetState() noexcept {
}
OpenGLContext::FenceSync OpenGLContext::createFenceSync(
OpenGLPlatform&) noexcept {
auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
CHECK_GL_ERROR(utils::slog.e)
return { .sync = sync };
}
void OpenGLContext::destroyFenceSync(
OpenGLPlatform&, FenceSync sync) noexcept {
glDeleteSync(sync.sync);
CHECK_GL_ERROR(utils::slog.e)
}
OpenGLContext::FenceSync::Status OpenGLContext::clientWaitSync(
OpenGLPlatform&, FenceSync sync) const noexcept {
GLenum const status = glClientWaitSync(sync.sync, 0, 0u);
CHECK_GL_ERROR(utils::slog.e)
using Status = OpenGLContext::FenceSync::Status;
switch (status) {
case GL_ALREADY_SIGNALED: return Status::ALREADY_SIGNALED;
case GL_TIMEOUT_EXPIRED: return Status::TIMEOUT_EXPIRED;
case GL_CONDITION_SATISFIED: return Status::CONDITION_SATISFIED;
default: return Status::FAILURE;
}
}
} // namesapce filament
+74 -77
View File
@@ -33,6 +33,8 @@
namespace filament::backend {
class OpenGLPlatform;
class OpenGLContext {
public:
static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT;
@@ -67,6 +69,24 @@ public:
OpenGLContext() noexcept;
constexpr bool isAtLeastGL(int major, int minor) const noexcept {
#ifdef BACKEND_OPENGL_VERSION_GL
return state.major > major || (state.major == major && state.minor >= minor);
#else
(void)major, (void)minor;
return false;
#endif
}
constexpr bool isAtLeastGLES(int major, int minor) const noexcept {
#ifdef BACKEND_OPENGL_VERSION_GLES
return state.major > major || (state.major == major && state.minor >= minor);
#else
(void)major, (void)minor;
return false;
#endif
}
constexpr static inline size_t getIndexForTextureTarget(GLuint target) noexcept;
constexpr inline size_t getIndexForCap(GLenum cap) noexcept;
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
@@ -110,9 +130,6 @@ public:
GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept;
inline void stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept;
inline void polygonOffset(GLfloat factor, GLfloat units) noexcept;
inline void beginQuery(GLenum target, GLuint query) noexcept;
inline void endQuery(GLenum target) noexcept;
inline GLuint getQuery(GLenum target) const noexcept;
inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
@@ -121,6 +138,26 @@ public:
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
// we abstract GL's sync because it's not available in ES2, but we can use EGL's sync
// instead, if available.
struct FenceSync {
enum class Status {
ALREADY_SIGNALED,
TIMEOUT_EXPIRED,
CONDITION_SATISFIED,
FAILURE
};
union {
void* fence;
GLsync sync;
};
};
FenceSync createFenceSync(OpenGLPlatform& platform) noexcept;
void destroyFenceSync(OpenGLPlatform& platform, FenceSync sync) noexcept;
FenceSync::Status clientWaitSync(OpenGLPlatform& platform, FenceSync sync) const noexcept;
// glGet*() values
struct {
GLfloat max_anisotropy;
@@ -149,23 +186,22 @@ public:
bool EXT_color_buffer_half_float;
bool EXT_debug_marker;
bool EXT_disjoint_timer_query;
bool EXT_multisampled_render_to_texture;
bool EXT_multisampled_render_to_texture2;
bool EXT_multisampled_render_to_texture;
bool EXT_shader_framebuffer_fetch;
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool EXT_texture_compression_bptc;
bool EXT_texture_compression_etc2;
bool EXT_texture_compression_rgtc;
bool EXT_texture_compression_s3tc;
bool EXT_texture_compression_s3tc_srgb;
bool EXT_texture_compression_rgtc;
bool EXT_texture_compression_bptc;
bool EXT_texture_cube_map_array;
bool EXT_texture_filter_anisotropic;
bool EXT_texture_sRGB;
bool GOOGLE_cpp_style_line_directive;
bool KHR_debug;
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool OES_EGL_image_external_essl3;
bool QCOM_tiled_rendering;
bool WEBGL_compressed_texture_etc;
bool WEBGL_compressed_texture_s3tc;
bool WEBGL_compressed_texture_s3tc_srgb;
@@ -315,8 +351,8 @@ public:
GLintptr offset = 0;
GLsizeiptr size = 0;
} buffers[MAX_BUFFER_BINDINGS];
} targets[2]; // there are only 2 indexed buffer target (uniform and transform feedback)
GLuint genericBinding[9] = { 0 };
} targets[3]; // there are only 3 indexed buffer targets
GLuint genericBinding[7] = {};
} buffers;
struct {
@@ -332,15 +368,10 @@ public:
struct {
GLint row_length = 0;
GLint alignment = 4;
GLint skip_pixels = 0;
GLint skip_row = 0;
} unpack;
struct {
GLint row_length = 0;
GLint alignment = 4;
GLint skip_pixels = 0;
GLint skip_row = 0;
} pack;
struct {
@@ -348,10 +379,6 @@ public:
vec4gli viewport { 0 };
vec2glf depthRange { 0.0f, 1.0f };
} window;
struct {
GLuint timer = -1u;
} queries;
} state;
private:
@@ -403,8 +430,12 @@ private:
RenderPrimitive mDefaultVAO;
// this is chosen to minimize code size
#if defined(BACKEND_OPENGL_VERSION_GLES)
void initExtensionsGLES() noexcept;
#endif
#if defined(BACKEND_OPENGL_VERSION_GL)
void initExtensionsGL() noexcept;
#endif
template <typename T, typename F>
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
@@ -429,7 +460,9 @@ constexpr size_t OpenGLContext::getIndexForTextureTarget(GLuint target) noexcept
case GL_TEXTURE_2D: return 0;
case GL_TEXTURE_2D_ARRAY: return 1;
case GL_TEXTURE_CUBE_MAP: return 2;
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_TEXTURE_2D_MULTISAMPLE: return 3;
#endif
case GL_TEXTURE_EXTERNAL_OES: return 4;
case GL_TEXTURE_3D: return 5;
case GL_TEXTURE_CUBE_MAP_ARRAY: return 6;
@@ -450,17 +483,15 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
case GL_SAMPLE_ALPHA_TO_COVERAGE: index = 6; break;
case GL_SAMPLE_COVERAGE: index = 7; break;
case GL_POLYGON_OFFSET_FILL: index = 8; break;
case GL_PRIMITIVE_RESTART_FIXED_INDEX: index = 9; break;
case GL_RASTERIZER_DISCARD: index = 10; break;
#ifdef GL_ARB_seamless_cube_map
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 11; break;
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break;
#endif
#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
case GL_PROGRAM_POINT_SIZE: index = 12; break;
#ifdef BACKEND_OPENGL_VERSION_GL
case GL_PROGRAM_POINT_SIZE: index = 10; break;
#endif
default: index = 13; break; // should never happen
default: break;
}
assert_invariant(index < 13 && index < state.enables.caps.size());
assert_invariant(index < state.enables.caps.size());
return index;
}
@@ -470,15 +501,14 @@ constexpr size_t OpenGLContext::getIndexForBufferTarget(GLenum target) noexcept
// The indexed buffers MUST be first in this list (those usable with bindBufferRange)
case GL_UNIFORM_BUFFER: index = 0; break;
case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break;
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_SHADER_STORAGE_BUFFER: index = 2; break;
#endif
case GL_ARRAY_BUFFER: index = 3; break;
case GL_COPY_READ_BUFFER: index = 4; break;
case GL_COPY_WRITE_BUFFER: index = 5; break;
case GL_ELEMENT_ARRAY_BUFFER: index = 6; break;
case GL_PIXEL_PACK_BUFFER: index = 7; break;
case GL_PIXEL_UNPACK_BUFFER: index = 8; break;
default: index = 9; break; // should never happen
case GL_ELEMENT_ARRAY_BUFFER: index = 4; break;
case GL_PIXEL_PACK_BUFFER: index = 5; break;
case GL_PIXEL_UNPACK_BUFFER: index = 6; break;
default: break;
}
assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression)
return index;
@@ -501,21 +531,21 @@ void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
}
void OpenGLContext::setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
vec4gli scissor(left, bottom, width, height);
vec4gli const scissor(left, bottom, width, height);
update_state(state.window.scissor, scissor, [&]() {
glScissor(left, bottom, width, height);
});
}
void OpenGLContext::viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
vec4gli viewport(left, bottom, width, height);
vec4gli const viewport(left, bottom, width, height);
update_state(state.window.viewport, viewport, [&]() {
glViewport(left, bottom, width, height);
});
}
void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept {
vec2glf depthRange(near, far);
vec2glf const depthRange(near, far);
update_state(state.window.depthRange, depthRange, [&]() {
glDepthRangef(near, far);
});
@@ -526,7 +556,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
update_state(state.vao.p, vao, [&]() {
glBindVertexArray(vao->vao);
// update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray
size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
state.buffers.genericBinding[targetIndex] = vao->elementArray;
if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding)) {
// This shouldn't be needed, but it looks like some drivers don't do the implicit
@@ -538,8 +568,10 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
GLintptr offset, GLsizeiptr size) noexcept {
size_t targetIndex = getIndexForBufferTarget(target);
assert_invariant(targetIndex <= 2); // validity check
size_t const targetIndex = getIndexForBufferTarget(target);
// validity check
assert_invariant(targetIndex < sizeof(state.buffers.targets) / sizeof(*state.buffers.targets));
// this ALSO sets the generic binding
if ( state.buffers.targets[targetIndex].buffers[index].name != buffer
@@ -616,7 +648,7 @@ void OpenGLContext::disableVertexAttribArray(GLuint index) noexcept {
}
void OpenGLContext::enable(GLenum cap) noexcept {
size_t index = getIndexForCap(cap);
size_t const index = getIndexForCap(cap);
if (UTILS_UNLIKELY(!state.enables.caps[index])) {
state.enables.caps.set(index);
glEnable(cap);
@@ -624,7 +656,7 @@ void OpenGLContext::enable(GLenum cap) noexcept {
}
void OpenGLContext::disable(GLenum cap) noexcept {
size_t index = getIndexForCap(cap);
size_t const index = getIndexForCap(cap);
if (UTILS_UNLIKELY(state.enables.caps[index])) {
state.enables.caps.unset(index);
glDisable(cap);
@@ -723,41 +755,6 @@ void OpenGLContext::polygonOffset(GLfloat factor, GLfloat units) noexcept {
});
}
void OpenGLContext::beginQuery(GLenum target, GLuint query) noexcept {
switch (target) {
case GL_TIME_ELAPSED:
if (state.queries.timer != -1u) {
// this is an error
break;
}
state.queries.timer = query;
break;
default:
return;
}
glBeginQuery(target, query);
}
void OpenGLContext::endQuery(GLenum target) noexcept {
switch (target) {
case GL_TIME_ELAPSED:
state.queries.timer = -1u;
break;
default:
return;
}
glEndQuery(target);
}
GLuint OpenGLContext::getQuery(GLenum target) const noexcept {
switch (target) {
case GL_TIME_ELAPSED:
return state.queries.timer;
default:
return 0;
}
}
} // namespace filament
#endif //TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H
+150 -117
View File
@@ -69,7 +69,9 @@ using namespace utils;
namespace filament::backend {
Driver* OpenGLDriverFactory::create(
OpenGLPlatform* const platform, void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
OpenGLPlatform* const platform,
void* const sharedGLContext,
const Platform::DriverConfig& driverConfig) noexcept {
return OpenGLDriver::create(platform, sharedGLContext, driverConfig);
}
@@ -129,18 +131,18 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
return {};
}
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
if (UTILS_UNLIKELY(!(major >= 3 && minor >= 0))) {
PANIC_LOG("OpenGL ES 3.0 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
} else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// we require GL 4.1 headers and minimum version
if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
PANIC_LOG("OpenGL 4.1 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
#if defined(BACKEND_OPENGL_VERSION_GLES)
if (UTILS_UNLIKELY(!(major >= 3 && minor >= 0))) {
PANIC_LOG("OpenGL ES 3.0 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
#else
// we require GL 4.1 headers and minimum version
if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
PANIC_LOG("OpenGL 4.1 minimum needed (current %d.%d)", major, minor);
goto cleanup;
}
#endif
size_t const defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
Platform::DriverConfig const validConfig {
@@ -177,8 +179,16 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
slog.i << "OS version: " << mPlatform.getOSVersion() << io::endl;
#endif
if (mContext.ext.EXT_disjoint_timer_query ||
BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// Timer queries are core in GL 3.3, otherwise we need EXT_disjoint_timer_query
// iOS headers don't define GL_EXT_disjoint_timer_query, so make absolutely sure
// we won't use it.
#if defined(BACKEND_OPENGL_VERSION_GL)
assert_invariant(mContext.ext.EXT_disjoint_timer_query);
#endif
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
if (mContext.ext.EXT_disjoint_timer_query) {
// timer queries are available
if (mContext.bugs.dont_use_timer_query && mPlatform.canCreateFence()) {
// however, they don't work well, revert to using fences if we can.
@@ -187,7 +197,9 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mTimerQueryImpl = new TimerQueryNative(mContext);
}
mFrameTimeSupported = true;
} else if (mPlatform.canCreateFence()) {
} else
#endif
if (mPlatform.canCreateFence()) {
// no timer queries, but we can use fences
mTimerQueryImpl = new OpenGLTimerQueryFence(mPlatform);
mFrameTimeSupported = true;
@@ -537,23 +549,29 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
GLsizei(width), GLsizei(height), GLsizei(depth) * 6);
break;
}
#ifdef BACKEND_OPENGL_LEVEL_GLES31
case GL_TEXTURE_2D_MULTISAMPLE:
if constexpr (TEXTURE_2D_MULTISAMPLE_SUPPORTED) {
// NOTE: if there is a mix of texture and renderbuffers, "fixed_sample_locations" must be true
// NOTE: what's the benefit of setting "fixed_sample_locations" to false?
#if BACKEND_OPENGL_LEVEL >= BACKEND_OPENGL_LEVEL_GLES31
// only supported from GL 4.3 and GLES 3.1 headers
glTexStorage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
GLsizei(width), GLsizei(height), GL_TRUE);
#elif defined(GL_VERSION_4_1)
// only supported in GL (GL4.1 doesn't support glTexStorage2DMultisample)
glTexImage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
GLsizei(width), GLsizei(height), GL_TRUE);
if (mContext.isAtLeastGL(4, 3) || mContext.isAtLeastGLES(3, 1)) {
// only supported from GL 4.3 and GLES 3.1 headers
glTexStorage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
GLsizei(width), GLsizei(height), GL_TRUE);
}
#ifdef BACKEND_OPENGL_VERSION_GL
else {
// only supported in GL (GL4.1 doesn't support glTexStorage2DMultisample)
glTexImage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
GLsizei(width), GLsizei(height), GL_TRUE);
}
#endif
} else {
PANIC_LOG("GL_TEXTURE_2D_MULTISAMPLE is not supported");
}
break;
#endif // BACKEND_OPENGL_LEVEL_GLES31
default: // cannot happen
break;
}
@@ -621,6 +639,7 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
@@ -629,6 +648,7 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
#endif
}
textureStorage(t, w, h, depth);
}
@@ -721,6 +741,7 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
@@ -728,6 +749,7 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
#endif
}
CHECK_GL_ERROR(utils::slog.e)
@@ -800,6 +822,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
GLTexture* t = handle_cast<GLTexture*>(binfo.handle);
assert_invariant(t);
assert_invariant(t->target != SamplerType::SAMPLER_EXTERNAL);
assert_invariant(rt->width <= valueForLevel(binfo.level, t->width) &&
rt->height <= valueForLevel(binfo.level, t->height));
@@ -907,7 +930,9 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
case GL_TEXTURE_2D:
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_TEXTURE_2D_MULTISAMPLE:
#endif
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment,
target, t->gl.id, binfo.level);
@@ -1135,21 +1160,26 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
rt->gl.samples = samples;
rt->targets = targets;
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = {std::numeric_limits<uint32_t>::max()};
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = {0};
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = { std::numeric_limits<uint32_t>::max() };
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = { 0 };
auto checkDimensions = [&tmin, &tmax](GLTexture* t, uint8_t level) {
const auto twidth = std::max(1u, t->width >> level);
const auto theight = std::max(1u, t->height >> level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
};
if (any(targets & TargetBufferFlags::COLOR_ALL)) {
GLenum bufs[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { GL_NONE };
const size_t maxDrawBuffers = getMaxDrawBuffers();
for (size_t i = 0; i < maxDrawBuffers; i++) {
if (any(targets & getTargetBufferFlagsAt(i))) {
auto t = rt->gl.color[i] = handle_cast<GLTexture*>(color[i].handle);
const auto twidth = std::max(1u, t->width >> color[i].level);
const auto theight = std::max(1u, t->height >> color[i].level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
assert_invariant(color[i].handle);
rt->gl.color[i] = handle_cast<GLTexture*>(color[i].handle);
framebufferTexture(color[i], rt, GL_COLOR_ATTACHMENT0 + i);
bufs[i] = GL_COLOR_ATTACHMENT0 + i;
checkDimensions(rt->gl.color[i], color[i].level);
}
}
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
@@ -1159,37 +1189,28 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
// handle special cases first (where depth/stencil are packed)
bool specialCased = false;
if ((targets & TargetBufferFlags::DEPTH_AND_STENCIL) == TargetBufferFlags::DEPTH_AND_STENCIL) {
assert_invariant(!stencil.handle || stencil.handle == depth.handle);
auto t = rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
if (any(rt->gl.depth->usage & TextureUsage::SAMPLEABLE) ||
(!depth.handle && !stencil.handle)) {
// special case: depth & stencil requested, and both provided as the same texture
// special case: depth & stencil requested, but both not provided
specialCased = true;
assert_invariant(depth.handle);
// either we supplied only the depth handle or both depth/stencil are identical and not null
if (depth.handle && (stencil.handle == depth.handle || !stencil.handle)) {
rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
framebufferTexture(depth, rt, GL_DEPTH_STENCIL_ATTACHMENT);
specialCased = true;
checkDimensions(rt->gl.depth, depth.level);
}
}
if (!specialCased) {
if (any(targets & TargetBufferFlags::DEPTH)) {
auto t = rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
assert_invariant(depth.handle);
rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
framebufferTexture(depth, rt, GL_DEPTH_ATTACHMENT);
checkDimensions(rt->gl.depth, depth.level);
}
if (any(targets & TargetBufferFlags::STENCIL)) {
auto t = rt->gl.stencil = handle_cast<GLTexture*>(stencil.handle);
const auto twidth = std::max(1u, t->width >> stencil.level);
const auto theight = std::max(1u, t->height >> stencil.level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
assert_invariant(stencil.handle);
rt->gl.stencil = handle_cast<GLTexture*>(stencil.handle);
framebufferTexture(stencil, rt, GL_STENCIL_ATTACHMENT);
checkDimensions(rt->gl.stencil, stencil.level);
}
}
@@ -1211,19 +1232,19 @@ void OpenGLDriver::createSyncR(Handle<HwSync> fh, int) {
DEBUG_MARKER()
GLSync* f = handle_cast<GLSync *>(fh);
f->gl.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
CHECK_GL_ERROR(utils::slog.e)
f->handle = mContext.createFenceSync(mPlatform);
// check the status of the sync once a frame, since we must do this from our thread
std::weak_ptr<GLSync::State> const weak = f->result;
runEveryNowAndThen([sync = f->gl.sync, weak]() -> bool {
runEveryNowAndThen(
[&platform = mPlatform, context = mContext, handle = f->handle, weak]() -> bool {
auto result = weak.lock();
if (result) {
GLenum const status = glClientWaitSync(sync, 0, 0u);
auto const status = context.clientWaitSync(platform, handle);
result->status.store(status, std::memory_order_relaxed);
return (status != GL_TIMEOUT_EXPIRED);
return (status != OpenGLContext::FenceSync::Status::TIMEOUT_EXPIRED);
}
return true; // we're done
return true;
});
}
@@ -1332,9 +1353,6 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
glDeleteRenderbuffers(1, &t->gl.id);
}
if (t->gl.fence) {
glDeleteSync(t->gl.fence);
}
if (t->gl.sidecarRenderBufferMS) {
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
@@ -1413,10 +1431,9 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
void OpenGLDriver::destroySync(Handle<HwSync> sh) {
DEBUG_MARKER()
if (sh) {
GLSync* s = handle_cast<GLSync*>(sh);
glDeleteSync(s->gl.sync);
mContext.destroyFenceSync(mPlatform, s->handle);
destruct(sh, s);
}
}
@@ -1640,7 +1657,7 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) {
// Three-component SRGB is a color-renderable texture format in core OpenGL on desktop.
case TextureFormat::SRGB8:
return BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL;
return mContext.isAtLeastGL(4, 5);
// Half-float formats, requires extension.
case TextureFormat::R16F:
@@ -1970,7 +1987,9 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture *>(th);
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
assert_invariant(t->gl.target != GL_TEXTURE_2D_MULTISAMPLE);
#endif
// Note: glGenerateMimap can also fail if the internal format is not both
// color-renderable and filterable (i.e.: doesn't work for depth)
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
@@ -2011,8 +2030,13 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
gl.pixelStore(GL_UNPACK_ROW_LENGTH, GLint(p.stride));
gl.pixelStore(GL_UNPACK_ALIGNMENT, GLint(p.alignment));
gl.pixelStore(GL_UNPACK_SKIP_PIXELS, GLint(p.left));
gl.pixelStore(GL_UNPACK_SKIP_ROWS, GLint(p.top));
// This is equivalent to using GL_UNPACK_SKIP_PIXELS and GL_UNPACK_SKIP_ROWS
using PBD = PixelBufferDescriptor;
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
void const* const buffer = static_cast<char const*>(p.buffer) + p.left * bpp + bpr * p.top;
switch (t->target) {
case SamplerType::SAMPLER_EXTERNAL:
@@ -2026,7 +2050,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
assert_invariant(t->gl.target == GL_TEXTURE_2D);
glTexSubImage2D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), glFormat, glType, p.buffer);
GLsizei(width), GLsizei(height), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_3D:
assert_invariant(zoffset + depth <= std::max(1u, t->depth >> level));
@@ -2035,7 +2059,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
assert_invariant(t->gl.target == GL_TEXTURE_3D);
glTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, p.buffer);
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
@@ -2047,7 +2071,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
t->gl.target == GL_TEXTURE_CUBE_MAP_ARRAY);
glTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, p.buffer);
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_CUBEMAP: {
assert_invariant(t->gl.target == GL_TEXTURE_CUBE_MAP);
@@ -2063,7 +2087,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
GLenum const target = getCubemapTarget(zoffset + face);
glTexSubImage2D(target, GLint(level), GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), glFormat, glType,
static_cast<uint8_t const*>(p.buffer) + faceSize * face);
static_cast<uint8_t const*>(buffer) + faceSize * face);
}
break;
}
@@ -2337,13 +2361,14 @@ SyncStatus OpenGLDriver::getSyncStatus(Handle<HwSync> sh) {
return SyncStatus::NOT_SIGNALED;
}
auto status = s->result->status.load(std::memory_order_relaxed);
using Status = OpenGLContext::FenceSync::Status;
switch (status) {
case GL_CONDITION_SATISFIED:
case GL_ALREADY_SIGNALED:
case Status::CONDITION_SATISFIED:
case Status::ALREADY_SIGNALED:
return SyncStatus::SIGNALED;
case GL_TIMEOUT_EXPIRED:
case Status::TIMEOUT_EXPIRED:
return SyncStatus::NOT_SIGNALED;
case GL_WAIT_FAILED:
case Status::FAILURE:
default:
return SyncStatus::ERROR;
}
@@ -2368,21 +2393,20 @@ void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
// glInvalidateFramebuffer appeared on GLES 3.0 and GL4.3, for simplicity we just
// ignore it on GL (rather than having to do a runtime check).
if (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES &&
BACKEND_OPENGL_LEVEL >= BACKEND_OPENGL_LEVEL_GLES30) {
if (!gl.bugs.disable_invalidate_framebuffer) {
AttachmentArray attachments; // NOLINT
GLsizei const attachmentCount = getAttachments(attachments, rt, discardFlags);
if (attachmentCount) {
glInvalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
CHECK_GL_ERROR(utils::slog.e)
// glInvalidateFramebuffer appeared on GLES 3.0 and GL4.3
#if defined(BACKEND_OPENGL_LEVEL_GLES30) || defined(BACKEND_OPENGL_VERSION_GL)
if ((mContext.isAtLeastGLES(3, 0) || mContext.isAtLeastGL(4, 3))
&& !gl.bugs.disable_invalidate_framebuffer) {
AttachmentArray attachments; // NOLINT
GLsizei const attachmentCount = getAttachments(attachments, rt, discardFlags);
if (attachmentCount) {
glInvalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
} else {
// on GL desktop we assume we don't have glInvalidateFramebuffer, but even if the GPU is
// not a tiler, it's important to clear the framebuffer before drawing, as it resets
CHECK_GL_ERROR(utils::slog.e)
} else
#endif
{
// It's important to clear the framebuffer before drawing, as it resets
// the fb to a known state (resets fb compression and possibly other things).
// So we use glClear instead of glInvalidateFramebuffer
gl.disable(GL_SCISSOR_TEST);
@@ -2454,8 +2478,8 @@ void OpenGLDriver::endRenderPass(int) {
// glInvalidateFramebuffer appeared on GLES 3.0 and GL4.3, for simplicity we just
// ignore it on GL (rather than having to do a runtime check).
if (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES &&
BACKEND_OPENGL_LEVEL >= BACKEND_OPENGL_LEVEL_GLES30) {
#if defined(BACKEND_OPENGL_LEVEL_GLES30) || defined(BACKEND_OPENGL_VERSION_GL)
if (mContext.isAtLeastGLES(3, 0) || mContext.isAtLeastGL(4, 3)) {
auto effectiveDiscardFlags = discardFlags;
if (gl.bugs.invalidate_end_only_if_invalidate_start) {
effectiveDiscardFlags &= mRenderPassParams.flags.discardStart;
@@ -2471,6 +2495,7 @@ void OpenGLDriver::endRenderPass(int) {
CHECK_GL_ERROR(utils::slog.e)
}
}
#endif
#ifndef NDEBUG
// clear the discarded buffers in debug builds
@@ -2714,10 +2739,7 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
GLenum const glFormat = getFormat(p.format);
GLenum const glType = getType(p.type);
gl.pixelStore(GL_PACK_ROW_LENGTH, (GLint)p.stride);
gl.pixelStore(GL_PACK_ALIGNMENT, (GLint)p.alignment);
gl.pixelStore(GL_PACK_SKIP_PIXELS, (GLint)p.left);
gl.pixelStore(GL_PACK_SKIP_ROWS, (GLint)p.top);
gl.pixelStore(GL_PACK_ALIGNMENT, (GLint)p.alignment);
/*
* glReadPixel() operation...
@@ -2745,44 +2767,54 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
*/
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
// glReadPixel doesn't resolve automatically, but it does with the auto-resolve extension,
// which we're always emulating. So if we have a resolved fbo (fbo_read), use that instead.
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
using PBD = PixelBufferDescriptor;
// The PBO only needs to accommodate the area we're reading, with alignment.
auto const pboSize = (GLsizeiptr)PBD::computeDataSize(
p.format, p.type, width, height, p.alignment);
GLuint pbo;
glGenBuffers(1, &pbo);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, (GLsizeiptr)p.size, nullptr, GL_STATIC_DRAW);
glBufferData(GL_PIXEL_PACK_BUFFER, pboSize, nullptr, GL_STATIC_DRAW);
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, nullptr);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
// we're forced to make a copy on the heap because otherwise it deletes std::function<> copy
// constructor.
auto* pUserBuffer = new PixelBufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, width, height, pbo, pUserBuffer]() mutable {
auto* const pUserBuffer = new PixelBufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, width, height, pbo, pboSize, pUserBuffer]() mutable {
PixelBufferDescriptor& p = *pUserBuffer;
auto& gl = mContext;
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
void* vaddr = nullptr;
#if defined(__EMSCRIPTEN__)
std::unique_ptr<uint8_t> clientBuffer = std::make_unique<uint8_t>(p.size);
glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, p.size, clientBuffer.get());
std::unique_ptr<uint8_t> clientBuffer = std::make_unique<uint8_t>(pboSize);
glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, pboSize, clientBuffer.get());
vaddr = clientBuffer.get();
#else
vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, (GLsizeiptr)p.size, GL_MAP_READ_BIT);
vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, pboSize, GL_MAP_READ_BIT);
#endif
if (vaddr) {
// now we need to flip the buffer vertically to match our API
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PixelBufferDescriptor::computeDataSize(
p.format, p.type, 1, 1, 1);
size_t const bpr = PixelBufferDescriptor::computeDataSize(
p.format, p.type, stride, 1, p.alignment);
char const* head = (char const*)vaddr + p.left * bpp + bpr * p.top;
char* tail = (char*)p.buffer + p.left * bpp + bpr * (p.top + height - 1);
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const dstBpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
char* pDst = (char*)p.buffer + p.left * bpp + dstBpr * (p.top + height - 1);
size_t const srcBpr = PBD::computeDataSize(p.format, p.type, width, 1, p.alignment);
char const* pSrc = (char const*)vaddr;
for (size_t i = 0; i < height; ++i) {
memcpy(tail, head, bpp * width);
head += bpr;
tail -= bpr;
memcpy(pDst, pSrc, bpp * width);
pSrc += srcBpr;
pDst -= dstBpr;
}
#if !defined(__EMSCRIPTEN__)
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
@@ -2849,7 +2881,7 @@ void OpenGLDriver::readBufferSubData(backend::BufferObjectHandle boh,
}
void OpenGLDriver::whenGpuCommandsComplete(std::function<void()> fn) noexcept {
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
OpenGLContext::FenceSync sync = mContext.createFenceSync(mPlatform);
mGpuCommandCompleteOps.emplace_back(sync, std::move(fn));
CHECK_GL_ERROR(utils::slog.e)
}
@@ -2862,15 +2894,16 @@ void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
auto& v = mGpuCommandCompleteOps;
auto it = v.begin();
while (it != v.end()) {
GLenum const status = glClientWaitSync(it->first, 0, 0);
if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED) {
using Status = OpenGLContext::FenceSync::Status;
auto const status = mContext.clientWaitSync(mPlatform, it->first);
if (status == Status::ALREADY_SIGNALED || status == Status::CONDITION_SATISFIED) {
it->second();
glDeleteSync(it->first);
mContext.destroyFenceSync(mPlatform, it->first);
it = v.erase(it);
} else if (UTILS_UNLIKELY(status == GL_WAIT_FAILED)) {
} else if (UTILS_UNLIKELY(status == Status::FAILURE)) {
// This should never happen, but is very problematic if it does, as we might leak
// some data depending on what the callback does. However, we clean up our own state.
glDeleteSync(it->first);
mContext.destroyFenceSync(mPlatform, it->first);
it = v.erase(it);
} else {
++it;
@@ -3207,7 +3240,7 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
useProgram(p);
#if defined(GL_ES_VERSION_3_1) || defined(GL_VERSION_4_3)
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
#if defined(__ANDROID__)
// on Android, GLES3.1 and above entry-points are defined in glext
@@ -3216,7 +3249,7 @@ void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGr
#endif
glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
#endif
#endif // BACKEND_OPENGL_LEVEL_GLES31
#ifdef FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
+4 -6
View File
@@ -122,7 +122,6 @@ public:
GLenum target = 0;
GLenum internalFormat = 0;
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
mutable GLsync fence = {};
// texture parameters go here too
GLfloat anisotropy = 1.0;
@@ -186,11 +185,10 @@ public:
struct GLSync : public HwSync {
using HwSync::HwSync;
struct State {
std::atomic<GLenum> status{ GL_TIMEOUT_EXPIRED };
std::atomic<OpenGLContext::FenceSync::Status> status{
OpenGLContext::FenceSync::Status::TIMEOUT_EXPIRED };
};
struct {
GLsync sync;
} gl;
OpenGLContext::FenceSync handle{};
std::shared_ptr<State> result{ std::make_shared<GLSync::State>() };
};
@@ -364,7 +362,7 @@ private:
// tasks executed on the main thread after the fence signaled
void whenGpuCommandsComplete(std::function<void()> fn) noexcept;
void executeGpuCommandsCompleteOps() noexcept;
std::vector<std::pair<GLsync, std::function<void()>>> mGpuCommandCompleteOps;
std::vector<std::pair<OpenGLContext::FenceSync, std::function<void()>>> mGpuCommandCompleteOps;
// tasks regularly executed on the main thread at until they return true
void runEveryNowAndThen(std::function<bool()> fn) noexcept;
+18 -14
View File
@@ -121,11 +121,21 @@ void OpenGLProgram::compileShaders(OpenGLContext& context,
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage stage = static_cast<ShaderStage>(i);
GLenum glShaderType;
GLenum glShaderType{};
switch (stage) {
case ShaderStage::VERTEX: glShaderType = GL_VERTEX_SHADER; break;
case ShaderStage::FRAGMENT: glShaderType = GL_FRAGMENT_SHADER; break;
case ShaderStage::COMPUTE: glShaderType = GL_COMPUTE_SHADER; break;
case ShaderStage::VERTEX:
glShaderType = GL_VERTEX_SHADER;
break;
case ShaderStage::FRAGMENT:
glShaderType = GL_FRAGMENT_SHADER;
break;
case ShaderStage::COMPUTE:
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
glShaderType = GL_COMPUTE_SHADER;
#else
continue;
#endif
break;
}
if (UTILS_LIKELY(!shadersSource[i].empty())) {
@@ -182,13 +192,12 @@ std::string_view OpenGLProgram::process_GOOGLE_cpp_style_line_directive(OpenGLCo
return { source, len };
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 and
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
std::string_view OpenGLProgram::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
using namespace std::literals;
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
if (context.state.major == 4 && context.state.minor == 1 &&
!context.ext.ARB_shading_language_packing) {
#ifdef BACKEND_OPENGL_VERSION_GL
if (!context.isAtLeastGL(4, 2) && !context.ext.ARB_shading_language_packing) {
return R"(
// these don't handle denormals, NaNs or inf
@@ -226,7 +235,7 @@ highp uint packHalf2x16(vec2 v) {
}
)"sv;
}
}
#endif // BACKEND_OPENGL_VERSION_GL
return ""sv;
}
@@ -406,11 +415,6 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* gld) const noexcept {
const GLTexture* const t = sb->textureUnitEntries[j].texture;
GLuint const s = sb->textureUnitEntries[j].sampler;
if (t) { // program may not use all samplers of sampler group
if (UTILS_UNLIKELY(t->gl.fence)) {
glWaitSync(t->gl.fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(t->gl.fence);
t->gl.fence = nullptr;
}
gld->bindTexture(tmu, t);
gld->bindSampler(tmu, s);
}
@@ -34,8 +34,9 @@ OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default;
// ------------------------------------------------------------------------------------------------
TimerQueryNative::TimerQueryNative(OpenGLContext& context)
: gl(context) {
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
TimerQueryNative::TimerQueryNative(OpenGLContext&) {
}
TimerQueryNative::~TimerQueryNative() = default;
@@ -44,12 +45,12 @@ void TimerQueryNative::flush() {
}
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* query) {
gl.beginQuery(GL_TIME_ELAPSED, query->gl.query);
glBeginQuery(GL_TIME_ELAPSED, query->gl.query);
CHECK_GL_ERROR(utils::slog.e)
}
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery*) {
gl.endQuery(GL_TIME_ELAPSED);
glEndQuery(GL_TIME_ELAPSED);
CHECK_GL_ERROR(utils::slog.e)
}
@@ -62,14 +63,14 @@ bool TimerQueryNative::queryResultAvailable(GLTimerQuery* query) {
uint64_t TimerQueryNative::queryResult(GLTimerQuery* query) {
GLuint64 elapsedTime = 0;
// IOS doesn't have glGetQueryObjectui64v, we'll never end-up here on ios anyways
#ifndef IOS
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
glGetQueryObjectui64v(query->gl.query, GL_QUERY_RESULT, &elapsedTime);
#endif
CHECK_GL_ERROR(utils::slog.e)
return elapsedTime;
}
#endif
// ------------------------------------------------------------------------------------------------
OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform)
@@ -85,7 +86,7 @@ OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform)
});
exitRequested = mExitRequested;
if (!queue.empty()) {
Job job(queue.front());
Job const job(queue.front());
queue.erase(queue.begin());
lock.unlock();
job();
@@ -105,7 +106,7 @@ OpenGLTimerQueryFence::~OpenGLTimerQueryFence() {
}
void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) {
std::unique_lock<utils::Mutex> lock(mLock);
std::unique_lock<utils::Mutex> const lock(mLock);
mQueue.push_back(std::forward<Job>(job));
mCondition.notify_one();
}
@@ -114,9 +115,9 @@ void OpenGLTimerQueryFence::flush() {
// Use calls to flush() as a proxy for when the GPU work started.
GLTimerQuery* query = mActiveQuery;
if (query) {
uint64_t elapsed = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
uint64_t const elapsed = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
if (!elapsed) {
uint64_t now = clock::now().time_since_epoch().count();
uint64_t const now = clock::now().time_since_epoch().count();
query->gl.emulation->elapsed.store(now, std::memory_order_relaxed);
//SYSTRACE_CONTEXT();
//SYSTRACE_ASYNC_BEGIN("gpu", query->gl.query);
@@ -139,7 +140,7 @@ void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* query) {
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* query) {
assert_invariant(mActiveQuery);
Platform::Fence* fence = mPlatform.createFence();
std::weak_ptr<GLTimerQuery::State> weak = query->gl.emulation;
std::weak_ptr<GLTimerQuery::State> const weak = query->gl.emulation;
mActiveQuery = nullptr;
//uint32_t cookie = cookie = query->gl.query;
push([&platform = mPlatform, fence, weak]() {
@@ -48,6 +48,8 @@ public:
virtual uint64_t queryResult(GLTimerQuery* query) = 0;
};
#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
class TimerQueryNative : public OpenGLTimerQueryInterface {
public:
explicit TimerQueryNative(OpenGLContext& context);
@@ -58,9 +60,10 @@ private:
void endTimeElapsedQuery(GLTimerQuery* query) override;
bool queryResultAvailable(GLTimerQuery* query) override;
uint64_t queryResult(GLTimerQuery* query) override;
OpenGLContext& gl;
};
#endif
class OpenGLTimerQueryFence : public OpenGLTimerQueryInterface {
public:
explicit OpenGLTimerQueryFence(OpenGLPlatform& platform);
+25 -59
View File
@@ -16,16 +16,18 @@
#include "gl_headers.h"
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
#include <EGL/egl.h>
#include <mutex>
// for non EGL platforms, we'd need to implement this differently. Currently, it's not a problem.
template<typename T>
static void getProcAddress(T& pfn, const char* name) noexcept {
pfn = (T)eglGetProcAddress(name);
}
namespace glext {
#ifdef GL_QCOM_tiled_rendering
PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
PFNGLENDTILINGQCOMPROC glEndTilingQCOM;
#endif
#ifdef GL_OES_EGL_image
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
@@ -46,7 +48,7 @@ PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#endif
#ifdef GL_EXT_clip_control
PFNGLCLIPCONTROLEXTPROC glClipControl;
PFNGLCLIPCONTROLEXTPROC glClipControlEXT;
#endif
#if defined(__ANDROID__)
@@ -58,71 +60,35 @@ PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
static std::once_flag sGlExtInitialized;
void importGLESExtensionsEntryPoints() {
std::call_once(sGlExtInitialized, []() {
#ifdef GL_QCOM_tiled_rendering
glStartTilingQCOM =
(PFNGLSTARTTILINGQCOMPROC)eglGetProcAddress(
"glStartTilingQCOM");
glEndTilingQCOM =
(PFNGLENDTILINGQCOMPROC)eglGetProcAddress(
"glEndTilingQCOM");
#endif
std::call_once(sGlExtInitialized, +[]() {
#ifdef GL_OES_EGL_image
glEGLImageTargetTexture2DOES =
(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(
"glEGLImageTargetTexture2DOES");
getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
#endif
#if GL_EXT_debug_marker
glInsertEventMarkerEXT =
(PFNGLINSERTEVENTMARKEREXTPROC)eglGetProcAddress(
"glInsertEventMarkerEXT");
glPushGroupMarkerEXT =
(PFNGLPUSHGROUPMARKEREXTPROC)eglGetProcAddress(
"glPushGroupMarkerEXT");
glPopGroupMarkerEXT =
(PFNGLPOPGROUPMARKEREXTPROC)eglGetProcAddress(
"glPopGroupMarkerEXT");
getProcAddress(glInsertEventMarkerEXT, "glInsertEventMarkerEXT");
getProcAddress(glPushGroupMarkerEXT, "glPushGroupMarkerEXT");
getProcAddress(glPopGroupMarkerEXT, "glPopGroupMarkerEXT");
#endif
#if GL_EXT_multisampled_render_to_texture
glFramebufferTexture2DMultisampleEXT =
(PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)eglGetProcAddress(
"glFramebufferTexture2DMultisampleEXT");
glRenderbufferStorageMultisampleEXT =
(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)eglGetProcAddress(
"glRenderbufferStorageMultisampleEXT");
getProcAddress(glFramebufferTexture2DMultisampleEXT, "glFramebufferTexture2DMultisampleEXT");
getProcAddress(glRenderbufferStorageMultisampleEXT, "glRenderbufferStorageMultisampleEXT");
#endif
#ifdef GL_KHR_debug
glDebugMessageCallbackKHR =
(PFNGLDEBUGMESSAGECALLBACKKHRPROC)eglGetProcAddress(
"glDebugMessageCallbackKHR");
glGetDebugMessageLogKHR =
(PFNGLGETDEBUGMESSAGELOGKHRPROC)eglGetProcAddress(
"glGetDebugMessageLogKHR");
getProcAddress(glDebugMessageCallbackKHR, "glDebugMessageCallbackKHR");
getProcAddress(glGetDebugMessageLogKHR, "glGetDebugMessageLogKHR");
#endif
#ifdef GL_EXT_disjoint_timer_query
glGetQueryObjectui64v =
(PFNGLGETQUERYOBJECTUI64VEXTPROC)eglGetProcAddress(
"glGetQueryObjectui64vEXT");
getProcAddress(glGetQueryObjectui64v, "glGetQueryObjectui64vEXT");
#endif
#ifdef GL_EXT_clip_control
getProcAddress(glClipControlEXT, "glClipControlEXT");
#endif
#if defined(__ANDROID__)
getProcAddress(glDispatchCompute, "glDispatchCompute");
#endif
});
#ifdef GL_EXT_clip_control
glClipControl =
(PFNGLCLIPCONTROLEXTPROC)eglGetProcAddress(
"glClipControlEXT");
#endif
#if defined(__ANDROID__)
glDispatchCompute =
(PFNGLDISPATCHCOMPUTEPROC)eglGetProcAddress(
"glDispatchCompute");
#endif
}
} // namespace glext
#endif
#endif // defined(FILAMENT_IMPORT_ENTRY_POINTS)
+125 -148
View File
@@ -17,6 +17,26 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GL_HEADERS_H
#define TNT_FILAMENT_BACKEND_OPENGL_GL_HEADERS_H
/*
* Configuration we aim to support:
*
* GL 4.5 headers
* - GL 4.1 runtime (for macOS)
* - GL 4.5 runtime
*
* GLES 2.0 headers
* - GLES 2.0 runtime Android only
*
* GLES 3.0 headers
* - GLES 3.0 runtime iOS and WebGL2 only
*
* GLES 3.1 headers
* - GLES 2.0 runtime
* - GLES 3.0 runtime
* - GLES 3.1 runtime
*/
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
#if defined(__EMSCRIPTEN__)
@@ -26,45 +46,6 @@
#endif
#include <GLES2/gl2ext.h>
/* The Android NDK doesn't expose extensions, fake it with eglGetProcAddress */
namespace glext {
// importGLESExtensionsEntryPoints is thread-safe and can be called multiple times.
// it is currently called from PlatformEGL.
void importGLESExtensionsEntryPoints();
#ifdef GL_QCOM_tiled_rendering
extern PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
extern PFNGLENDTILINGQCOMPROC glEndTilingQCOM;
#endif
#ifdef GL_OES_EGL_image
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
#ifdef GL_EXT_debug_marker
extern PFNGLINSERTEVENTMARKEREXTPROC glInsertEventMarkerEXT;
extern PFNGLPUSHGROUPMARKEREXTPROC glPushGroupMarkerEXT;
extern PFNGLPOPGROUPMARKEREXTPROC glPopGroupMarkerEXT;
#endif
#ifdef GL_EXT_multisampled_render_to_texture
extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
extern PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
#endif
#ifdef GL_KHR_debug
extern PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
extern PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
#endif
#ifdef GL_EXT_disjoint_timer_query
extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#endif
#ifdef GL_EXT_clip_control
extern PFNGLCLIPCONTROLEXTPROC glClipControl;
#endif
#if defined(__ANDROID__)
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
#endif
}
using namespace glext;
#elif defined(IOS)
#define GLES_SILENCE_DEPRECATION
@@ -85,111 +66,116 @@
#endif
#if (!defined(GL_ES_VERSION_2_0) && !defined(GL_VERSION_4_1))
#error "Minimum header version must be OpenGL ES 2.0 or OpenGL 4.1"
#endif
/* Validate the header configurations we aim to support */
#if defined(GL_VERSION_4_5)
#elif defined(GL_ES_VERSION_3_1)
#elif defined(GL_ES_VERSION_3_0)
# if !defined(IOS) && !defined(__EMSCRIPTEN__)
# error "GLES 3.0 headers only supported on iOS and WebGL2"
# endif
#elif defined(GL_ES_VERSION_2_0)
# if !defined(__ANDROID__)
# error "GLES 2.0 headers only supported on Android"
# endif
#else
# error "Minimum header version must be OpenGL ES 2.0 or OpenGL 4.5"
#endif
/*
* Since we need ES3.1 headers and iOS only has ES3.0, we also define the constants we
* need to avoid many #ifdef in the actual code.
* GLES extensions
*/
#if defined(GL_ES_VERSION_2_0)
#if defined(GL_ES_VERSION_2_0) // this basically means all versions of GLES
#ifdef GL_EXT_disjoint_timer_query
# ifndef GL_TIME_ELAPSED
# define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
# endif
#if defined(IOS)
// iOS headers only provide prototypes, nothing to do.
#else
#define FILAMENT_IMPORT_ENTRY_POINTS
/* The Android NDK doesn't expose extensions, fake it with eglGetProcAddress */
namespace glext {
// importGLESExtensionsEntryPoints is thread-safe and can be called multiple times.
// it is currently called from PlatformEGL.
void importGLESExtensionsEntryPoints();
#ifdef GL_OES_EGL_image
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
#ifdef GL_EXT_debug_marker
extern PFNGLINSERTEVENTMARKEREXTPROC glInsertEventMarkerEXT;
extern PFNGLPUSHGROUPMARKEREXTPROC glPushGroupMarkerEXT;
extern PFNGLPOPGROUPMARKEREXTPROC glPopGroupMarkerEXT;
#endif
#ifdef GL_EXT_multisampled_render_to_texture
extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
extern PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
#endif
#ifdef GL_KHR_debug
extern PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
extern PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
#endif
#ifdef GL_EXT_clip_control
# ifndef GL_LOWER_LEFT
# define GL_LOWER_LEFT GL_LOWER_LEFT_EXT
# endif
# ifndef GL_ZERO_TO_ONE
# define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
# endif
extern PFNGLCLIPCONTROLEXTPROC glClipControlEXT;
#endif
#ifdef GL_EXT_disjoint_timer_query
extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#endif
#if defined(__ANDROID__)
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
#endif
} // namespace glext
using namespace glext;
#ifndef GL_TEXTURE_CUBE_MAP_ARRAY
# define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009
#endif
// Prevent lots of #ifdef's between desktop and mobile
#ifdef GL_EXT_disjoint_timer_query
# define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
#endif
#ifdef GL_EXT_clip_control
# define GL_LOWER_LEFT GL_LOWER_LEFT_EXT
# define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
#endif
// we need GL_TEXTURE_CUBE_MAP_ARRAY defined, but we won't use it if the extension/feature
// is not available.
#if defined(GL_EXT_texture_cube_map_array)
# define GL_TEXTURE_CUBE_MAP_ARRAY GL_TEXTURE_CUBE_MAP_ARRAY_EXT
#else
# define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009
#endif
#if defined(GL_KHR_debug)
# ifndef GL_DEBUG_OUTPUT
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
# endif
# ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_HIGH
# define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_MEDIUM
# define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_LOW
# define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_NOTIFICATION
# define GL_DEBUG_SEVERITY_NOTIFICATION GL_DEBUG_SEVERITY_NOTIFICATION_KHR
# endif
# ifndef GL_DEBUG_TYPE_MARKER
# define GL_DEBUG_TYPE_MARKER GL_DEBUG_TYPE_MARKER_KHR
# endif
# ifndef GL_DEBUG_TYPE_ERROR
# define GL_DEBUG_TYPE_ERROR GL_DEBUG_TYPE_ERROR_KHR
# endif
# ifndef GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR
# define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR
# endif
# ifndef GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR
# define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR
# endif
# ifndef GL_DEBUG_TYPE_PORTABILITY
# define GL_DEBUG_TYPE_PORTABILITY GL_DEBUG_TYPE_PORTABILITY_KHR
# endif
# ifndef GL_DEBUG_TYPE_PERFORMANCE
# define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR
# endif
# ifndef GL_DEBUG_TYPE_OTHER
# define GL_DEBUG_TYPE_OTHER GL_DEBUG_TYPE_OTHER_KHR
# endif
# define glDebugMessageCallback glDebugMessageCallbackKHR
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
# define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
# define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
# define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
# define GL_DEBUG_SEVERITY_NOTIFICATION GL_DEBUG_SEVERITY_NOTIFICATION_KHR
# define GL_DEBUG_TYPE_MARKER GL_DEBUG_TYPE_MARKER_KHR
# define GL_DEBUG_TYPE_ERROR GL_DEBUG_TYPE_ERROR_KHR
# define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR
# define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR
# define GL_DEBUG_TYPE_PORTABILITY GL_DEBUG_TYPE_PORTABILITY_KHR
# define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR
# define GL_DEBUG_TYPE_OTHER GL_DEBUG_TYPE_OTHER_KHR
# define glDebugMessageCallback glDebugMessageCallbackKHR
#endif
/* The iOS SDK only provides OpenGL ES headers up to 3.0. Filament works with OpenGL 3.0, but
* requires ES3.1 headers */
#if !defined(GL_ES_VERSION_3_1)
#define GL_SHADER_STORAGE_BUFFER 0x90D2
#define GL_COMPUTE_SHADER 0x91B9
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
// FIXME: The GL_TIME_ELAPSED define is used unconditionally in Filament, but
// requires extension support.
#ifndef GL_TIME_ELAPSED
#define GL_TIME_ELAPSED 0x88BF
#endif
#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A
#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C
#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D
#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E
#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F
#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054
#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F
#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A
#endif
#endif // GL_ES_VERSION_2_0
// This is just to simplify the implementation (i.e. so we don't have to have #ifdefs everywhere)
#ifndef GL_OES_EGL_image_external
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
// This is an odd duck function that exists in WebGL 2.0 but not in OpenGL ES.
#if defined(__EMSCRIPTEN__)
extern "C" {
@@ -197,34 +183,25 @@ void glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void *d
}
#endif
#define BACKEND_OPENGL_VERSION_GLES 0
#define BACKEND_OPENGL_VERSION_GL 1
#if defined(GL_ES_VERSION_2_0)
# define BACKEND_OPENGL_VERSION BACKEND_OPENGL_VERSION_GLES
#elif defined(GL_VERSION_4_1)
# define BACKEND_OPENGL_VERSION BACKEND_OPENGL_VERSION_GL
# define BACKEND_OPENGL_VERSION_GLES
#elif defined(GL_VERSION_4_5)
# define BACKEND_OPENGL_VERSION_GL
#else
# error "Unsupported header version"
#endif
#define BACKEND_OPENGL_LEVEL_GLES20 0
#define BACKEND_OPENGL_LEVEL_GLES30 1
#define BACKEND_OPENGL_LEVEL_GLES31 2
#if defined(GL_VERSION_4_1)
# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES30
#if defined(GL_VERSION_4_5) || defined(GL_ES_VERSION_3_1)
# define BACKEND_OPENGL_LEVEL_GLES31
# ifdef __EMSCRIPTEN__
# error "__EMSCRIPTEN__ shouldn't be defined with GLES 3.1 headers"
# endif
#endif
#if defined(GL_ES_VERSION_3_1)
# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES31
#elif defined(GL_ES_VERSION_3_0)
# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES30
#elif defined(GL_ES_VERSION_2_0)
# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES20
#if defined(GL_VERSION_4_5) || defined(GL_ES_VERSION_3_0)
# define BACKEND_OPENGL_LEVEL_GLES30
#endif
// This is just to simplify the implementation (i.e. so we don't have to have #ifdefs everywhere)
#ifndef GL_OES_EGL_image_external
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#if defined(GL_VERSION_4_5) || defined(GL_ES_VERSION_2_0)
# define BACKEND_OPENGL_LEVEL_GLES20
#endif
#include "NullGLES.h"
@@ -84,8 +84,8 @@ Driver* PlatformCocoaTouchGL::createDriver(void* const sharedGLContext, const Pl
pImpl->mDefaultColorbuffer = renderbuffer[0];
pImpl->mDefaultDepthbuffer = renderbuffer[1];
CVReturn success = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, nullptr,
pImpl->mGLContext, nullptr, &pImpl->mTextureCache);
UTILS_UNUSED_IN_RELEASE CVReturn success = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault,
nullptr, pImpl->mGLContext, nullptr, &pImpl->mTextureCache);
assert_invariant(success == kCVReturnSuccess);
pImpl->mExternalImageSharedGl = new CocoaTouchExternalImage::SharedGl();
@@ -94,9 +94,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
return nullptr;
}
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
// PlatofrmEGL is used with and without GLES, but this function is only
// meaningful when GLES is used.
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
importGLESExtensionsEntryPoints();
#endif
@@ -57,7 +57,7 @@
constexpr struct VkAllocationCallbacks* VKALLOC = nullptr;
constexpr static const int VK_REQUIRED_VERSION_MAJOR = 1;
constexpr static const int VK_REQUIRED_VERSION_MINOR = 0;
constexpr static const int VK_REQUIRED_VERSION_MINOR = 1;
// Maximum number of VkCommandBuffer handles managed simultaneously by VulkanCommands.
//
+41 -166
View File
@@ -75,6 +75,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform,
mContext.commands->setObserver(&mPipelineCache);
mPipelineCache.setDevice(mContext.device, mContext.allocator);
mPipelineCache.setDummyTexture(mContext.emptyTexture->getPrimaryImageView());
mReadPixels.initialize(mContext.device);
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -134,6 +136,9 @@ void VulkanDriver::terminate() {
void VulkanDriver::tick(int) {
mContext.commands->updateFences();
// Handle any posted tasks
runTaskHandler();
}
// Garbage collection should not occur too frequently, only about once per frame. Internally, the
@@ -1011,31 +1016,35 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
rt->transformClientRectToPlatform(&renderPassInfo.renderArea);
VkClearValue clearValues[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {};
VkClearValue clearValues[
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT +
1] = {};
if (params.flags.clear != TargetBufferFlags::NONE) {
// NOTE: clearValues must be populated in the same order as the attachments array in
// VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
// actually clearing that particular target.
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (fbkey.color[i]) {
VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.color.float32[0] = params.clearColor.r;
clearValue.color.float32[1] = params.clearColor.g;
clearValue.color.float32[2] = params.clearColor.b;
clearValue.color.float32[3] = params.clearColor.a;
// NOTE: clearValues must be populated in the same order as the attachments array in
// VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
// actually clearing that particular target.
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (fbkey.color[i]) {
VkClearValue &clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.color.float32[0] = params.clearColor.r;
clearValue.color.float32[1] = params.clearColor.g;
clearValue.color.float32[2] = params.clearColor.b;
clearValue.color.float32[3] = params.clearColor.a;
}
}
}
// Resolve attachments are not cleared but still have entries in the list, so skip over them.
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (rpkey.needsResolveMask & (1u << i)) {
renderPassInfo.clearValueCount++;
// Resolve attachments are not cleared but still have entries in the list, so skip over them.
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
if (rpkey.needsResolveMask & (1u << i)) {
renderPassInfo.clearValueCount++;
}
}
if (fbkey.depth) {
VkClearValue &clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.depthStencil = {(float) params.clearDepth, 0};
}
renderPassInfo.pClearValues = &clearValues[0];
}
if (fbkey.depth) {
VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
clearValue.depthStencil = {(float) params.clearDepth, 0};
}
renderPassInfo.pClearValues = &clearValues[0];
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
@@ -1321,152 +1330,18 @@ void VulkanDriver::stopCapture(int) {
}
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
uint32_t width, uint32_t height, PixelBufferDescriptor&& pbd) {
const VkDevice device = mContext.device;
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, PixelBufferDescriptor&& pbd) {
VulkanRenderTarget* srcTarget = handle_cast<VulkanRenderTarget*>(src);
VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
assert_invariant(srcTexture);
const VkFormat srcFormat = srcTexture->getVkFormat();
const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM;
// Create a host visible, linearly tiled image as a staging area.
VkImageCreateInfo imageInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = srcFormat,
.extent = { width, height, 1 },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage stagingImage;
vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
VkMemoryRequirements memReqs;
VkDeviceMemory stagingMemory;
vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = mContext.selectMemoryType(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT)
};
vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
vkBindImageMemory(device, stagingImage, stagingMemory, 0);
// TODO: don't flush/wait here, this should be asynchronous
mContext.commands->flush();
mContext.commands->wait();
// Transition the staging image layout.
const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
transitionImageLayout(cmdbuffer, {
.image = stagingImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.subresources = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
.srcAccessMask = 0,
.dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
});
const VulkanAttachment srcAttachment = srcTarget->getColor(0);
VkImageCopy imageCopyRegion = {
.srcSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = srcAttachment.level,
.baseArrayLayer = srcAttachment.layer,
.layerCount = 1,
},
.srcOffset = {
.x = (int32_t) x,
.y = (int32_t) (srcTarget->getExtent().height - (height + y)),
},
.dstSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1,
},
.extent = {
.width = width,
.height = height,
.depth = 1,
},
};
// Transition the source image layout (which might be the swap chain)
const VkImageSubresourceRange srcRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = srcAttachment.level,
.levelCount = 1,
.baseArrayLayer = srcAttachment.layer,
.layerCount = 1,
};
srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
// Perform the copy into the staging area. At this point we know that the src layout is
// TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
UTILS_UNUSED_IN_RELEASE VkExtent2D srcExtent = srcAttachment.getExtent2D();
assert_invariant(imageCopyRegion.srcOffset.x + imageCopyRegion.extent.width <= srcExtent.width);
assert_invariant(imageCopyRegion.srcOffset.y + imageCopyRegion.extent.height <= srcExtent.height);
vkCmdCopyImage(cmdbuffer, srcAttachment.getImage(),
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_GENERAL,
1, &imageCopyRegion);
// Restore the source image layout. Between driver API calls, color images are always kept in
// UNDEFINED layout or in their "usage default" layout (see comment for getDefaultImageLayout).
srcTexture->transitionLayout(cmdbuffer, srcRange,
getDefaultImageLayout(TextureUsage::COLOR_ATTACHMENT));
// TODO: don't flush/wait here -- we should do this asynchronously
// Flush and wait.
mContext.commands->flush();
mContext.commands->wait();
VkImageSubresource subResource { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT };
VkSubresourceLayout subResourceLayout;
vkGetImageSubresourceLayout(device, stagingImage, &subResource, &subResourceLayout);
// Map image memory so we can start copying from it.
const uint8_t* srcPixels;
vkMapMemory(device, stagingMemory, 0, VK_WHOLE_SIZE, 0, (void**) &srcPixels);
srcPixels += subResourceLayout.offset;
if (!DataReshaper::reshapeImage(&pbd, getComponentType(srcFormat), getComponentCount(srcFormat),
srcPixels, subResourceLayout.rowPitch, width, height, swizzle)) {
utils::slog.e << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
}
vkUnmapMemory(device, stagingMemory);
vkDestroyImage(device, stagingImage, nullptr);
vkFreeMemory(device, stagingMemory, nullptr);
scheduleDestroy(std::move(pbd));
mReadPixels.run(
srcTarget, x, y, width, height, mContext.graphicsQueueFamilyIndex, std::move(pbd),
getTaskHandler(),
[&context = mContext](uint32_t reqs, VkFlags flags) {
return context.selectMemoryType(reqs, flags);
},
[this](PixelBufferDescriptor&& pbd) {
this->scheduleDestroy(std::move(pbd));
});
}
void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
+7 -4
View File
@@ -23,8 +23,10 @@
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanFboCache.h"
#include "VulkanReadPixels.h"
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
#include "VulkanTaskHandler.h"
#include "VulkanUtility.h"
#include "private/backend/Driver.h"
@@ -39,11 +41,14 @@ namespace filament::backend {
class VulkanPlatform;
struct VulkanSamplerGroup;
class VulkanDriver final : public DriverBase {
class VulkanDriver final : public DriverBase, private VulkanTaskHandler::Host {
public:
static Driver* create(VulkanPlatform* platform,
const char* const* ppEnabledExtensions, uint32_t enabledExtensionCount, const Platform::DriverConfig& driverConfig) noexcept;
VulkanDriver(VulkanDriver const&) = delete;
VulkanDriver& operator = (VulkanDriver const&) = delete;
private:
void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
@@ -73,9 +78,6 @@ private:
#include "private/backend/DriverAPI.inc"
VulkanDriver(VulkanDriver const&) = delete;
VulkanDriver& operator = (VulkanDriver const&) = delete;
private:
HandleAllocatorVK mHandleAllocator;
@@ -151,6 +153,7 @@ private:
VulkanSamplerCache mSamplerCache;
VulkanBlitter mBlitter;
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
VulkanReadPixels mReadPixels;
};
} // namespace filament::backend
@@ -17,6 +17,10 @@
#include <bluevk/BlueVK.h> // must be included before vk_mem_alloc
#pragma clang diagnostic push
// Needed because not all clang versions have the warning -Wdeprecated-copy.
#pragma clang diagnostic ignored "-Wunknown-warning-option"
#pragma clang diagnostic ignored "-Wdeprecated-copy"
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
@@ -0,0 +1,261 @@
/*
* Copyright (C) 2023 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 "VulkanReadPixels.h"
#include "DataReshaper.h"
#include "VulkanHandles.h"
#include "VulkanTaskHandler.h"
#include "VulkanTexture.h"
#include <utils/Log.h>
using namespace bluevk;
namespace filament::backend {
VulkanReadPixels::~VulkanReadPixels() noexcept {
assert_invariant(mDevice != VK_NULL_HANDLE);
if (mCommandPool == VK_NULL_HANDLE) {
return;
}
vkDestroyCommandPool(mDevice, mCommandPool, VKALLOC);
}
void VulkanReadPixels::initialize(VkDevice device) {
mDevice = device;
}
void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x, uint32_t const y,
uint32_t const width, uint32_t const height, uint32_t const graphicsQueueFamilyIndex,
PixelBufferDescriptor&& pbd, VulkanTaskHandler& taskHandler,
SelecteMemoryFunction const& selectMemoryFunc,
OnReadCompleteFunction const& readCompleteFunc) {
assert_invariant(mDevice != VK_NULL_HANDLE);
VkDevice device = mDevice;
if (mCommandPool == VK_NULL_HANDLE) {
// Create a command pool if one has not been created.
VkCommandPoolCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
| VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
.queueFamilyIndex = graphicsQueueFamilyIndex,
};
vkCreateCommandPool(device, &createInfo, VKALLOC, &mCommandPool);
}
VkCommandPool cmdpool = mCommandPool;
VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
assert_invariant(srcTexture);
VkFormat const srcFormat = srcTexture->getVkFormat();
bool const swizzle
= srcFormat == VK_FORMAT_B8G8R8A8_UNORM || srcFormat == VK_FORMAT_B8G8R8A8_SRGB;
// Create a host visible, linearly tiled image as a staging area.
VkImageCreateInfo const imageInfo{
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = srcFormat,
.extent = {width, height, 1},
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_LINEAR,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage stagingImage;
vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
VkMemoryRequirements memReqs;
VkDeviceMemory stagingMemory;
vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
VkMemoryAllocateInfo const allocInfo = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memReqs.size,
.memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
| VK_MEMORY_PROPERTY_HOST_CACHED_BIT)};
vkAllocateMemory(device, &allocInfo, VKALLOC, &stagingMemory);
vkBindImageMemory(device, stagingImage, stagingMemory, 0);
VkCommandBuffer cmdbuffer;
VkCommandBufferAllocateInfo const allocateInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = cmdpool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
vkAllocateCommandBuffers(device, &allocateInfo, &cmdbuffer);
VkCommandBufferBeginInfo const binfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(cmdbuffer, &binfo);
transitionImageLayout(cmdbuffer, {
.image = stagingImage,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.subresources = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
.srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
.srcAccessMask = 0,
.dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
});
VulkanAttachment const srcAttachment = srcTarget->getColor(0);
VkImageCopy const imageCopyRegion = {
.srcSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = srcAttachment.level,
.baseArrayLayer = srcAttachment.layer,
.layerCount = 1,
},
.srcOffset = {
.x = (int32_t) x,
.y = (int32_t) (srcTarget->getExtent().height - (height + y)),
},
.dstSubresource = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.layerCount = 1,
},
.extent = {
.width = width,
.height = height,
.depth = 1,
},
};
// Transition the source image layout (which might be the swap chain)
VkImageSubresourceRange const srcRange = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = srcAttachment.level,
.levelCount = 1,
.baseArrayLayer = srcAttachment.layer,
.layerCount = 1,
};
srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
// Perform the copy into the staging area. At this point we know that the src layout is
// TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
UTILS_UNUSED_IN_RELEASE VkExtent2D srcExtent = srcAttachment.getExtent2D();
assert_invariant(imageCopyRegion.srcOffset.x + imageCopyRegion.extent.width <= srcExtent.width);
assert_invariant(
imageCopyRegion.srcOffset.y + imageCopyRegion.extent.height <= srcExtent.height);
vkCmdCopyImage(cmdbuffer, srcAttachment.getImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
stagingImage, VK_IMAGE_LAYOUT_GENERAL, 1, &imageCopyRegion);
// Restore the source image layout. Between driver API calls, color images are always kept in
// UNDEFINED layout or in their "usage default" layout (see comment for getDefaultImageLayout).
srcTexture->transitionLayout(cmdbuffer, srcRange,
getDefaultImageLayout(TextureUsage::COLOR_ATTACHMENT));
vkEndCommandBuffer(cmdbuffer);
VkQueue queue;
vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &queue);
VkSubmitInfo const submitInfo{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 0,
.pWaitSemaphores = VK_NULL_HANDLE,
.pWaitDstStageMask = VK_NULL_HANDLE,
.commandBufferCount = 1,
.pCommandBuffers = &cmdbuffer,
.signalSemaphoreCount = 0,
.pSignalSemaphores = VK_NULL_HANDLE,
};
VkFence fence;
VkFenceCreateInfo const fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
};
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
vkQueueSubmit(queue, 1, &submitInfo, fence);
auto* const pUserBuffer = new PixelBufferDescriptor(std::move(pbd));
auto const waitTaskId = taskHandler.createTask(
[device, width, height, swizzle, srcFormat, fence, stagingImage, stagingMemory, cmdpool,
cmdbuffer, pUserBuffer, readCompleteFunc,
&taskHandler](VulkanTaskHandler::TaskId taskId, void* data) mutable {
PixelBufferDescriptor& p = *pUserBuffer;
vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
VkResult status = vkGetFenceStatus(device, fence);
// Fence hasn't been reached. Try waiting again.
if (status == VK_NOT_READY) {
taskHandler.post(taskId);
return;
}
// Need to abort the readPixels if the device is lost.
if (status == VK_ERROR_DEVICE_LOST) {
utils::slog.e << "Device lost while in VulkanReadPixels::run"
<< utils::io::endl;
taskHandler.completed(taskId);
// Try to free the pbd anyway
readCompleteFunc(std::move(p));
delete pUserBuffer;
return;
}
VkImageSubresource subResource{.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT};
VkSubresourceLayout subResourceLayout;
vkGetImageSubresourceLayout(device, stagingImage, &subResource, &subResourceLayout);
// Map image memory so that we can start copying from it.
uint8_t const* srcPixels;
vkMapMemory(device, stagingMemory, 0, VK_WHOLE_SIZE, 0, (void**) &srcPixels);
srcPixels += subResourceLayout.offset;
if (!DataReshaper::reshapeImage(&p, getComponentType(srcFormat),
getComponentCount(srcFormat), srcPixels,
static_cast<int>(subResourceLayout.rowPitch), static_cast<int>(width),
static_cast<int>(height), swizzle)) {
utils::slog.e << "Unsupported PixelDataFormat or PixelDataType"
<< utils::io::endl;
}
vkUnmapMemory(device, stagingMemory);
vkDestroyImage(device, stagingImage, VKALLOC);
vkFreeMemory(device, stagingMemory, VKALLOC);
vkDestroyFence(device, fence, VKALLOC);
vkFreeCommandBuffers(device, cmdpool, 1, &cmdbuffer);
readCompleteFunc(std::move(p));
delete pUserBuffer;
taskHandler.completed(taskId);
},
nullptr);
taskHandler.post(waitTaskId);
}
}// namespace filament::backend
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
#define TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
#include "VulkanTaskHandler.h"
#include "private/backend/Driver.h"
#include <bluevk/BlueVK.h>
#include <math/vec4.h>
namespace filament::backend {
struct VulkanContext;
struct VulkanRenderTarget;
class VulkanReadPixels {
public:
using OnReadCompleteFunction = std::function<void(PixelBufferDescriptor&&)>;
using SelecteMemoryFunction = std::function<uint32_t(uint32_t, VkFlags)>;
~VulkanReadPixels() noexcept;
void initialize(VkDevice device);
void run(VulkanRenderTarget const* srcTarget, uint32_t x, uint32_t y, uint32_t width,
uint32_t height, uint32_t graphicsQueueFamilyIndex, PixelBufferDescriptor&& pbd,
VulkanTaskHandler& taskHandler, SelecteMemoryFunction const& selectMemoryFunc,
OnReadCompleteFunction const& readCompleteFunc);
private:
VkDevice mDevice = VK_NULL_HANDLE;
VkCommandPool mCommandPool = VK_NULL_HANDLE;
};
}// namespace filament::backend
#endif//TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
@@ -259,48 +259,18 @@ static void getPresentationQueue(VulkanContext& mContext, VulkanSwapChain& sc) {
FixedCapacityVector<VkQueueFamilyProperties> queueFamiliesProperties(queueFamiliesCount);
vkGetPhysicalDeviceQueueFamilyProperties(mContext.physicalDevice, &queueFamiliesCount,
queueFamiliesProperties.data());
uint32_t presentQueueFamilyIndex = 0xffff;
// We prefer the graphics and presentation queues to be the same, so first check if that works.
// On most platforms they must be the same anyway, and this avoids issues with MoltenVK.
VkBool32 supported = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(mContext.physicalDevice, mContext.graphicsQueueFamilyIndex,
sc.surface, &supported);
if (supported) {
presentQueueFamilyIndex = mContext.graphicsQueueFamilyIndex;
}
// Otherwise fall back to separate graphics and presentation queues.
if (presentQueueFamilyIndex == 0xffff) {
for (uint32_t j = 0; j < queueFamiliesCount; ++j) {
vkGetPhysicalDeviceSurfaceSupportKHR(mContext.physicalDevice, j, sc.surface, &supported);
if (supported) {
presentQueueFamilyIndex = j;
break;
}
}
}
ASSERT_POSTCONDITION(presentQueueFamilyIndex != 0xffff,
"This physical device does not support the presentation queue.");
if (mContext.graphicsQueueFamilyIndex != presentQueueFamilyIndex) {
// We assume that the chosen graphics queues are able to present. See also
// https://github.com/google/filament/issues/1532
ASSERT_POSTCONDITION(supported, "Graphics queues do not support presentation.");
// TODO: Strictly speaking, this code path is incorrect. However it is not triggered on any
// Android devices that we've tested with, nor with MoltenVK.
//
// This is incorrect because we created the logical device early on, before we had a handle
// to the rendering surface. Therefore the device was not created with the presentation
// queue family index included in VkDeviceQueueCreateInfo.
//
// This is non-trivial to fix because the driver API allows clients to do certain things
// (e.g. upload a vertex buffer) before the swap chain is created.
//
// https://github.com/google/filament/issues/1532
vkGetDeviceQueue(mContext.device, presentQueueFamilyIndex, 0, &sc.presentQueue);
} else {
sc.presentQueue = mContext.graphicsQueue;
}
ASSERT_POSTCONDITION(sc.presentQueue, "Unable to obtain presentation queue.");
sc.presentQueue = mContext.graphicsQueue;
sc.headlessQueue = VK_NULL_HANDLE;
}
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_VULKANTASKHANDLER_H
#define TNT_FILAMENT_BACKEND_VULKANTASKHANDLER_H
#include "utils/Panic.h"
#include <functional>
#include <map>
#include <memory>
#include <queue>
namespace filament::backend {
class VulkanTaskHandler {
public:
// The Host class is meant to be called on the thread that processes the tasks. It has access to
// the handle function.
class Host {
public:
Host(Host const&) = delete;
Host& operator=(Host const&) = delete;
protected:
Host()
: mHandler(std::make_unique<VulkanTaskHandler>()) {}
void runTaskHandler() noexcept {
mHandler->handle();
}
VulkanTaskHandler& getTaskHandler() noexcept {
return *mHandler;
}
private:
std::unique_ptr<VulkanTaskHandler> mHandler;
};
using TaskId = uint32_t;
using TaskFunc = std::function<void(TaskId, void*)>;
using Task = std::pair<TaskFunc, void*>;
TaskId createTask(TaskFunc const& func, void* data) noexcept {
TaskId const id = mNextTaskId++;
mTasks[id] = std::pair(func, data);
return id;
}
// Task that will never be put on the queue again should be marked as completed by calling this
// function.
void completed(TaskId taskId) noexcept {
assert_invariant(mTasks.find(taskId) != mTasks.end());
mTasks.erase(taskId);
}
void post(TaskId taskId) noexcept {
assert_invariant(mTasks.find(taskId) != mTasks.end());
mTaskQueue.push(taskId);
}
VulkanTaskHandler() = default;
VulkanTaskHandler(VulkanTaskHandler const&) = delete;
VulkanTaskHandler& operator=(VulkanTaskHandler const&) = delete;
private:
inline void handle() {
while (!mTaskQueue.empty()) {
auto taskId = mTaskQueue.front();
mTaskQueue.pop();
// It is possible for taskIds in the queue to refer to a task that has already been
// completed. Just ignore.
if (mTasks.find(taskId) == mTasks.end()) {
continue;
}
auto& [func, data] = mTasks[taskId];
func(taskId, data);
}
}
std::queue<TaskId> mTaskQueue;
std::map<TaskId, Task> mTasks;
uint32_t mNextTaskId = 0;
friend class Host;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_VULKANTASKHANDLER_H
+1 -1
View File
@@ -262,7 +262,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
api.stopCapture(0);
api.endFrame(0);
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0xC7E34C43, true);
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0x6CEFAC8F, true);
flushAndWait();
getDriver().purge();
+57 -15
View File
@@ -104,22 +104,24 @@ public:
}
/**
* Computes the bounding box of a box transformed by a rigid transform
* Transform a Box by a linear transform and a translation.
*
* @param m a 3x3 matrix, the linear transform
* @param t a float3, the translation
* @param box the box to transform
* @param m a 4x4 matrix that must be a rigid transform
* @return the bounding box of the transformed box.
* Result is undefined if \p m is not a rigid transform
* @return the bounding box of the transformed box
*/
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept;
static Box transform(const math::mat3f& m, math::float3 const& t, const Box& box) noexcept {
return { m * box.center + t, abs(m) * box.halfExtent };
}
/**
* Computes the bounding box of a box transformed by a rigid transform
* @param box the box to transform
* @param m a 3x3 matrix that must be a rigid transform
* @return the bounding box of the transformed box.
* Result is undefined if \p m is not a rigid transform
* @deprecated Use transform() instead
* @see transform()
*/
friend Box rigidTransform(Box const& box, const math::mat3f& m) noexcept;
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept {
return transform(m.upperLeft(), m[3].xyz, box);
}
};
/**
@@ -174,7 +176,18 @@ struct UTILS_PUBLIC Aabb {
/**
* Returns the 8 corner vertices of the AABB.
*/
Corners getCorners() const;
Corners getCorners() const {
return Aabb::Corners{ .vertices = {
{ min.x, min.y, min.z },
{ max.x, min.y, min.z },
{ min.x, max.y, min.z },
{ max.x, max.y, min.z },
{ min.x, min.y, max.z },
{ max.x, min.y, max.z },
{ min.x, max.y, max.z },
{ max.x, max.y, max.z },
}};
}
/**
* Returns whether the box contains a given point.
@@ -182,15 +195,44 @@ struct UTILS_PUBLIC Aabb {
* @param p the point to test
* @return the maximum signed distance to the box. Negative if p is in the box
*/
float contains(math::float3 p) const noexcept;
float contains(math::float3 p) const noexcept {
float d = min.x - p.x;
d = std::max(d, min.y - p.y);
d = std::max(d, min.z - p.z);
d = std::max(d, p.x - max.x);
d = std::max(d, p.y - max.y);
d = std::max(d, p.z - max.z);
return d;
}
/**
* Applies an affine transformation to the AABB.
*
* @param m the 4x4 transformation to apply
* @param m the 3x3 transformation to apply
* @param t the translation
* @return the transformed box
*/
Aabb transform(const math::mat4f& m) const noexcept;
static Aabb transform(const math::mat3f& m, math::float3 const& t, const Aabb& box) noexcept {
// Fast AABB transformation per Jim Arvo in Graphics Gems (1990).
Aabb result{ t, t };
for (size_t col = 0; col < 3; ++col) {
for (size_t row = 0; row < 3; ++row) {
const float a = m[col][row] * box.min[col];
const float b = m[col][row] * box.max[col];
result.min[row] += a < b ? a : b;
result.max[row] += a < b ? b : a;
}
}
return result;
}
/**
* @deprecated Use transform() instead
* @see transform()
*/
Aabb transform(const math::mat4f& m) const noexcept {
return transform(m.upperLeft(), m[3].xyz, *this);
}
};
} // namespace filament
+3
View File
@@ -171,6 +171,9 @@ public:
//! Indicates whether this material is double-sided.
bool isDoubleSided() const noexcept;
//! Indicates whether this material uses alpha to coverage.
bool isAlphaToCoverageEnabled() const noexcept;
//! Returns the alpha mask threshold used when the blending mode is set to masked.
float getMaskThreshold() const noexcept;
+11 -2
View File
@@ -119,6 +119,12 @@ public:
public:
enum Result { Error = -1, Success = 0 };
/**
* Default render channel
* @see Builder::channel()
*/
static constexpr uint8_t DEFAULT_CHANNEL = 2u;
/**
* Creates a builder for renderable components.
*
@@ -231,10 +237,13 @@ public:
/**
* Set the channel this renderable is associated to. There can be 4 channels.
* All renderables in a given channel are rendered together, regardless of anything else.
* They are sorted as usual withing a channel.
* They are sorted as usual within a channel.
* Channels work similarly to priorities, except that they enforce the strongest ordering.
*
* @param channel clamped to the range [0..3], defaults to 0.
* Channels 0 and 1 may not have render primitives using a material with `refractionType`
* set to `screenspace`.
*
* @param channel clamped to the range [0..3], defaults to 2.
*
* @return Builder reference for chaining calls.
*
-71
View File
@@ -1,71 +0,0 @@
/*
* Copyright (C) 2015 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 <filament/Box.h>
using namespace filament::math;
namespace filament {
Box rigidTransform(Box const& UTILS_RESTRICT box, const mat4f& UTILS_RESTRICT m) noexcept {
const mat3f u(m.upperLeft());
return { u * box.center + m[3].xyz, abs(u) * box.halfExtent };
}
Box rigidTransform(Box const& UTILS_RESTRICT box, const mat3f& UTILS_RESTRICT u) noexcept {
return { u * box.center, abs(u) * box.halfExtent };
}
Aabb::Corners Aabb::getCorners() const {
return Aabb::Corners{ .vertices = {
{ min.x, min.y, min.z },
{ max.x, min.y, min.z },
{ min.x, max.y, min.z },
{ max.x, max.y, min.z },
{ min.x, min.y, max.z },
{ max.x, min.y, max.z },
{ min.x, max.y, max.z },
{ max.x, max.y, max.z },
}};
}
float Aabb::contains(float3 p) const noexcept {
float d = min.x - p.x;
d = std::max(d, min.y - p.y);
d = std::max(d, min.z - p.z);
d = std::max(d, p.x - max.x);
d = std::max(d, p.y - max.y);
d = std::max(d, p.z - max.z);
return d;
}
// Fast AABB transformation per Jim Arvo in Graphics Gems (1990).
Aabb Aabb::transform(const mat4f& mat) const noexcept {
const float3 translation = mat[3].xyz;
const mat3f upperLeft = mat.upperLeft();
Aabb result = { translation, translation };
for (size_t col = 0; col < upperLeft.getColumnCount(); ++col) {
for (size_t row = 0; row < upperLeft.getRowCount(); ++row) {
const float a = upperLeft[col][row] * min[col];
const float b = upperLeft[col][row] * max[col];
result.min[row] += a < b ? a : b;
result.max[row] += a < b ? b : a;
}
}
return result;
}
} // namespace filament
+4
View File
@@ -72,6 +72,10 @@ bool Material::isDoubleSided() const noexcept {
return downcast(this)->isDoubleSided();
}
bool Material::isAlphaToCoverageEnabled() const noexcept {
return downcast(this)->isAlphaToCoverageEnabled();
}
float Material::getMaskThreshold() const noexcept {
return downcast(this)->getMaskThreshold();
}
+8
View File
@@ -246,6 +246,14 @@ bool MaterialParser::getMaskThreshold(float* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialMaskThreshold, value);
}
bool MaterialParser::getAlphaToCoverageSet(bool* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialAlphaToCoverageSet, value);
}
bool MaterialParser::getAlphaToCoverage(bool* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialAlphaToCoverage, value);
}
bool MaterialParser::hasShadowMultiplier(bool* value) const noexcept {
return mImpl.getFromSimpleChunk(ChunkType::MaterialShadowMultiplier, value);
}
+2
View File
@@ -88,6 +88,8 @@ public:
bool getShading(Shading*) const noexcept;
bool getBlendingMode(BlendingMode*) const noexcept;
bool getMaskThreshold(float*) const noexcept;
bool getAlphaToCoverageSet(bool*) const noexcept;
bool getAlphaToCoverage(bool*) const noexcept;
bool hasShadowMultiplier(bool*) const noexcept;
bool getRequiredAttributes(AttributeBitset*) const noexcept;
bool getRefractionMode(RefractionMode* value) const noexcept;
+1 -2
View File
@@ -60,9 +60,8 @@ void PerShadowMapUniforms::prepareCamera(Transaction const& transaction,
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.clipFromWorldMatrix = clipFromWorld; // projection * view
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
s.clipTransform = camera.clipTransfrom;
s.cameraPosition = float3{ camera.getPosition() };
s.worldOffset = camera.getWorldOffset();
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
+5 -11
View File
@@ -76,9 +76,8 @@ void PerViewUniforms::prepareCamera(FEngine& engine, const CameraInfo& camera) n
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.clipFromWorldMatrix = clipFromWorld; // projection * view
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldOrigin));
s.clipTransform = camera.clipTransfrom;
s.cameraPosition = float3{ camera.getPosition() };
s.worldOffset = camera.getWorldOffset();
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
@@ -130,15 +129,10 @@ void PerViewUniforms::prepareTemporalNoise(FEngine& engine,
}
void PerViewUniforms::prepareFog(float3 const& cameraPosition, FogOptions const& options) noexcept {
// this can't be too high because we need density / heightFalloff to produce something
// close to fogOptions.density in the fragment shader which use 16-bits floats.
constexpr float epsilon = 0.001f;
const float heightFalloff = std::max(epsilon, options.heightFalloff);
const float heightFalloff = std::max(0.0f, options.heightFalloff);
// precalculate the constant part of density integral and correct for exp2() in the shader
const float density = ((options.density / heightFalloff) *
std::exp(-heightFalloff * (cameraPosition.y - options.height)))
* float(1.0f / F_LN2);
// precalculate the constant part of density integral
const float density = -heightFalloff * (cameraPosition.y - options.height);
auto& s = mUniforms.edit();
s.fogStart = options.distance;
@@ -146,7 +140,7 @@ void PerViewUniforms::prepareFog(float3 const& cameraPosition, FogOptions const&
s.fogHeight = options.height;
s.fogHeightFalloff = heightFalloff;
s.fogColor = options.color;
s.fogDensity = density;
s.fogDensity = { options.density, density, options.density * std::exp(density) };
s.fogInscatteringStart = options.inScatteringStart;
s.fogInscatteringSize = options.inScatteringSize;
s.fogColorFromIbl = options.fogColorFromIbl ? 1.0f : 0.0f;
+6 -2
View File
@@ -86,11 +86,12 @@ void RenderPass::setScissorViewport(backend::Viewport viewport) noexcept {
}
void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandTypeFlags) noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
assert_invariant(mRenderableSoa);
utils::Range<uint32_t> vr = mVisibleRenderables;
utils::Range<uint32_t> const vr = mVisibleRenderables;
// trace the number of visible renderables
SYSTRACE_VALUE32("visibleRenderables", vr.size());
if (UTILS_UNLIKELY(vr.empty())) {
@@ -129,7 +130,7 @@ void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandT
work(vr.first, vr.size());
} else {
auto* jobCommandsParallel = jobs::parallel_for(js, nullptr, vr.first, (uint32_t)vr.size(),
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 4>());
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 5>());
js.runAndWait(jobCommandsParallel);
}
@@ -375,6 +376,8 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm
FScene::VisibleMaskType visibilityMask,
float3 cameraPosition, float3 cameraForward) noexcept {
SYSTRACE_CALL();
// generateCommands() writes both the draw and depth commands simultaneously such that
// we go throw the list of renderables just once.
// (in principle, we could have split this method into two, at the cost of going through
@@ -723,6 +726,7 @@ UTILS_NOINLINE // no need to be inlined
void RenderPass::Executor::execute(backend::DriverApi& driver,
const Command* first, const Command* last) const noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
if (first != last) {
SYSTRACE_VALUE32("commandCount", last - first);
+3 -3
View File
@@ -389,9 +389,9 @@ private:
void resize(size_t count) noexcept;
void instanceify(FEngine& engine) noexcept;
// on 64-bits systems, we process batches of 256 (64 bytes) cache-lines, or 512 (32 bytes) commands
// on 32-bits systems, we process batches of 512 (32 bytes) cache-lines, or 512 (32 bytes) commands
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 512;
// we choose the command count per job to minimize JobSystem overhead.
// on a Pixel 4, 2048 commands is about half a millisecond of processing.
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 2048;
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE =
sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT;
+5 -2
View File
@@ -246,10 +246,13 @@ std::pair<FrameGraphId<FrameGraphTexture>, bool> RendererUtils::refractionPass(
auto input = blackboard.get<FrameGraphTexture>("color");
FrameGraphId<FrameGraphTexture> output;
// find the first refractive object
// find the first refractive object in channel 2
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
[](auto const& command) {
return (command.key & RenderPass::PASS_MASK) < uint64_t(RenderPass::Pass::REFRACT);
constexpr uint64_t mask = RenderPass::CHANNEL_MASK | RenderPass::PASS_MASK;
constexpr uint64_t channel = uint64_t(RenderableManager::Builder::DEFAULT_CHANNEL) << RenderPass::CHANNEL_SHIFT;
constexpr uint64_t value = channel | uint64_t(RenderPass::Pass::REFRACT);
return (command.key & mask) < value;
});
const bool hasScreenSpaceRefraction =
+87 -83
View File
@@ -58,7 +58,7 @@ ShadowMap::ShadowMap(FEngine& engine) noexcept
void ShadowMap::terminate(FEngine& engine) {
Entity entities[] = { mCamera->getEntity(), mDebugCamera->getEntity() };
for (Entity e : entities) {
for (Entity const e : entities) {
engine.destroyCameraComponent(e);
}
engine.getEntityManager().destroy(sizeof(entities) / sizeof(Entity), entities);
@@ -112,12 +112,12 @@ math::mat4f ShadowMap::getPointLightViewMatrix(backend::TextureCubemapFace face,
ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
ShadowMapInfo const& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept {
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept {
// Note: we keep the polygon offset even with VSM as it seems to help.
auto& lcm = engine.getLightManager();
FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::Instance const li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::ShadowParams params = lcm.getShadowParams(li);
#ifndef NDEBUG
@@ -134,8 +134,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// Adjust the camera's projection for the light's shadowFar
const mat4f cullingProjection{ [&](auto p) {
if (params.options.shadowFar > 0.0f) {
float n = camera.zn;
float f = params.options.shadowFar;
float const n = camera.zn;
float const f = params.options.shadowFar;
// orthographic projection
assert_invariant(std::abs(p[2].w) <= std::numeric_limits<float>::epsilon());
p[2].z = 2.0f / (n - f);
@@ -153,10 +153,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = getDirectionalLightViewMatrix(direction);
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, scene, sceneInfo);
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
const Aabb wsShadowCastersVolume = sceneInfo.wsShadowCastersVolume;
const Aabb wsShadowReceiversVolume = sceneInfo.wsShadowReceiversVolume;
@@ -177,7 +174,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// compute the intersection of the shadow receivers' volume with the view volume
// in world space. This returns a set of points on the convex-hull of the intersection.
size_t vertexCount = intersectFrustumWithBox(wsClippedShadowReceiverVolume,
size_t const vertexCount = intersectFrustumWithBox(wsClippedShadowReceiverVolume,
wsFrustum, wsViewFrustumVertices, wsShadowReceiversVolume);
if (UTILS_UNLIKELY(vertexCount < 4)) {
@@ -206,7 +203,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
}
for (size_t i = 0; i < vertexCount; ++i) {
// far: figure out the farthest shadow receivers
float3 v = mat4f::project(MvAtOrigin, wsClippedShadowReceiverVolume[i]);
float3 const v = mat4f::project(MvAtOrigin, wsClippedShadowReceiverVolume[i]);
lsLightFrustumBounds.min.z = std::min(lsLightFrustumBounds.min.z, v.z);
if constexpr (USE_DEPTH_CLAMP) {
// tighten the shadow receiver volume further
@@ -360,7 +357,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
if (params.options.stable) {
// Use the world origin as reference point, fixed w.r.t. the camera
snapLightFrustum(s, o, Mv, -camera.getWorldOffset(),
snapLightFrustum(s, o, Mv, camera.worldOrigin[3].xyz,
1.0f / float(shadowMapInfo.shadowDimension));
}
@@ -487,7 +484,7 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
filament::CameraInfo const&,
const ShadowMapInfo& shadowMapInfo,
FScene const& scene, SceneInfo sceneInfo) noexcept {
@@ -499,8 +496,17 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
const FLightManager::ShadowParams& params = lcm.getShadowParams(li);
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
// find decent near/far
ShadowMap::updateSceneInfoSpot(Mv, scene, sceneInfo);
// We only keep this for reference. updateSceneInfoSpot() is quite expensive on large scenes
// currently, and only needed to find a near/far. Instead, we just use a small near and the
// radius as far.
// TODO: Another potential solution would be to visit only the part of the scene that's visible
// by the light -- which should be much smaller.
if constexpr (false) {
// find decent near/far
ShadowMap::updateSceneInfoSpot(Mv, scene, sceneInfo);
} else {
sceneInfo.lsNearFar = { -0.01f, -radius };
}
// if the scene was empty, near > far
mHasVisibleShadows = -sceneInfo.lsNearFar[0] < -sceneInfo.lsNearFar[1];
@@ -509,22 +515,22 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
}
// FIXME: we need a configuration for minimum near plane (for now hardcoded to 1cm)
float nearPlane = std::max(0.01f, -sceneInfo.lsNearFar[0]);
float farPlane = std::min(radius, -sceneInfo.lsNearFar[1]);
float const nearPlane = std::max(0.01f, -sceneInfo.lsNearFar[0]);
float const farPlane = std::min(radius, -sceneInfo.lsNearFar[1]);
auto outerConeAngle = lcm.getSpotLightOuterCone(li);
return updatePunctual(Mv, outerConeAngle, nearPlane, farPlane, shadowMapInfo, params);
}
ShadowMap::ShaderParameters ShadowMap::updatePoint(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera, const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo, uint8_t face) noexcept {
const FScene::LightSoa& lightData, size_t index, filament::CameraInfo const&,
const ShadowMapInfo& shadowMapInfo, FScene const& scene, uint8_t face) noexcept {
// check if this shadow map has anything to render
mHasVisibleShadows = false;
FScene::RenderableSoa const& UTILS_RESTRICT soa = scene.getRenderableData();
auto const* const UTILS_RESTRICT visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t c = soa.size();
size_t const c = soa.size();
for (size_t i = 0; i < c; i++) {
if (visibleMasks[i] & VISIBLE_DYN_SHADOW_RENDERABLE) {
mHasVisibleShadows = true;
@@ -702,18 +708,12 @@ mat4f ShadowMap::directionalLightFrustum(float near, float far) noexcept {
return m;
}
float2 ShadowMap::computeNearFar(const mat4f& view,
Aabb const& wsShadowCastersVolume) noexcept {
const Aabb::Corners wsSceneCastersCorners = wsShadowCastersVolume.getCorners();
return computeNearFar(view, wsSceneCastersCorners.data(), wsSceneCastersCorners.size());
}
float2 ShadowMap::computeNearFar(const mat4f& view,
float3 const* wsVertices, size_t count) noexcept {
float2 nearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
for (size_t i = 0; i < count; i++) {
// we're on the z axis in light space (looking down to -z)
float c = mat4f::project(view, wsVertices[i]).z;
float const c = mat4f::project(view, wsVertices[i]).z;
nearFar.x = std::max(nearFar.x, c); // near
nearFar.y = std::min(nearFar.y, c); // far
}
@@ -726,7 +726,7 @@ float2 ShadowMap::computeNearFarOfWarpSpace(mat4f const& lightView,
#pragma nounroll
for (size_t i = 0; i < count; i++) {
// we're on the y-axis in light space (looking down to +y)
float c = mat4f::project(lightView, wsVertices[i]).y;
float const c = mat4f::project(lightView, wsVertices[i]).y;
nearFar.x = std::min(nearFar.x, c);
nearFar.y = std::max(nearFar.y, c);
}
@@ -791,11 +791,11 @@ void ShadowMap::intersectWithShadowCasters(Aabb& UTILS_RESTRICT lightFrustum,
// first intersect wsShadowCastersVolume with the light's frustum, otherwise we end-up
// transforming vertices that are "outside" the frustum, and that's forbidden.
FrustumBoxIntersection wsClippedShadowCasterVolumeVertices;
size_t vertexCount = intersectFrustumWithBox(wsClippedShadowCasterVolumeVertices,
size_t const vertexCount = intersectFrustumWithBox(wsClippedShadowCasterVolumeVertices,
wsLightFrustum, wsLightFrustumCorners, wsShadowCastersVolume);
// compute shadow-caster bounds in light space
Aabb box = compute2DBounds(lightView, wsClippedShadowCasterVolumeVertices.data(), vertexCount);
Aabb const box = compute2DBounds(lightView, wsClippedShadowCasterVolumeVertices.data(), vertexCount);
// intersect shadow-caster and current light frustum bounds
lightFrustum.min.xy = max(box.min.xy, lightFrustum.min.xy);
@@ -809,7 +809,7 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
// matrix to convert: ndc -> camera -> world
float near = csNearFar.x;
float far = csNearFar.y;
float3 csViewFrustumCorners[8] = {
float3 const csViewFrustumCorners[8] = {
{ -1, -1, far },
{ 1, -1, far },
{ -1, 1, far },
@@ -819,7 +819,7 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
{ -1, 1, near },
{ 1, 1, near },
};
for (float3 c : csViewFrustumCorners) {
for (float3 const c : csViewFrustumCorners) {
*out++ = mat4f::project(projectionViewInverse, c);
}
}
@@ -869,7 +869,7 @@ size_t ShadowMap::intersectFrustumWithBox(
// a) Keep the frustum's vertices that are known to be inside the scene's box
UTILS_NOUNROLL
for (size_t i = 0; i < 8; i++) {
float3 p = wsFrustumCorners[i];
float3 const p = wsFrustumCorners[i];
outVertices[vertexCount] = p;
if ((p.x >= wsBox.min.x && p.x <= wsBox.max.x) &&
(p.y >= wsBox.min.y && p.y <= wsBox.max.y) &&
@@ -889,14 +889,14 @@ size_t ShadowMap::intersectFrustumWithBox(
// the frustum. This actually often happens due to fitting light-space
// We fudge the distance to the plane by a small amount.
#pragma nounroll
for (float3 p : wsSceneReceiversCorners) {
for (float3 const p : wsSceneReceiversCorners) {
outVertices[vertexCount] = p;
float l = dot(wsFrustumPlanes[0].xyz, p) + wsFrustumPlanes[0].w;
float b = dot(wsFrustumPlanes[1].xyz, p) + wsFrustumPlanes[1].w;
float r = dot(wsFrustumPlanes[2].xyz, p) + wsFrustumPlanes[2].w;
float t = dot(wsFrustumPlanes[3].xyz, p) + wsFrustumPlanes[3].w;
float f = dot(wsFrustumPlanes[4].xyz, p) + wsFrustumPlanes[4].w;
float n = dot(wsFrustumPlanes[5].xyz, p) + wsFrustumPlanes[5].w;
float const l = dot(wsFrustumPlanes[0].xyz, p) + wsFrustumPlanes[0].w;
float const b = dot(wsFrustumPlanes[1].xyz, p) + wsFrustumPlanes[1].w;
float const r = dot(wsFrustumPlanes[2].xyz, p) + wsFrustumPlanes[2].w;
float const t = dot(wsFrustumPlanes[3].xyz, p) + wsFrustumPlanes[3].w;
float const f = dot(wsFrustumPlanes[4].xyz, p) + wsFrustumPlanes[4].w;
float const n = dot(wsFrustumPlanes[5].xyz, p) + wsFrustumPlanes[5].w;
if ((l <= EPSILON) && (b <= EPSILON) &&
(r <= EPSILON) && (t <= EPSILON) &&
(f <= EPSILON) && (n <= EPSILON)) {
@@ -981,7 +981,7 @@ size_t ShadowMap::intersectFrustum(
const float3 s0{ segmentsVertices[segment.v0] };
const float3 s1{ segmentsVertices[segment.v1] };
// each segment should only intersect with 2 quads at most
size_t maxVertexCount = vertexCount + 2;
size_t const maxVertexCount = vertexCount + 2;
for (size_t j = 0; j < 6 && vertexCount < maxVertexCount; ++j) {
const Quad quad = sBoxQuads[j];
const float3 t0{ quadsVertices[quad.v0] };
@@ -1036,8 +1036,8 @@ inline bool ShadowMap::intersectSegmentWithTriangle(float3& UTILS_RESTRICT p,
bool ShadowMap::intersectSegmentWithPlanarQuad(float3& UTILS_RESTRICT p,
float3 s0, float3 s1, float3 t0, float3 t1, float3 t2, float3 t3) noexcept {
bool hit = intersectSegmentWithTriangle(p, s0, s1, t0, t1, t2) ||
intersectSegmentWithTriangle(p, s0, s1, t0, t2, t3);
bool const hit = intersectSegmentWithTriangle(p, s0, s1, t0, t1, t2) ||
intersectSegmentWithTriangle(p, s0, s1, t0, t2, t3);
return hit;
}
@@ -1070,7 +1070,7 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
// The Jacobian is not constant, so we evaluate it in the center of the shadow-map texture.
// It might be better to do this computation in the vertex shader.
float3 p = {0.5, 0.5, 0.0};
float3 const p = { 0.5f, 0.5f, 0.0f };
const float ures = 1.0f / float(shadowDimension);
const float vres = 1.0f / float(shadowDimension);
@@ -1115,9 +1115,9 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
0.0f, j * Z * sx, j * dz * sx
});
float3 Jx = J[0] * ures;
float3 Jy = J[1] * vres;
UTILS_UNUSED float3 Jz = J[2] * dres;
float3 const Jx = J[0] * ures;
float3 const Jy = J[1] * vres;
UTILS_UNUSED float3 const Jz = J[2] * dres;
const float s = std::max(length(Jx), length(Jy));
return s;
}
@@ -1128,13 +1128,13 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
SYSTRACE_CALL();
using State = FRenderableManager::Visibility;
FScene::RenderableSoa const& UTILS_RESTRICT soa = scene.getRenderableData();
float3 const* const UTILS_RESTRICT worldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
float3 const* const UTILS_RESTRICT worldAABBExtent = soa.data<FScene::WORLD_AABB_EXTENT>();
uint8_t const* const UTILS_RESTRICT layers = soa.data<FScene::LAYERS>();
State const* const UTILS_RESTRICT visibility = soa.data<FScene::VISIBILITY_STATE>();
auto const* const UTILS_RESTRICT visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t c = soa.size();
FScene::RenderableSoa const& soa = scene.getRenderableData();
float3 const* const worldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
float3 const* const worldAABBExtent = soa.data<FScene::WORLD_AABB_EXTENT>();
uint8_t const* const layers = soa.data<FScene::LAYERS>();
State const* const visibility = soa.data<FScene::VISIBILITY_STATE>();
auto const* const visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t const c = soa.size();
for (size_t i = 0; i < c; i++) {
if (layers[i] & visibleLayers) {
const Aabb aabb{ worldAABBCenter[i] - worldAABBExtent[i],
@@ -1149,46 +1149,47 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
}
}
void ShadowMap::initSceneInfo(ShadowMap::SceneInfo& sceneInfo,
uint8_t visibleLayers, FScene const& scene, mat4f const& viewMatrix) {
sceneInfo.csNearFar = { -1.0f, 1.0f };
sceneInfo.lsNearFar = {};
sceneInfo.visibleLayers = visibleLayers;
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
ShadowMap::SceneInfo::SceneInfo(
FScene const& scene, uint8_t visibleLayers, mat4f const& viewMatrix) noexcept
: vsNearFar(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()),
visibleLayers(visibleLayers) {
// the code below only works with affine transforms
assert_invariant(transpose(viewMatrix)[3] == float4(0, 0, 0, 1));
// We assume the light is at the origin to compute the SceneInfo. This is consumed later by
// computeShadowCameraDirectional() which takes this into account.
// Compute scene bounds in world space, as well as the light-space and view-space near/far planes
sceneInfo.wsShadowCastersVolume = {};
sceneInfo.wsShadowReceiversVolume = {};
visitScene(scene, sceneInfo.visibleLayers,
wsShadowCastersVolume = {};
wsShadowReceiversVolume = {};
ShadowMap::visitScene(scene, visibleLayers,
[&](Aabb caster, Culler::result_type) {
sceneInfo.wsShadowCastersVolume.min =
min(sceneInfo.wsShadowCastersVolume.min, caster.min);
sceneInfo.wsShadowCastersVolume.max =
max(sceneInfo.wsShadowCastersVolume.max, caster.max);
wsShadowCastersVolume.min = min(wsShadowCastersVolume.min, caster.min);
wsShadowCastersVolume.max = max(wsShadowCastersVolume.max, caster.max);
},
[&](Aabb receiver, Culler::result_type) {
sceneInfo.wsShadowReceiversVolume.min =
min(sceneInfo.wsShadowReceiversVolume.min, receiver.min);
sceneInfo.wsShadowReceiversVolume.max =
max(sceneInfo.wsShadowReceiversVolume.max, receiver.max);
float2 nf = ShadowMap::computeNearFar(viewMatrix, receiver);
sceneInfo.vsNearFar.x = std::max(sceneInfo.vsNearFar.x, nf.x);
sceneInfo.vsNearFar.y = std::min(sceneInfo.vsNearFar.y, nf.y);
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
auto r = Aabb::transform(viewMatrix.upperLeft(), viewMatrix[3].xyz, receiver);
vsNearFar.x = std::max(vsNearFar.x, r.max.z);
vsNearFar.y = std::min(vsNearFar.y, r.min.z);
}
);
}
void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
SceneInfo& sceneInfo) {
// the code below only works with affine transforms
assert_invariant(transpose(Mv)[3] == float4(0, 0, 0, 1));
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type) {
float2 nf = ShadowMap::computeNearFar(Mv, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, nf.x); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, nf.y); // far
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, r.max.z); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, r.min.z); // far
},
[&](Aabb receiver, Culler::result_type) {
}
@@ -1197,14 +1198,17 @@ void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
SceneInfo& sceneInfo) {
// the code below only works with affine transforms
assert_invariant(transpose(Mv)[3] == float4(0, 0, 0, 1));
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type mask) {
if (mask & VISIBLE_DYN_SHADOW_RENDERABLE) {
float2 nf = ShadowMap::computeNearFar(Mv, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, nf.x); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, nf.y); // far
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, r.max.z); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, r.min.z); // far
}
},
[&](Aabb receiver, Culler::result_type) {
+12 -18
View File
@@ -95,16 +95,20 @@ public:
};
struct SceneInfo {
SceneInfo() noexcept = default;
SceneInfo(FScene const& scene, uint8_t visibleLayers, math::mat4f const& viewMatrix) noexcept;
// scratch data: The near and far planes, in clip space, to use for this shadow map
math::float2 csNearFar = { -1.0f, 1.0f };
// scratch data: light's near/far expressed in light-space, calculated from the scene's
// content assuming the light is at the origin.
math::float2 lsNearFar{};
math::float2 lsNearFar;
// scratch data: Viewing camera's near/far expressed in view-space, calculated from the
// Viewing camera's near/far expressed in view-space, calculated from the
// scene's content.
math::float2 vsNearFar{};
math::float2 vsNearFar;
// World-space shadow-casters volume
Aabb wsShadowCastersVolume;
@@ -136,8 +140,8 @@ public:
ShaderParameters updateDirectional(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
ShadowMapInfo const& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept;
ShaderParameters updateSpot(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
@@ -145,11 +149,9 @@ public:
const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo sceneInfo) noexcept;
ShaderParameters updatePoint(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera, const ShadowMapInfo& shadowMapInfo,
FScene const& scene,
SceneInfo sceneInfo, uint8_t face) noexcept;
ShadowMap::ShaderParameters updatePoint(FEngine& engine,
const FScene::LightSoa& lightData, size_t index, filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo, FScene const& scene, uint8_t face) noexcept;
// Do we have visible shadows. Valid after calling update().
bool hasVisibleShadows() const noexcept { return mHasVisibleShadows; }
@@ -160,11 +162,6 @@ public:
// use only for debugging
FCamera const& getDebugCamera() const noexcept { return *mDebugCamera; }
// Call once per frame to populate the SceneInfo struct, then pass to update().
// This computes values constant across all shadow maps.
static void initSceneInfo(ShadowMap::SceneInfo& sceneInfo, uint8_t visibleLayers,
FScene const& scene, math::mat4f const& viewMatrix);
// Update SceneInfo struct for a given light
static void updateSceneInfoDirectional(const math::mat4f& Mv, FScene const& scene,
SceneInfo& sceneInfo);
@@ -230,9 +227,6 @@ private:
static inline void computeFrustumCorners(math::float3* out,
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
Aabb const& wsShadowCastersVolume) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
math::float3 const* wsVertices, size_t count) noexcept;
+24 -13
View File
@@ -76,15 +76,16 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FVie
calculateTextureRequirements(engine, view, lightData);
// Compute scene-dependent values shared across all shadow maps
ShadowMap::initSceneInfo(mSceneInfo,
view.getVisibleLayers(), *view.getScene(), cameraInfo.view);
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers(), cameraInfo.view };
shadowTechnique |= updateCascadeShadowMaps(
engine, view, cameraInfo, renderableData, lightData, mSceneInfo);
engine, view, cameraInfo, renderableData, lightData, info);
shadowTechnique |= updateSpotShadowMaps(
engine, lightData);
mSceneInfo = info;
return shadowTechnique;
}
@@ -207,7 +208,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
},
[this, &engine, &view, vsmShadowOptions,
scene, mainCameraInfo, userTime, passTemplate = pass](
FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
FrameGraphResources const&, auto const& data, DriverApi& driver) {
// Note: we could almost parallel_for the loop below, the problem currently is
// that updatePrimitivesLod() updates temporary global state.
@@ -435,11 +436,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept {
FScene::LightSoa& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
FScene* scene = view.getScene();
auto& lcm = engine.getLightManager();
FLightManager::Instance directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::ShadowOptions const& options = lcm.getShadowOptions(directionalLight);
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
@@ -458,8 +459,18 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// entire camera frustum, as if we only had a single cascade.
ShadowMap& shadowMap = *mCascadeShadowMaps[0];
const auto direction = lightData.elementAt<FScene::DIRECTION>(0);
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
shadowMap.updateDirectional(mEngine,
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
hasVisibleShadows = shadowMap.hasVisibleShadows();
@@ -533,7 +544,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
auto shaderParameters = shadowMap.updateDirectional(mEngine,
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
if (shadowMap.hasVisibleShadows()) {
const size_t shadowIndex = shadowMap.getShadowIndex();
@@ -560,7 +571,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
}
// screen-space contact shadows for the directional light
float screenSpaceShadowDistance = options.maxShadowDistance;
float const screenSpaceShadowDistance = options.maxShadowDistance;
if (options.screenSpaceContactShadows) {
shadowTechnique |= ShadowTechnique::SCREEN_SPACE;
}
@@ -749,7 +760,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
};
auto shaderParameters = shadowMap.updatePoint(mEngine, lightData, lightIndex,
mainCameraInfo, shadowMapInfo, *view.getScene(), sceneInfo, face);
mainCameraInfo, shadowMapInfo, *view.getScene(), face);
// and if we need to generate it, update all the UBO data
@@ -812,8 +823,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
return shadowTechnique;
}
void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view,
FScene::LightSoa& lightData) noexcept {
void ShadowMapManager::calculateTextureRequirements(FEngine&, FView& view,
FScene::LightSoa&) noexcept {
// Lay out the shadow maps. For now, we take the largest requested dimension and allocate a
// texture of that size. Each cascade / shadow map gets its own layer in the array texture.
@@ -867,7 +878,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
if (useMipmapping) {
// Limit the lowest mipmap level to 256x256.
// This avoids artifacts on high derivative tangent surfaces.
int lowMipmapLevel = 7; // log2(256) - 1
int const lowMipmapLevel = 7; // log2(256) - 1
mipLevels = std::max(1, FTexture::maxLevelCount(maxDimension) - lowMipmapLevel);
}
+1 -1
View File
@@ -110,7 +110,7 @@ public:
private:
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept;
FScene::LightSoa& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
ShadowMapManager::ShadowTechnique updateSpotShadowMaps(FEngine& engine,
FScene::LightSoa& lightData) noexcept;
@@ -51,7 +51,7 @@ struct RenderableManager::BuilderDetails {
Box mAABB;
uint8_t mLayerMask = 0x1;
uint8_t mPriority = 0x4;
uint8_t mCommandChannel = 0x0;
uint8_t mCommandChannel = RenderableManager::Builder::DEFAULT_CHANNEL;
uint8_t mLightChannels = 1;
uint16_t mInstanceCount = 1;
bool mCulling : 1;
-1
View File
@@ -219,7 +219,6 @@ struct CameraInfo {
float d{}; // focus distance [m]
math::float3 const& getPosition() const noexcept { return model[3].xyz; }
math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); }
math::float3 getWorldOffset() const noexcept { return -worldOrigin[3].xyz; }
math::mat4 getUserViewMatrix() const noexcept { return view * worldOrigin; }
};
+10 -1
View File
@@ -298,7 +298,16 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
parser->getColorWrite(&colorWrite);
mRasterState.colorWrite = colorWrite;
mRasterState.depthFunc = depthTest ? DepthFunc::GE : DepthFunc::A;
mRasterState.alphaToCoverage = mBlendingMode == BlendingMode::MASKED;
bool alphaToCoverageSet = false;
parser->getAlphaToCoverageSet(&alphaToCoverageSet);
if (alphaToCoverageSet) {
bool alphaToCoverage = false;
parser->getAlphaToCoverage(&alphaToCoverage);
mRasterState.alphaToCoverage = alphaToCoverage;
} else {
mRasterState.alphaToCoverage = mBlendingMode == BlendingMode::MASKED;
}
parser->hasSpecularAntiAliasing(&mSpecularAntiAliasing);
if (mSpecularAntiAliasing) {
+1
View File
@@ -124,6 +124,7 @@ public:
}
bool isDoubleSided() const noexcept { return mDoubleSided; }
bool hasDoubleSidedCapability() const noexcept { return mDoubleSidedCapability; }
bool isAlphaToCoverageEnabled() const noexcept { return mRasterState.alphaToCoverage; }
float getMaskThreshold() const noexcept { return mMaskThreshold; }
bool hasShadowMultiplier() const noexcept { return mHasShadowMultiplier; }
AttributeBitset getRequiredAttributes() const noexcept { return mRequiredAttributes; }
+202 -105
View File
@@ -49,76 +49,136 @@ FScene::FScene(FEngine& engine) :
FScene::~FScene() noexcept = default;
void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCasters) noexcept {
void FScene::prepare(utils::JobSystem& js,
LinearAllocatorArena& allocator,
const mat4& worldOriginTransform,
bool shadowReceiversAreCasters) noexcept {
// TODO: can we skip this in most cases? Since we rely on indices staying the same,
// we could only skip, if nothing changed in the RCM.
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
// This will reset the allocator upon exiting
ArenaScope const arena(allocator);
FEngine& engine = mEngine;
EntityManager& em = engine.getEntityManager();
FRenderableManager& rcm = engine.getRenderableManager();
FTransformManager& tcm = engine.getTransformManager();
FLightManager& lcm = engine.getLightManager();
EntityManager const& em = engine.getEntityManager();
FRenderableManager const& rcm = engine.getRenderableManager();
FTransformManager const& tcm = engine.getTransformManager();
FLightManager const& lcm = engine.getLightManager();
// go through the list of entities, and gather the data of those that are renderables
auto& sceneData = mRenderableData;
auto& lightData = mLightData;
auto const& entities = mEntities;
using RenderableContainerData = std::pair<RenderableManager::Instance, TransformManager::Instance>;
using RenderableInstanceContainer = FixedCapacityVector<RenderableContainerData,
utils::STLAllocator< RenderableContainerData, LinearAllocatorArena >, false>;
// NOTE: we can't know in advance how many entities are renderable or lights because the corresponding
// component can be added after the entity is added to the scene.
using LightContainerData = std::pair<LightManager::Instance, TransformManager::Instance>;
using LightInstanceContainer = FixedCapacityVector<LightContainerData,
utils::STLAllocator< LightContainerData, LinearAllocatorArena >, false>;
size_t renderableDataCapacity = entities.size();
// we need the capacity to be multiple of 16 for SIMD loops
renderableDataCapacity = (renderableDataCapacity + 0xFu) & ~0xFu;
// we need 1 extra entry at the end for the summed primitive count
renderableDataCapacity = renderableDataCapacity + 1;
RenderableInstanceContainer renderableInstances{
RenderableInstanceContainer::with_capacity(entities.size(), allocator) };
sceneData.clear();
if (sceneData.capacity() < renderableDataCapacity) {
sceneData.setCapacity(renderableDataCapacity);
}
// The light data list will always contain at least one entry for the
// dominating directional light, even if there are no entities.
size_t lightDataCapacity = std::max<size_t>(1, entities.size());
// we need the capacity to be multiple of 16 for SIMD loops
lightDataCapacity = (lightDataCapacity + 0xFu) & ~0xFu;
lightData.clear();
if (lightData.capacity() < lightDataCapacity) {
lightData.setCapacity(lightDataCapacity);
}
// the first entries are reserved for the directional lights (currently only one)
lightData.resize(DIRECTIONAL_LIGHTS_COUNT);
LightInstanceContainer lightInstances{
LightInstanceContainer::with_capacity(entities.size(), allocator) };
SYSTRACE_NAME_BEGIN("InstanceLoop");
// find the max intensity directional light index in our local array
float maxIntensity = 0.0f;
std::pair<LightManager::Instance, TransformManager::Instance> directionalLightInstances{};
for (Entity e : entities) {
if (!em.isAlive(e)) {
continue;
/*
* First compute the exact number of renderables and lights in the scene.
* Also find the main directional light.
*/
for (Entity const e: entities) {
if (UTILS_LIKELY(em.isAlive(e))) {
auto ti = tcm.getInstance(e);
auto li = lcm.getInstance(e);
auto ri = rcm.getInstance(e);
if (li) {
// we handle the directional light here because it'd prevent multithreading below
if (UTILS_UNLIKELY(lcm.isDirectionalLight(li))) {
// we don't store the directional lights, because we only have a single one
if (lcm.getIntensity(li) >= maxIntensity) {
maxIntensity = lcm.getIntensity(li);
directionalLightInstances = { li, ti };
}
} else {
lightInstances.emplace_back(li, ti);
}
}
if (ri) {
renderableInstances.emplace_back(ri, ti);
}
}
}
// getInstance() always returns null if the entity is the Null entity,
// so we don't need to check for that, but we need to check it's alive
auto ri = rcm.getInstance(e);
auto li = lcm.getInstance(e);
if (!ri && !li) {
continue;
SYSTRACE_NAME_END();
/*
* Evaluate the capacity needed for the renderable and light SoAs
*/
// we need the capacity to be multiple of 16 for SIMD loops
// we need 1 extra entry at the end for the summed primitive count
size_t renderableDataCapacity = entities.size();
renderableDataCapacity = (renderableDataCapacity + 0xFu) & ~0xFu;
renderableDataCapacity = renderableDataCapacity + 1;
// The light data list will always contain at least one entry for the
// dominating directional light, even if there are no entities.
// we need the capacity to be multiple of 16 for SIMD loops
size_t lightDataCapacity = std::max<size_t>(DIRECTIONAL_LIGHTS_COUNT, entities.size());
lightDataCapacity = (lightDataCapacity + 0xFu) & ~0xFu;
/*
* Now resize the SoAs if needed
*/
// TODO: the resize below could happen in a job
if (sceneData.size() != renderableInstances.size()) {
sceneData.clear();
if (sceneData.capacity() < renderableDataCapacity) {
sceneData.setCapacity(renderableDataCapacity);
}
assert_invariant(renderableInstances.size() <= sceneData.capacity());
sceneData.resize(renderableInstances.size());
}
// get the world transform
auto ti = tcm.getInstance(e);
// this is where we go from double to float for our transforms
const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0;
if (lightData.size() != lightInstances.size() + DIRECTIONAL_LIGHTS_COUNT) {
lightData.clear();
if (lightData.capacity() < lightDataCapacity) {
lightData.setCapacity(lightDataCapacity);
}
assert_invariant(lightInstances.size() + DIRECTIONAL_LIGHTS_COUNT <= lightData.capacity());
lightData.resize(lightInstances.size() + DIRECTIONAL_LIGHTS_COUNT);
}
/*
* Fill the SoA with the JobSystem
*/
auto renderableWork = [first = renderableInstances.data(), &rcm, &tcm, &worldOriginTransform,
&sceneData, shadowReceiversAreCasters](auto* p, auto c) {
SYSTRACE_NAME("renderableWork");
for (size_t i = 0; i < c; i++) {
auto [ri, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0;
// don't even draw this object if it doesn't have a transform (which shouldn't happen
// because one is always created when creating a Renderable component).
if (ri && ti) {
// compute the world AABB so we can perform culling
const Box worldAABB = rigidTransform(rcm.getAABB(ri), worldTransform);
@@ -131,81 +191,117 @@ void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCa
// FIXME: We compute and store the local scale because it's needed for glTF but
// we need a better way to handle this
const mat4f& transform = tcm.getTransform(ti);
float scale = (length(transform[0].xyz) + length(transform[1].xyz) +
length(transform[2].xyz)) / 3.0f;
float const scale = (length(transform[0].xyz) + length(transform[1].xyz) +
length(transform[2].xyz)) / 3.0f;
// we know there is enough space in the array
sceneData.push_back_unsafe(
ri, // RENDERABLE_INSTANCE
worldTransform, // WORLD_TRANSFORM
visibility, // VISIBILITY_STATE
rcm.getSkinningBufferInfo(ri), // SKINNING_BUFFER
rcm.getMorphingBufferInfo(ri), // MORPHING_BUFFER
worldAABB.center, // WORLD_AABB_CENTER
0, // VISIBLE_MASK
rcm.getChannels(ri), // CHANNELS
rcm.getInstanceCount(ri), // INSTANCE_COUNT
rcm.getLayerMask(ri), // LAYERS
worldAABB.halfExtent, // WORLD_AABB_EXTENT
{}, // PRIMITIVES
0, // SUMMED_PRIMITIVE_COUNT
{}, // UBO
scale // USER_DATA
);
size_t const index = std::distance(first, p) + i;
assert_invariant(index < sceneData.size());
sceneData.elementAt<RENDERABLE_INSTANCE>(index) = ri;
sceneData.elementAt<WORLD_TRANSFORM>(index) = worldTransform;
sceneData.elementAt<VISIBILITY_STATE>(index) = visibility;
sceneData.elementAt<SKINNING_BUFFER>(index) = rcm.getSkinningBufferInfo(ri);
sceneData.elementAt<MORPHING_BUFFER>(index) = rcm.getMorphingBufferInfo(ri);
sceneData.elementAt<WORLD_AABB_CENTER>(index) = worldAABB.center;
sceneData.elementAt<VISIBLE_MASK>(index) = 0;
sceneData.elementAt<CHANNELS>(index) = rcm.getChannels(ri);
sceneData.elementAt<INSTANCE_COUNT>(index) = rcm.getInstanceCount(ri);
sceneData.elementAt<LAYERS>(index) = rcm.getLayerMask(ri);
sceneData.elementAt<WORLD_AABB_EXTENT>(index) = worldAABB.halfExtent;
//sceneData.elementAt<PRIMITIVES>(index) = {}; // already initialized, Slice<>
sceneData.elementAt<SUMMED_PRIMITIVE_COUNT>(index) = 0;
//sceneData.elementAt<UBO>(index) = {}; // not needed here
sceneData.elementAt<USER_DATA>(index) = scale;
}
};
if (li) {
// find the dominant directional light
if (UTILS_UNLIKELY(lcm.isDirectionalLight(li))) {
// we don't store the directional lights, because we only have a single one
if (lcm.getIntensity(li) >= maxIntensity) {
maxIntensity = lcm.getIntensity(li);
float3 d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
lightData.elementAt<FScene::POSITION_RADIUS>(0) =
float4{ 0, 0, 0, std::numeric_limits<float>::infinity() };
lightData.elementAt<FScene::DIRECTION>(0) = d;
lightData.elementAt<FScene::LIGHT_INSTANCE>(0) = li;
}
} else {
const float4 p = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
float3 d = 0;
if (!lcm.isPointLight(li) || lcm.isIESLight(li)) {
d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
}
lightData.push_back_unsafe(
float4{ p.xyz, lcm.getRadius(li) }, d, li, {}, {}, {});
auto lightWork = [first = lightInstances.data(), &lcm, &tcm, &worldOriginTransform,
&lightData](auto* p, auto c) {
SYSTRACE_NAME("lightWork");
for (size_t i = 0; i < c; i++) {
auto [li, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const float4 position = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
float3 d = 0;
if (!lcm.isPointLight(li) || lcm.isIESLight(li)) {
d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
}
size_t const index = DIRECTIONAL_LIGHTS_COUNT + std::distance(first, p) + i;
assert_invariant(index < lightData.size());
lightData.elementAt<POSITION_RADIUS>(index) = float4{ position.xyz, lcm.getRadius(li) };
lightData.elementAt<DIRECTION>(index) = d;
lightData.elementAt<LIGHT_INSTANCE>(index) = li;
}
};
SYSTRACE_NAME_BEGIN("Renderable and Light jobs");
JobSystem::Job* rootJob = js.createJob();
auto* renderableJob = jobs::parallel_for(js, rootJob,
renderableInstances.data(), renderableInstances.size(),
std::cref(renderableWork), jobs::CountSplitter<128, 5>());
auto* lightJob = jobs::parallel_for(js, rootJob,
lightInstances.data(), lightInstances.size(),
std::cref(lightWork), jobs::CountSplitter<32, 5>());
js.run(renderableJob);
js.run(lightJob);
// Everything below can be done in parallel.
/*
* Handle the directional light separately
*/
if (auto [li, ti] = directionalLightInstances ; li) {
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
// using mat3f::getTransformForNormals handles non-uniform scaling
float3 d = lcm.getLocalDirection(li);
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
constexpr float inf = std::numeric_limits<float>::infinity();
lightData.elementAt<POSITION_RADIUS>(0) = float4{ 0, 0, 0, inf };
lightData.elementAt<DIRECTION>(0) = d;
lightData.elementAt<LIGHT_INSTANCE>(0) = li;
} else {
lightData.elementAt<LIGHT_INSTANCE>(0) = 0;
}
// some elements past the end of the array will be accessed by SIMD code, we need to make
// sure the data is valid enough as not to produce errors such as divide-by-zero
// (e.g. in computeLightRanges())
for (size_t i = lightData.size(), e = lightDataCapacity; i < e; i++) {
for (size_t i = lightData.size(), e = lightData.capacity(); i < e; i++) {
new(lightData.data<POSITION_RADIUS>() + i) float4{ 0, 0, 0, 1 };
}
// Purely for the benefit of MSAN, we can avoid uninitialized reads by zeroing out the
// unused scene elements between the end of the array and the rounded-up count.
if (UTILS_HAS_SANITIZE_MEMORY) {
for (size_t i = sceneData.size(), e = renderableDataCapacity; i < e; i++) {
for (size_t i = sceneData.size(), e = sceneData.capacity(); i < e; i++) {
sceneData.data<LAYERS>()[i] = 0;
sceneData.data<VISIBLE_MASK>()[i] = 0;
sceneData.data<VISIBILITY_STATE>()[i] = {};
}
}
js.runAndWait(rootJob);
SYSTRACE_NAME_END();
}
void FScene::prepareVisibleRenderables(Range<uint32_t> visibleRenderables) noexcept {
SYSTRACE_CALL();
RenderableSoa& sceneData = mRenderableData;
FRenderableManager& rcm = mEngine.getRenderableManager();
FRenderableManager const& rcm = mEngine.getRenderableManager();
mHasContactShadows = false;
for (uint32_t i : visibleRenderables) {
for (uint32_t const i : visibleRenderables) {
PerRenderableData& uboData = sceneData.elementAt<UBO>(i);
auto const visibility = sceneData.elementAt<VISIBILITY_STATE>(i);
@@ -257,6 +353,7 @@ void FScene::prepareVisibleRenderables(Range<uint32_t> visibleRenderables) noexc
void FScene::updateUBOs(
Range<uint32_t> visibleRenderables,
Handle<HwBufferObject> renderableUbh) noexcept {
SYSTRACE_CALL();
FEngine::DriverApi& driver = mEngine.getDriverApi();
// store the UBO handle
@@ -278,7 +375,7 @@ void FScene::updateUBOs(
// copy our data into the UBO for each visible renderable
PerRenderableData const* const uboData = mRenderableData.data<UBO>();
for (uint32_t i : visibleRenderables) {
for (uint32_t const i : visibleRenderables) {
buffer[i] = uboData[i];
}
@@ -308,15 +405,15 @@ void FScene::updateUBOs(
}
}
void FScene::terminate(FEngine& engine) {
void FScene::terminate(FEngine&) {
// DO NOT destroy this UBO, it's owned by the View
mRenderableViewUbh.clear();
}
void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootArena,
void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope&,
Handle<HwBufferObject> lightUbh) noexcept {
FEngine::DriverApi& driver = mEngine.getDriverApi();
FLightManager& lcm = mEngine.getLightManager();
FLightManager const& lcm = mEngine.getLightManager();
FScene::LightSoa& lightData = getLightData();
/*
@@ -325,7 +422,7 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
size_t const size = lightData.size();
// number of point-light/spotlights
size_t positionalLightCount = size - DIRECTIONAL_LIGHTS_COUNT;
size_t const positionalLightCount = size - DIRECTIONAL_LIGHTS_COUNT;
assert_invariant(positionalLightCount);
float4 const* const UTILS_RESTRICT spheres = lightData.data<FScene::POSITION_RADIUS>();
@@ -418,11 +515,11 @@ void FScene::removeEntities(const Entity* entities, size_t count) {
UTILS_NOINLINE
size_t FScene::getRenderableCount() const noexcept {
FEngine& engine = mEngine;
EntityManager& em = engine.getEntityManager();
FRenderableManager& rcm = engine.getRenderableManager();
EntityManager const& em = engine.getEntityManager();
FRenderableManager const& rcm = engine.getRenderableManager();
size_t count = 0;
auto const& entities = mEntities;
for (Entity e : entities) {
for (Entity const e : entities) {
count += em.isAlive(e) && rcm.getInstance(e) ? 1 : 0;
}
return count;
@@ -431,11 +528,11 @@ size_t FScene::getRenderableCount() const noexcept {
UTILS_NOINLINE
size_t FScene::getLightCount() const noexcept {
FEngine& engine = mEngine;
EntityManager& em = engine.getEntityManager();
FLightManager& lcm = engine.getLightManager();
EntityManager const& em = engine.getEntityManager();
FLightManager const& lcm = engine.getLightManager();
size_t count = 0;
auto const& entities = mEntities;
for (Entity e : entities) {
for (Entity const e : entities) {
count += em.isAlive(e) && lcm.getInstance(e) ? 1 : 0;
}
return count;
+2 -1
View File
@@ -70,7 +70,8 @@ public:
~FScene() noexcept;
void terminate(FEngine& engine);
void prepare(const math::mat4& worldOriginTransform, bool shadowReceiversAreCasters) noexcept;
void prepare(utils::JobSystem& js, LinearAllocatorArena& allocator,
math::mat4 const& worldOriginTransform, bool shadowReceiversAreCasters) noexcept;
void prepareVisibleRenderables(utils::Range<uint32_t> visibleRenderables) noexcept;
+30 -22
View File
@@ -268,9 +268,8 @@ bool FView::isSkyboxVisible() const noexcept {
return skybox != nullptr && (skybox->getLayerMask() & mVisibleLayers);
}
void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
CameraInfo const& cameraInfo) noexcept {
void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, CameraInfo const& cameraInfo) noexcept {
SYSTRACE_CALL();
mHasShadowing = false;
@@ -284,7 +283,7 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
auto& lcm = engine.getLightManager();
// dominant directional light is always as index 0
FLightManager::Instance directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
const bool hasDirectionalShadows = directionalLight && lcm.isShadowCaster(directionalLight);
if (UTILS_UNLIKELY(hasDirectionalShadows)) {
const auto& shadowOptions = lcm.getShadowOptions(directionalLight);
@@ -338,6 +337,7 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaScope& arena,
filament::Viewport const& viewport, CameraInfo const& cameraInfo) noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
FScene* const scene = mScene;
auto const& lightData = scene->getLightData();
@@ -390,7 +390,7 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc
* Directional light (always at index 0)
*/
FLightManager::Instance directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
const float3 sceneSpaceDirection = lightData.elementAt<FScene::DIRECTION>(0); // guaranteed normalized
mPerViewUniforms.prepareDirectionalLight(engine, exposure, sceneSpaceDirection, directionalLight);
mHasDirectionalLight = directionalLight.isValid();
@@ -401,38 +401,40 @@ CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
/*
* We apply a "world origin" to "everything" in order to implement the IBL rotation.
* The "world origin" could also be useful for other things, like keeping the origin
* close to the camera position to improve fp precision in the shader for large scenes.
* The "world origin" is also be used to kee the origin close to the camera position to
* improve fp precision in the shader for large scenes.
*/
mat4 worldOriginScene;
FIndirectLight const* const ibl = scene->getIndirectLight();
if (ibl) {
// the IBL transformation must be a rigid transform
mat3f rotation{ scene->getIndirectLight()->getRotation() };
// for a rigid-body transform, the inverse is the transpose
worldOriginScene = mat4{ transpose(rotation) };
}
mat4 translation;
mat4 rotation;
/*
* Calculate all camera parameters needed to render this View for this frame.
*/
FCamera const* const camera = mViewingCamera ? mViewingCamera : mCullingCamera;
if (engine.debug.view.camera_at_origin) {
// this moves the camera to the origin, effectively doing all shader computations in
// view-space, which improves floating point precision in the shader by staying around
// zero, where fp precision is highest. This also ensures that when the camera is placed
// very far from the origin, objects are still rendered and lit properly.
worldOriginScene[3].xyz -= camera->getPosition();
translation = mat4::translation( -camera->getPosition() );
}
return { *camera, worldOriginScene };
FIndirectLight const* const ibl = scene->getIndirectLight();
if (ibl) {
// the IBL transformation must be a rigid transform
rotation = mat4{ transpose(scene->getIndirectLight()->getRotation()) };
}
return { *camera, rotation * translation };
}
void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
filament::Viewport const& viewport, CameraInfo const& cameraInfo,
float4 const& userTime, bool needsAlphaChannel) noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
JobSystem& js = engine.getJobSystem();
/*
@@ -444,7 +446,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
if (UTILS_LIKELY(mViewingCamera == nullptr)) {
// In the common case when we don't have a viewing camera, cameraInfo.view is
// already the culling view matrix
return Frustum{ mat4f{ highPrecisionMultiply(cameraInfo.projection, cameraInfo.view) }};
return Frustum{ mat4f{ highPrecisionMultiply(cameraInfo.cullingProjection, cameraInfo.view) }};
} else {
// Otherwise, we need to recalculate it from the culling camera.
// Note: it is correct to always do the math from mCullingCamera, but it hides the
@@ -464,7 +466,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
* Gather all information needed to render this scene. Apply the world origin to all
* objects in the scene.
*/
scene->prepare(cameraInfo.worldOrigin, hasVSM());
scene->prepare(js, arena.getAllocator(), cameraInfo.worldOrigin, hasVSM());
/*
* Light culling: runs in parallel with Renderable culling (below)
@@ -504,7 +506,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
if (prepareVisibleLightsJob) {
js.waitAndRelease(prepareVisibleLightsJob);
}
prepareShadowing(engine, driver, renderableData, scene->getLightData(), cameraInfo);
prepareShadowing(engine, renderableData, scene->getLightData(), cameraInfo);
/*
* Partition the SoA so that renderables are partitioned w.r.t their visibility into the
@@ -528,6 +530,8 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
// TODO: we need to compare performance of doing this partitioning vs not doing it.
// and rely on checking visibility in the loops
SYSTRACE_NAME_BEGIN("Partitioning");
// calculate the sorting key for all elements, based on their visibility
uint8_t const* layers = renderableData.data<FScene::LAYERS>();
auto const* visibility = renderableData.data<FScene::VISIBILITY_STATE>();
@@ -568,6 +572,8 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
mSpotLightShadowCasters = merged;
SYSTRACE_NAME_END();
// TODO: when any spotlight is used, `merged` ends-up being the whole list. However,
// some of the items will end-up not being visible by any light. Can we do better?
// e.g. could we deffer some of the prepareVisibleRenderables() to later?
@@ -603,8 +609,10 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
* Update driver state
*/
auto const userModelMatrix = inverse(cameraInfo.getUserViewMatrix());
auto const userCameraPosition = userModelMatrix[3].xyz;
mPerViewUniforms.prepareTime(engine, userTime);
mPerViewUniforms.prepareFog(cameraInfo.getPosition(), mFogOptions);
mPerViewUniforms.prepareFog(userCameraPosition, mFogOptions);
mPerViewUniforms.prepareTemporalNoise(engine, mTemporalAntiAliasingOptions);
mPerViewUniforms.prepareBlending(needsAlphaChannel);
}
+2 -3
View File
@@ -140,9 +140,8 @@ public:
const Viewport& physicalViewport,
const filament::Viewport& logicalViewport) const noexcept;
void prepareShadowing(FEngine& engine, backend::DriverApi& driver,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
CameraInfo const& cameraInfo) noexcept;
void prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, CameraInfo const& cameraInfo) noexcept;
void prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaScope& arena,
filament::Viewport const& viewport, CameraInfo const &cameraInfo) noexcept;
+7 -2
View File
@@ -151,7 +151,12 @@ void RenderPassNode::resolve() noexcept {
rt.targetBufferFlags |= target;
if (!rt.outgoing[i] || !rt.outgoing[i]->hasActiveReaders()) {
// Discard at the end only if we are writing to this attachment AND later reading
// from it. (in particular, don't discard if we're not writing at all, because this
// attachment might have other readers after us).
// TODO: we could set the discard flag if we are the last reader, i.e.
// if rt->incoming[i] last reader is us.
if (rt.outgoing[i] && !rt.outgoing[i]->hasActiveReaders()) {
rt.backend.params.flags.discardEnd |= target;
}
if (!rt.outgoing[i] || !rt.outgoing[i]->hasWriterPass()) {
@@ -161,10 +166,10 @@ void RenderPassNode::resolve() noexcept {
rt.backend.params.readOnlyDepthStencil |= RenderPassParams::READONLY_STENCIL;
}
}
// Discard at the start if this attachment has no prior writer
if (!rt.incoming[i] || !rt.incoming[i]->hasActiveWriters()) {
rt.backend.params.flags.discardStart |= target;
}
VirtualResource* pResource = mFrameGraph.getResource(rt.descriptor.attachments.array[i]);
Resource<FrameGraphTexture>* pTextureResource = static_cast<Resource<FrameGraphTexture>*>(pResource);
@@ -890,3 +890,55 @@ TEST_F(FrameGraphTest, SubResourcesWriteRead) {
fg.execute(driverApi);
}
TEST_F(FrameGraphTest, WriteResourceReadAsAttachment) {
// this check s that using a resources as a read-only attachment doesn't set the
// discard flag on that resource. i.e. that the mere fact of reading a resource as an
// attachment doesn't make it invalid.
// see: https://github.com/google/filament/issues/5005
struct PassData {
FrameGraphId<FrameGraphTexture> input;
};
auto& writePass = fg.addPass<PassData>("Write Pass", [&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.create<FrameGraphTexture>("Output buffer", {.width=16, .height=32});
data.input = builder.write(data.input, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
builder.declareRenderPass("Output target", { .attachments = {
.color = { data.input }
}});
builder.sideEffect();
},
[=](FrameGraphResources const& resources, auto const&, backend::DriverApi&) {
auto rt = resources.getRenderPassInfo();
EXPECT_EQ(rt.params.flags.discardStart, TargetBufferFlags::COLOR0);
EXPECT_EQ(rt.params.flags.discardEnd, TargetBufferFlags::NONE);
});
fg.addPass<PassData>("Read Pass", [&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.read(writePass->input, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
builder.declareRenderPass("Input target", { .attachments = {
.color = { data.input }
}});
builder.sideEffect();
},
[=](FrameGraphResources const& resources, auto const&, backend::DriverApi&) {
auto rt = resources.getRenderPassInfo();
EXPECT_EQ(rt.params.flags.discardStart, TargetBufferFlags::NONE);
EXPECT_EQ(rt.params.flags.discardEnd, TargetBufferFlags::NONE);
});
fg.addPass<PassData>("Sample Pass", [&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.sample(writePass->input);
builder.sideEffect();
},
[=](FrameGraphResources const& resources, auto const&, backend::DriverApi&) {
});
EXPECT_TRUE(fg.isAcyclic());
fg.compile();
fg.execute(driverApi);
}

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