Compare commits

..
Author SHA1 Message Date
Benjamin Doherty 15424c85b5 Metal: use atomic for buffer tracking 2025-09-19 13:41:37 -07:00
Powei Feng 0dd1d3211b backend: fix scissor viewport region test (#9220)
Make sure that the rendertarget dimensions matches the texture
dimensions. Update the expected image for the test.

Also fixed usage, rendertarget parameters to make sure vk does
not have any validation errors.
2025-09-17 21:58:50 +00:00
Filament Bot 33249c6eb8 [automated] Updating /docs due to commit 59f19ae
Full commit hash is 59f19aee0e

DOCS_ALLOW_DIRECT_EDITS
2025-09-16 17:44:15 +00:00
Ben Doherty 59f19aee0e Add documentation and script to publish to Maven Central (#9216) 2025-09-16 13:41:06 -04:00
Sungun Park 06c8fc39c2 Update MATERIAL_VERSION to 64 2025-09-16 10:08:23 -07:00
Powei Feng a36ad004b7 renderdiff: add ability to diff arbitrary rendered images (#9211)
Add a way to show the difference between two rendererd image.
This is a convenience feature to see the differences between
backends.

Refactor the ExpandedFailedResult to use this new view.
2025-09-16 00:27:42 +00:00
Ivan Mikhalchuk 6ac5f26bb5 fix UB in AtomicFreeList::push and FreeList::push: access to non-static data member without proper lifetime management 2025-09-15 08:50:02 -07:00
Powei Feng bce8320f06 vk: make sure default render target is not removed (#9213)
We need to have an reference to the default render target for
the duration of the backend lifetime.  So even if the client
tries to destroy the default render target, we don't actually
destroy it.

Note that it will be destroyed on driver shutdown.
2025-09-12 22:52:21 +00:00
Powei Feng f3a4e21fe5 backend: fix blit tests for vulkan
- The rendertarget layer count should be >=1.
- Add read pixels usage bit
2025-09-12 15:27:49 -07:00
Powei Feng 6838d8602e github: fix caching for vulkansdk and mac brew (#9203)
We should be able to use ~ (tilde) to indicate the user root
directory.

Before the cache didn't work because these paths weren't found.
2025-09-12 17:54:52 +00:00
Powei Feng fe9033c7db backend: fix vulkan test with readPixels (#9202)
filament/backend/test/Workarounds.h has a specific workaround
with regards to readPixels and texture usage.  Originally this
was introduced for the WebGPU backend. This is needed
for the vulkan backend as well.

Also simplify BufferUpdatesTest.BufferObjectUpdateWithOffset
 - no need to call flush() or executeCommands(), these are
   called when the test terminates.
 - layerCount for a rendertarget should be at least 1.
2025-09-12 17:25:28 +00:00
Powei Feng 480b0878d7 vk: fix validation warnings (#9207)
- Remove extensions that have been promoted to 1.1
- Use vkCreateMetalSurfaceEXT instead of the deprecated
   vkCreateMacOSSurfaceMVK
2025-09-11 23:59:25 +00:00
Sungun Park 57d1a6a165 Add MSAA swapchain support (#9196)
Users can now create a swapchain with MSAA support, which enables direct
rendering to a multi-sampled swapchain image and automatic resolution.

This is only supported by EGL(Android). Other GL platforms (GLX, WGL,
etc) don't support it because the swapchain MSAA settings must be
configured before window creation.

Support for other backends will follow.

BUGS=[433327615]
2025-09-11 21:29:06 +00:00
Ben Doherty fe81493052 Fix g3 build (#9209) 2025-09-11 16:28:03 -04:00
Mathias Agopian 9c52ec32ff hybrid instancing improvements
Hybrid (InstanceBuffer based) instancing no longer needs a 
descriptor-set per object because we now use the same UBO for all
objects, so the "shared" per-object descriptor-set can be used.

This also implies that InstanceBuffer doesn't need to track the UBO
handle anymore, only its offset in the buffer.
2025-09-11 12:13:47 -07:00
Mathias Agopian 5cf091220c Fix descriptor set update bug
the buffer offset in descriptors wasn't updated if it was the only 
value to change when setting the descriptor.


FIXES=[443991507]
2025-09-11 12:13:47 -07:00
Powei Feng f6892a111c vk: fix VulkanDriver::finish() (#9199)
VulkanDriver::finish() seems like it is waiting for all the
commands to be processed, but in reality, the present calls
are not waited on (because commands->wait() only waited on
the command buffers).

Here we just call vkQueueWaitIdle to address that case.

This fixes a validation error that occurs when running the
backend test BackendTest.MissingRequiredAttributes.
2025-09-11 19:02:07 +00:00
Sungun Park 63f877a68e Fix: Correctly handle unsupported SwapChain flags (#9208)
FSwapChain initialization logic would filter out unsupported flags but
would incorrectly use the original, unfiltered flags when creating the
swap chain.

This commit ensures the filtered flags are passed to the driver. It also
adds the warning to log which ones are removed.

BUGS=[433327615]
2025-09-11 18:24:02 +00:00
Powei Feng 3d8fb3bcea vk: fix blitDEPRECATED implementation (#9201)
blitDEPRECATED can also be used in the context of a multisampled
texture resolve. Fix that case to use the resolve() path in the
VulkanBlitter.

Also fixed backend test BlitTest.ColorResolve to add the correct
usage bit (otherwise it'd have validation error).

FIXES=417229577
2025-09-11 16:48:50 +00:00
Anish Goyal e218eca6b0 Create exportable Syncs (fences) (#9110)
* Create exportable fences on Android

Also - create a conversion method for the driver api so that a fence can
be converted to an external handle, where available.

* Create a frontend API for external fences

Fences will be called 'Sync' on the frontend, and come with a method for
conversion to an external sync. This will typically be a file
descriptor, for use in posix systems.

* Refactor Sync to rely on opaque handles

Also - push the callback invocations into the backend, such that they
are scheduled on a worker thread, and not run on the Filament backend
thread.

* Address PR comment - minor refactor

Clear vectors after they've been consumed, and update null checks by
removing explicit reference to nullptr.

* Remove convertSyncToFd from non-android

Other platforms might have other handle types.

Also - remove unused header include

* Add logging for convert to fd

Also, remove duplicate includes

* Address PR - change fd type

As per request by rafadavi@, changed to match the Android spec for file
descriptor type, which is `int`.

* Address PR - remove vtable from sync

Also - rename derived sync classes

* Address comment - make SyncCallbackData private

It's technically implementation-specific, even though it currently
happens to be the same. Separate this into the different backends.

* Add stub for metal backend for sync

* Add stub impl for Sync in WebGPU
2025-09-11 09:02:56 -07:00
Powei Feng 17b8b9ff65 backend: fix MRT test's layercount (#9200)
layercount should be at least 1. This was causing a validation
error for the vk backend when running the test.
2025-09-10 15:34:15 -07:00
Powei Feng 49b8a17b1f 3p: remove spirv-tools targets for binaries and shared libs (#9198)
We need to modify spirv-tools existing CMakeLists.txt to make
sure that its binaries and libs are not included in the filament
installed output.

This change was present before, but overwritten when spirv-tools
updated (wrt the WebGPU project).

In addition to modifying the CMakeLists.txt, we add a script to
carry out the changes automatically when running the update script
in the future.

Fixes #9168
2025-09-10 20:01:42 +00:00
rafadevai f89c105941 VK: Fix leak in VulkanExternalImageManager (#9194)
In the cases of a descriptor set being updated
with a external sampled texture but that descriptor
set doesn't get used later on for rendering.

Their corresponding entry in VulkanExternalImageManager::mSetBindings
will never get removed until the application is terminated, causing
performance issues or a fatal crash if run long enough.

The expectation is that the amount of bindings with
external samplers is low or none, so this won't cause
any regression when not using external samplers.
2025-09-10 17:08:14 +00:00
Filament Bot 3d9da8ab3b [automated] Updating /docs due to commit da315ef
Full commit hash is da315ef9fe

DOCS_ALLOW_DIRECT_EDITS
2025-09-10 00:19:44 +00:00
90 changed files with 2238 additions and 296 deletions
+4 -1
View File
@@ -8,7 +8,7 @@ runs:
uses: actions/cache@v3
id: cache-vulkan-sdk
with:
path: ${{ runner.homedir }}/VulkanSDK
path: ~/VulkanSDK
key: vulkansdk-${{ env.GITHUB_VULKANSDK_VERSION }}-2-${{ runner.os }}
- name: Download Vulkan SDK
if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
@@ -21,6 +21,9 @@ runs:
run: |
source ${{ github.workspace }}/build/common/get-vulkan-sdk.sh
unpack_vulkan_installer
shell: bash
- name: Run Vulkan SDK setup
run: |
pushd .
cd ~/VulkanSDK/${GITHUB_VULKANSDK_VERSION}
sudo ./install_vulkan.py
+1 -1
View File
@@ -14,7 +14,7 @@ runs:
id: brew-cache
uses: actions/cache@v4 # Use a specific version
with:
path: $HOME/Library/Caches/Homebrew
path: ~/Library/Caches/Homebrew
key: ${{ runner.os }}-brew-20250424
- name: Install Mac Prerequisites
shell: bash
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.65.1'
implementation 'com.google.android.filament:filament-android:1.65.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:
```shell
pod 'Filament', '~> 1.65.1'
pod 'Filament', '~> 1.65.0'
```
## Documentation
+2
View File
@@ -178,6 +178,8 @@ nexusPublishing {
repositories {
sonatype {
stagingProfileId = '9a75a224a4f17b'
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
}
}
}
@@ -40,6 +40,13 @@ Java_com_google_android_filament_SwapChain_nIsSRGBSwapChainSupported(
return (jboolean)SwapChain::isSRGBSwapChainSupported(*engine);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_SwapChain_nIsMSAASwapChainSupported(
JNIEnv *, jclass, jlong nativeEngine, jint samples) {
Engine* engine = (Engine*) nativeEngine;
return (jboolean)SwapChain::isMSAASwapChainSupported(*engine, samples);
}
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_android_filament_SwapChain_nIsProtectedContentSupported(
JNIEnv *, jclass, jlong nativeEngine) {
@@ -97,6 +97,19 @@ public class SwapChain {
return nIsSRGBSwapChainSupported(engine.getNativeObject());
}
/**
* Return whether createSwapChain supports the CONFIG_MSAA_*_SAMPLES flag.
* The default implementation returns false.
*
* @param engine A reference to the filament Engine
* @param samples The number of samples
* @return true if CONFIG_MSAA_*_SAMPLES is supported, false otherwise.
* @see SwapChainFlags#CONFIG_MSAA_*_SAMPLES
*/
public static boolean isMSAASwapChainSupported(@NonNull Engine engine, int samples) {
return nIsMSAASwapChainSupported(engine.getNativeObject(), samples);
}
/**
* @return the native <code>Object</code> this <code>SwapChain</code> was created from or null
* for a headless SwapChain.
@@ -139,5 +152,6 @@ public class SwapChain {
private static native void nSetFrameCompletedCallback(long nativeSwapChain, Object handler, Runnable callback);
private static native boolean nIsSRGBSwapChainSupported(long nativeEngine);
private static native boolean nIsMSAASwapChainSupported(long nativeEngine, int samples);
private static native boolean nIsProtectedContentSupported(long nativeEngine);
}
@@ -92,6 +92,15 @@ public final class SwapChainFlags {
* The SwapChain contains protected content. Only supported when isProtectedContentSupported()
* is true.
*/
public static final long CONFIG_PROTECTED_CONTENT = 0x40;
}
public static final long CONFIG_PROTECTED_CONTENT = 0x40;
/**
* Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the
* given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is
* true.
*
* This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it
* because the swapchain MSAA settings must be configured before window creation.
*/
public static final long CONFIG_MSAA_4_SAMPLES = 0x80;
}
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.65.1
VERSION_NAME=1.65.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
@@ -0,0 +1,349 @@
#!/usr/bin/env python3
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This script automates the process of closing a Sonatype OSSRH staging repository.
To run the script:
python3 ./close-sonatype-staging-repository.py
To run the embedded unit tests:
python3 ./close-sonatype-staging-repository.py test
"""
import sys
import os
import base64
import json
import pathlib
import unittest
import io
from unittest import mock
import urllib.request
import urllib.error
PROPERTIES_PATH = pathlib.Path.home() / ".gradle" / "gradle.properties"
API_BASE_URL = "https://ossrh-staging-api.central.sonatype.com/manual"
def get_gradle_credentials(file_path: pathlib.Path) -> (str, str):
"""
Reads sonatypeUsername and sonatypePassword from the properties file.
"""
if not file_path.exists():
print(f"Error: Properties file not found at {file_path}", file=sys.stderr)
sys.exit(1)
username = None
password = None
try:
with open(file_path, 'r') as f:
for line in f:
line = line.strip()
# Ignore blank lines and comments
if not line or line.startswith("#"):
continue
if line.startswith("sonatypeUsername="):
username = line.split("=", 1)[1].strip()
elif line.startswith("sonatypePassword="):
password = line.split("=", 1)[1].strip()
if not username or not password:
print(f"Error: 'sonatypeUsername' or 'sonatypePassword' not found in {file_path}", file=sys.stderr)
sys.exit(1)
return username, password
except Exception as e:
print(f"Error reading properties file: {e}", file=sys.stderr)
sys.exit(1)
def generate_auth_token(username: str, password: str) -> str:
"""
Generates a Base64 encoded auth token from username and password.
"""
auth_string = f"{username}:{password}"
auth_bytes = auth_string.encode('utf-8')
token_bytes = base64.b64encode(auth_bytes)
return token_bytes.decode('utf-8')
def find_staging_repository(token: str) -> str:
"""
Finds the 'open' staging repository key from the Sonatype API.
"""
print("Searching for staging repository...")
url = f"{API_BASE_URL}/search/repositories"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json"
}
req = urllib.request.Request(url, headers=headers, method="GET")
try:
with urllib.request.urlopen(req) as response:
response_body = response.read().decode('utf-8')
data = json.loads(response_body)
repositories = data.get("repositories", [])
open_repositories = list(filter(lambda repo: repo.get("state") == "open", repositories))
if len(open_repositories) == 0:
print("Error: No 'open' staging repositories found.", file=sys.stderr)
sys.exit(1)
if len(open_repositories) > 1:
print(f"Error: Expected 1 'open' repository, but found {len(repositories)}.", file=sys.stderr)
sys.exit(1)
repo_key = open_repositories[0].get("key")
if not repo_key:
print("Error: Repository found, but it has no 'key'.", file=sys.stderr)
sys.exit(1)
return repo_key
except urllib.error.HTTPError as e:
# Handle HTTP errors (e.g., 401 Unauthorized, 404 Not Found, 500 Server Error)
print(f"Error during repository search: HTTP {e.code} {e.reason}", file=sys.stderr)
try:
# Try to read the error response body for more context
error_body = e.read().decode('utf-8')
print(f"Response body: {error_body}", file=sys.stderr)
except Exception:
pass # Ignore if we can't read the body
sys.exit(1)
except urllib.error.URLError as e:
# Handle network errors (e.g., connection refused)
print(f"Error during repository search: {e.reason}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError:
print("Error: Failed to decode JSON response from server.", file=sys.stderr)
sys.exit(1)
def close_staging_repository(token: str, repo_key: str):
"""
Closes (promotes) the staging repository with the given key.
"""
print(f"Attempting to close staging repository: {repo_key}")
url = f"{API_BASE_URL}/upload/repository/{repo_key}"
headers = {"Authorization": f"Bearer {token}"}
req = urllib.request.Request(url, headers=headers, method="POST")
try:
with urllib.request.urlopen(req) as response:
print(f"Successfully submitted request. Server responded with: {response.status} {response.reason}")
print("It may take a few moments to process.")
except urllib.error.HTTPError as e:
print(f"Error closing repository: HTTP {e.code} {e.reason}", file=sys.stderr)
try:
error_body = e.read().decode('utf-8')
print(f"Response body: {error_body}", file=sys.stderr)
except Exception:
pass
sys.exit(1)
except urllib.error.URLError as e:
print(f"Error closing repository: {e.reason}", file=sys.stderr)
sys.exit(1)
def main():
username, password = get_gradle_credentials(PROPERTIES_PATH)
token = generate_auth_token(username, password)
print("Successfully generated auth token.")
repo_key = find_staging_repository(token)
print(f"Found staging repository: {repo_key}")
close_staging_repository(token, repo_key)
print("\nScript completed successfully.")
# --- Unit Tests ---
class TestSonatypeScript(unittest.TestCase):
@mock.patch('pathlib.Path.exists', return_value=True)
@mock.patch('builtins.open', new_callable=mock.mock_open,
read_data="# This is a comment\nsonatypeUsername=testuser\n\nsonatypePassword=testpass\nother=data")
def test_get_gradle_credentials_success(self, mock_file, mock_exists):
"""Tests successful credential parsing, ignoring comments."""
user, pw = get_gradle_credentials(pathlib.Path("/fake/path"))
self.assertEqual(user, "testuser")
self.assertEqual(pw, "testpass")
@mock.patch('pathlib.Path.exists', return_value=False)
def test_get_gradle_credentials_no_file(self, mock_exists):
"""Tests error exit when file is missing."""
with self.assertRaises(SystemExit) as cm:
get_gradle_credentials(pathlib.Path("/fake/path"))
@mock.patch('pathlib.Path.exists', return_value=True)
@mock.patch('builtins.open', new_callable=mock.mock_open, read_data="# Only comments\nsonatypeUsername=testuser")
def test_get_gradle_credentials_missing_key(self, mock_file, mock_exists):
"""Tests error exit when a key is missing."""
with self.assertRaises(SystemExit) as cm:
get_gradle_credentials(pathlib.Path("/fake/path"))
def test_generate_auth_token(self):
"""Tests the Base64 token encoding."""
token = generate_auth_token("foobar", "abc123")
# echo -n "foobar:abc123" | base64
self.assertEqual(token, "Zm9vYmFyOmFiYzEyMw==")
@mock.patch('urllib.request.urlopen')
def test_find_repository_success(self, mock_urlopen):
"""Tests finding exactly one open repository."""
mock_response = mock.Mock()
mock_response.status = 200
mock_response.reason = "OK"
mock_response.read.return_value = json.dumps({
"repositories": [{"key": "test-repo-key-123", "state": "open"}]
}).encode('utf-8')
mock_urlopen.return_value.__enter__.return_value = mock_response
key = find_staging_repository("fake-token")
self.assertEqual(key, "test-repo-key-123")
@mock.patch('urllib.request.urlopen')
def test_find_repository_success(self, mock_urlopen):
"""Tests finding exactly one open repository."""
mock_response = mock.Mock()
mock_response.status = 200
mock_response.reason = "OK"
mock_response.read.return_value = json.dumps({
"repositories": [{"key": "test-repo-key-123", "state": "open"}, {"key": "test-repo-key-456", "state": "closed"}]
}).encode('utf-8')
mock_urlopen.return_value.__enter__.return_value = mock_response
key = find_staging_repository("fake-token")
self.assertEqual(key, "test-repo-key-123")
@mock.patch('urllib.request.urlopen')
def test_find_repository_success(self, mock_urlopen):
"""Tests error exit when only a closed repository is found."""
mock_response = mock.Mock()
mock_response.status = 200
mock_response.reason = "OK"
mock_response.read.return_value = json.dumps({
"repositories": [{"key": "test-repo-key-123", "state": "closed"}]
}).encode('utf-8')
mock_urlopen.return_value.__enter__.return_value = mock_response
with self.assertRaises(SystemExit) as cm:
key = find_staging_repository("fake-token")
@mock.patch('urllib.request.urlopen')
def test_find_repository_not_found(self, mock_urlopen):
"""Tests error exit when zero repositories are found."""
mock_response = mock.Mock()
mock_response.status = 200
mock_response.read.return_value = json.dumps({"repositories": []}).encode('utf-8')
mock_urlopen.return_value.__enter__.return_value = mock_response
with self.assertRaises(SystemExit) as cm:
find_staging_repository("fake-token")
@mock.patch('urllib.request.urlopen')
def test_find_repository_too_many(self, mock_urlopen):
"""Tests error exit when multiple open repositories are found."""
mock_response = mock.Mock()
mock_response.status = 200
mock_response.read.return_value = json.dumps({
"repositories": [{"key": "key1", "state": "open"}, {"key": "key2", "state": "open"}]
}).encode('utf-8')
mock_urlopen.return_value.__enter__.return_value = mock_response
with self.assertRaises(SystemExit) as cm:
find_staging_repository("fake-token")
@mock.patch('urllib.request.urlopen')
def test_find_repository_http_error(self, mock_urlopen):
"""Tests error exit on a network HTTP error."""
# Mock an HTTPError
mock_urlopen.side_effect = urllib.error.HTTPError(
url='http://fake.com',
code=401,
msg="Unauthorized",
hdrs={},
fp=io.BytesIO(b'{"error":"bad token"}')
)
with self.assertRaises(SystemExit) as cm:
find_staging_repository("fake-token")
@mock.patch('urllib.request.urlopen')
def test_close_repository_success(self, mock_urlopen):
"""Tests successful repository close."""
mock_response = mock.Mock()
mock_response.status = 202 # 202 Accepted is a common response for POST
mock_response.reason = "Accepted"
mock_urlopen.return_value.__enter__.return_value = mock_response
close_staging_repository("fake-token", "test-repo-key")
# Check that the request object was created correctly
called_request = mock_urlopen.call_args[0][0]
self.assertEqual(called_request.full_url, f"{API_BASE_URL}/upload/repository/test-repo-key")
self.assertEqual(called_request.method, "POST")
self.assertEqual(called_request.headers["Authorization"], "Bearer fake-token")
@mock.patch('urllib.request.urlopen')
def test_close_repository_http_error(self, mock_urlopen):
"""Tests error exit on close network failure."""
mock_urlopen.side_effect = urllib.error.HTTPError(
url='http://fake.com',
code=500,
msg="Server Error",
hdrs={},
fp=io.BytesIO(b'{"error":"failed to close"}')
)
with self.assertRaises(SystemExit) as cm:
close_staging_repository("fake-token", "test-repo-key")
def run_unit_tests():
"""
Configures and runs the unit tests.
"""
print("Running unit tests...")
# We replace sys.argv to prevent unittest.main from
# trying to parse the 'test' argument.
original_argv = sys.argv
sys.argv = [original_argv[0]] # Keep only the script name
unittest.main()
sys.argv = original_argv # Restore original args
if __name__ == '__main__':
# Check if the user wants to run tests
if len(sys.argv) > 1 and sys.argv[1].lower() == 'test':
run_unit_tests()
else:
# Run the main script logic
main()
+267
View File
@@ -0,0 +1,267 @@
<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Maven Release - Filament</title>
<!-- Custom HTML head -->
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- MathJax -->
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "light" : "light";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div style="display:flex;align-items:center;justify-content:center">
<img class="flogo" src="../images/filament_logo_small.png"></img>
</div>
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<!-- Filament: disable themes because the markdeep part does not look good for dark themes -->
<!--
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
-->
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Filament</h1>
<div class="right-buttons">
<a href="https://github.com/google/filament" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="maven-release"><a class="header" href="#maven-release">Maven Release</a></h1>
<h2 id="register-for-a-sonatype-account"><a class="header" href="#register-for-a-sonatype-account">Register for a Sonatype Account</a></h2>
<p>First, you'll need to register for a Sonatype account at the <a href="https://central.sonatype.org/register/central-portal/">Central
Portal</a>.</p>
<p>To publish under the <code>com.google.android</code> namespace, you'll also need to email
<code>central-support@sonatype.com</code> with your account information to request access.</p>
<p>Then, generate a user token through the Sonatype website. Navigate to
<a href="https://central.sonatype.com/account">https://central.sonatype.com/account</a> and select <strong>Generate
User Token</strong>.</p>
<p>Finally, add the generated token credentials to <code>~/.gradle/gradle.properties</code>. (Note: these are
different from the credentials used to log into your Sonatype account):</p>
<pre><code class="language-gradle"># Generated Sonatype token
sonatypeUsername=&lt;username&gt;
sonatypePassword=&lt;password&gt;
</code></pre>
<hr />
<h2 id="signing-key"><a class="header" href="#signing-key">Signing Key</a></h2>
<p>All release artifacts must be signed. You'll need to create an OpenPGP key pair. See Gradle's
documentation on <a href="https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials">Signatory
credentials</a>
for instructions.</p>
<p>Update <code>~/.gradle/gradle.properties</code> with your new key's credentials:</p>
<pre><code class="language-gradle">signing.keyId=&lt;key id&gt;
signing.password=&lt;key password&gt;
signing.secretKeyRingFile=&lt;secret key ring file&gt;
</code></pre>
<hr />
<h2 id="build-the-android-release"><a class="header" href="#build-the-android-release">Build the Android Release</a></h2>
<p>Make sure <code>JAVA_HOME</code> is set correctly. For example:</p>
<pre><code class="language-bash">export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
./build.sh -C -i -p android release
</code></pre>
<hr />
<h2 id="publish-to-sonatype"><a class="header" href="#publish-to-sonatype">Publish to Sonatype</a></h2>
<h3 id="a-note-on-the-legacy-staging-service"><a class="header" href="#a-note-on-the-legacy-staging-service">A Note on the Legacy Staging Service</a></h3>
<p>Previously, Filament was published to Maven via OSSRH (Open Source Software Repository Hosting). In
2025, this service was <a href="https://central.sonatype.org/pages/ossrh-eol/">sunsetted</a>. Now, we use
Sonatype's <a href="https://central.sonatype.org/">Central Publisher Portal</a>.</p>
<p>The new Central Publisher Portal does not officially support Gradle. However, Sonatype provides a
staging API compatibility service, which works with Filament's Gradle setup.</p>
<hr />
<h3 id="1-upload-to-the-staging-api-compatibility-service"><a class="header" href="#1-upload-to-the-staging-api-compatibility-service">1. Upload to the Staging API Compatibility Service</a></h3>
<pre><code class="language-bash">cd android
./gradlew publishToSonatype
</code></pre>
<h3 id="2-move-the-repository-to-the-central-publisher-portal"><a class="header" href="#2-move-the-repository-to-the-central-publisher-portal">2. Move the Repository to the Central Publisher Portal</a></h3>
<p>We have a script to automate this. It reads the <code>sonatypeUsername</code> and <code>sonatypePassword</code> from your
<code>~/.gradle/gradle.properties</code> file.</p>
<pre><code class="language-bash">python3 build/common/close-sonatype-staging-repository.py
</code></pre>
<h3 id="3-publish-the-release-on-sonatype"><a class="header" href="#3-publish-the-release-on-sonatype">3. Publish the Release on Sonatype</a></h3>
<p>Navigate to <a href="https://central.sonatype.com/publishing/deployments">Maven Central Repository Deployments</a>.</p>
<p>Here, you should see a new deployment with a <strong>Validated</strong> status and all your artifacts listed. Click
the <strong>Publish</strong> button to publish the artifacts. It typically takes around 5 minutes after clicking
<strong>Publish</strong> for the artifacts to go live.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../build/windows_android.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/contributing.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../build/windows_android.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/contributing.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>
+2 -2
View File
@@ -273,7 +273,7 @@ copy filament-android\build\outputs\aar\filament-android-release.aar ..\..\out\
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/contributing.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../build/maven_release.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
@@ -287,7 +287,7 @@ copy filament-android\build\outputs\aar\filament-android-release.aar ..\..\out\
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../dup/contributing.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<a rel="next prefetch" href="../build/maven_release.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
+2 -2
View File
@@ -209,7 +209,7 @@ as possible. The current external dependencies of the runtime library include:</
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../build/windows_android.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../build/maven_release.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
@@ -223,7 +223,7 @@ as possible. The current external dependencies of the runtime library include:</
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../build/windows_android.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<a rel="prev" href="../build/maven_release.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
+2 -2
View File
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.64.1'
implementation 'com.google.android.filament:filament-android:1.65.0'
}
</code></pre>
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
@@ -196,7 +196,7 @@ dependencies {
</div>
<h3 id="ios"><a class="header" href="#ios">iOS</a></h3>
<p>iOS projects can use CocoaPods to install the latest release:</p>
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.64.1'
<pre><code class="language-shell">pod 'Filament', '~&gt; 1.65.0'
</code></pre>
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
<ul>
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -3,6 +3,7 @@
- [Introduction](./dup/intro.md)
- [Build](./dup/building.md)
- [Build for Android on Windows](./build/windows_android.md)
- [Maven Release](./build/maven_release.md)
- [Contribute](./dup/contributing.md)
- [Coding Style](./dup/code_style.md)
- [Core Concepts](./main/README.md)
@@ -0,0 +1,89 @@
# Maven Release
## Register for a Sonatype Account
First, you'll need to register for a Sonatype account at the [Central
Portal](https://central.sonatype.org/register/central-portal/).
To publish under the `com.google.android` namespace, you'll also need to email
`central-support@sonatype.com` with your account information to request access.
Then, generate a user token through the Sonatype website. Navigate to
[https://central.sonatype.com/account](https://central.sonatype.com/account) and select **Generate
User Token**.
Finally, add the generated token credentials to `~/.gradle/gradle.properties`. (Note: these are
different from the credentials used to log into your Sonatype account):
```gradle
# Generated Sonatype token
sonatypeUsername=<username>
sonatypePassword=<password>
```
-----
## Signing Key
All release artifacts must be signed. You'll need to create an OpenPGP key pair. See Gradle's
documentation on [Signatory
credentials](https://docs.gradle.org/current/userguide/signing_plugin.html#sec:signatory_credentials)
for instructions.
Update `~/.gradle/gradle.properties` with your new key's credentials:
```gradle
signing.keyId=<key id>
signing.password=<key password>
signing.secretKeyRingFile=<secret key ring file>
```
-----
## Build the Android Release
Make sure `JAVA_HOME` is set correctly. For example:
```bash
export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
./build.sh -C -i -p android release
```
-----
## Publish to Sonatype
### A Note on the Legacy Staging Service
Previously, Filament was published to Maven via OSSRH (Open Source Software Repository Hosting). In
2025, this service was [sunsetted](https://central.sonatype.org/pages/ossrh-eol/). Now, we use
Sonatype's [Central Publisher Portal](https://central.sonatype.org/).
The new Central Publisher Portal does not officially support Gradle. However, Sonatype provides a
staging API compatibility service, which works with Filament's Gradle setup.
-----
### 1\. Upload to the Staging API Compatibility Service
```bash
cd android
./gradlew publishToSonatype
```
### 2\. Move the Repository to the Central Publisher Portal
We have a script to automate this. It reads the `sonatypeUsername` and `sonatypePassword` from your
`~/.gradle/gradle.properties` file.
```bash
python3 build/common/close-sonatype-staging-repository.py
```
### 3\. Publish the Release on Sonatype
Navigate to [Maven Central Repository Deployments](https://central.sonatype.com/publishing/deployments).
Here, you should see a new deployment with a **Validated** status and all your artifacts listed. Click
the **Publish** button to publish the artifacts. It typically takes around 5 minutes after clicking
**Publish** for the artifacts to go live.
+3
View File
@@ -40,6 +40,7 @@ set(PUBLIC_HDRS
include/filament/Skybox.h
include/filament/Stream.h
include/filament/SwapChain.h
include/filament/Sync.h
include/filament/Texture.h
include/filament/TextureSampler.h
include/filament/ToneMapper.h
@@ -103,6 +104,7 @@ set(SRCS
src/Skybox.cpp
src/Stream.cpp
src/SwapChain.cpp
src/Sync.cpp
src/Texture.cpp
src/ToneMapper.cpp
src/TransformManager.cpp
@@ -132,6 +134,7 @@ set(SRCS
src/details/Skybox.cpp
src/details/Stream.cpp
src/details/SwapChain.cpp
src/details/Sync.cpp
src/details/Texture.cpp
src/details/VertexBuffer.cpp
src/details/View.cpp
+1
View File
@@ -295,6 +295,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WebGPUSync.h
src/webgpu/WebGPUTexture.cpp
src/webgpu/WebGPUTexture.h
src/webgpu/WebGPUTextureHelpers.h
@@ -101,6 +101,16 @@ static constexpr uint64_t SWAP_CHAIN_HAS_STENCIL_BUFFER = SWAP_CHAIN_CON
*/
static constexpr uint64_t SWAP_CHAIN_CONFIG_PROTECTED_CONTENT = 0x40;
/**
* Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the
* given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is
* true.
*
* This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it
* because the swapchain MSAA settings must be configured before window creation.
*/
static constexpr uint64_t SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES = 0x80;
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
static constexpr size_t MAX_SAMPLER_COUNT = 62; // Maximum needed at feature level 3.
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
@@ -38,6 +38,7 @@ struct HwRenderPrimitive;
struct HwRenderTarget;
struct HwStream;
struct HwSwapChain;
struct HwSync;
struct HwTexture;
struct HwTimerQuery;
struct HwVertexBufferInfo;
@@ -151,6 +152,7 @@ using RenderPrimitiveHandle = Handle<HwRenderPrimitive>;
using RenderTargetHandle = Handle<HwRenderTarget>;
using StreamHandle = Handle<HwStream>;
using SwapChainHandle = Handle<HwSwapChain>;
using SyncHandle = Handle<HwSync>;
using TextureHandle = Handle<HwTexture>;
using TimerQueryHandle = Handle<HwTimerQuery>;
using VertexBufferHandle = Handle<HwVertexBuffer>;
@@ -29,6 +29,7 @@
namespace filament::backend {
class CallbackHandler;
class Driver;
/**
@@ -42,6 +43,9 @@ public:
struct SwapChain {};
struct Fence {};
struct Stream {};
struct Sync {};
using SyncCallback = void(*)(Sync* UTILS_NONNULL sync, void* UTILS_NULLABLE userData);
class ExternalImageHandle;
@@ -85,6 +85,15 @@ public:
*/
virtual bool isSRGBSwapChainSupported() const noexcept;
/**
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_MSAA_*_SAMPLES flag.
* The default implementation returns false.
*
* @param samples The number of samples
* @return true if SWAP_CHAIN_CONFIG_MSAA_*_SAMPLES is supported, false otherwise.
*/
virtual bool isMSAASwapChainSupported(uint32_t samples) const noexcept;
/**
* Return whether protected contexts are supported by this backend.
* If protected context are supported, the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag can be
@@ -276,6 +285,26 @@ public:
*/
virtual backend::FenceStatus waitFence(Fence* UTILS_NONNULL fence, uint64_t timeout) noexcept;
// --------------------------------------------------------------------------------------------
// Sync support
/**
* Creates a Sync. These can be used for frame synchronization externally
* (certain platform implementations can be exported to handles that can
* be used in other processes).
*
* @return A Sync object.
*/
virtual Platform::Sync* UTILS_NONNULL createSync() noexcept;
/**
* Destroys a sync. If called with a sync not created by this platform
* object, this will lead to undefined behavior.
*
* @param sync The sync to destroy, that was created by this platform
* instance.
*/
virtual void destroySync(Platform::Sync* UTILS_NONNULL sync) noexcept;
// --------------------------------------------------------------------------------------------
// Streaming support
@@ -98,8 +98,9 @@ protected:
void terminate() noexcept override;
bool isProtectedContextSupported() const noexcept override;
bool isSRGBSwapChainSupported() const noexcept override;
bool isMSAASwapChainSupported(uint32_t samples) const noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
@@ -58,6 +58,17 @@ public:
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(ExternalImageHandle externalImage) noexcept;
/**
* Converts a sync to an external file descriptor, if possible. Accepts an
* opaque handle to a sync, as well as a pointer to where the fd should be
* stored.
* @param sync The sync to be converted to a file descriptor.
* @param fd A pointer to where the file descriptor should be stored.
* @return `true` on success, `false` on failure. The default implementation
* returns `false`.
*/
bool convertSyncToFd(Platform::Sync* sync, int* fd) noexcept;
protected:
// --------------------------------------------------------------------------------------------
// Platform Interface
@@ -74,6 +85,10 @@ protected:
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
struct SyncEGLAndroid : public Platform::Sync {
EGLSyncKHR sync;
};
void terminate() noexcept override;
void beginFrame(
@@ -89,9 +104,10 @@ protected:
*/
void setPresentationTime(int64_t presentationTimeInNanosecond) noexcept override;
Stream* createStream(void* nativeStream) noexcept override;
void destroyStream(Stream* stream) noexcept override;
Platform::Sync* createSync() noexcept override;
void destroySync(Platform::Sync* sync) noexcept override;
void attach(Stream* stream, intptr_t tname) noexcept override;
void detach(Stream* stream) noexcept override;
void updateTexImage(Stream* stream, int64_t* timestamp) noexcept override;
@@ -17,8 +17,9 @@
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKANPLATFORM_H
#include <backend/Platform.h>
#include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include <bluevk/BlueVK.h>
@@ -29,9 +30,9 @@
#include <cstddef>
#include <functional>
#include <string>
#include <tuple>
#include <unordered_set>
#include <string>
#include <stddef.h>
#include <stdint.h>
@@ -45,6 +46,10 @@ using SwapChain = Platform::SwapChain;
*/
struct VulkanPlatformPrivate;
// Forward declare the fence status that will be maintained by the command
// buffer manager.
struct VulkanCmdFence;
/**
* A Platform interface that creates a Vulkan backend.
*/
@@ -229,6 +234,25 @@ public:
virtual SwapChainPtr createSwapChain(void* nativeWindow, uint64_t flags = 0,
VkExtent2D extent = {0, 0});
/**
* Creates a Platform::Sync object, which tracks a fence and its status,
* and allows conversion to an external sync.
* @param fence The underlying VkFence to use for synchronization.
* @param fenceStatus An object tracking the fence's state
* @return A Platform::Sync object tracking the provided fence.
*/
virtual Platform::Sync* createSync(VkFence fence,
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept;
/**
* Destroys a sync. If called with a sync not created by this platform
* object, this will lead to undefined behavior.
*
* @param sync The sync to destroy, which was created by this platform
* instance.
*/
virtual void destroySync(Platform::Sync* sync) noexcept;
/**
* Allows implementers to provide instance extensions that they'd like to include in the
* instance creation.
@@ -408,12 +432,19 @@ public:
}
protected:
struct VulkanSync : public Platform::Sync {
VkFence fence;
std::shared_ptr<VulkanCmdFence> fenceStatus;
};
virtual ExtensionSet getSwapchainInstanceExtensions() const;
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept;
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept;
private:
// Platform dependent helper methods
static ExtensionSet getSwapchainInstanceExtensionsImpl();
@@ -44,6 +44,17 @@ public:
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
/**
* Converts a sync to an external file descriptor, if possible. Accepts an
* opaque handle to a sync, as well as a pointer to where the fd should be
* stored.
* @param sync The sync to be converted to a file descriptor.
* @param fd A pointer to where the file descriptor should be stored.
* @return `true` on success, `false` on failure. The default implementation
* returns `false`.
*/
bool convertSyncToFd(Platform::Sync* sync, int* fd) const noexcept;
protected:
virtual ExtensionSet getSwapchainInstanceExtensions() const override;
@@ -51,6 +62,8 @@ protected:
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
uint64_t flags) const noexcept override;
virtual VkExternalFenceHandleTypeFlagBits getFenceExportFlags() const noexcept override;
private:
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
AHardwareBuffer* aHardwareBuffer = nullptr;
@@ -272,6 +272,8 @@ DECL_DRIVER_API_TAGGED_R_N(backend::RenderTargetHandle, createRenderTarget,
DECL_DRIVER_API_TAGGED_R_N(backend::FenceHandle, createFence)
DECL_DRIVER_API_TAGGED_R_N(backend::SyncHandle, createSync)
DECL_DRIVER_API_TAGGED_R_N(backend::SwapChainHandle, createSwapChain,
void*, nativeWindow,
uint64_t, flags)
@@ -328,6 +330,7 @@ DECL_DRIVER_API_N(destroySwapChain, backend::SwapChainHandle, sch)
DECL_DRIVER_API_N(destroyStream, backend::StreamHandle, sh)
DECL_DRIVER_API_N(destroyTimerQuery, backend::TimerQueryHandle, sh)
DECL_DRIVER_API_N(destroyFence, backend::FenceHandle, fh)
DECL_DRIVER_API_N(destroySync, backend::SyncHandle, sh)
DECL_DRIVER_API_N(destroyDescriptorSetLayout, backend::DescriptorSetLayoutHandle, dslh)
DECL_DRIVER_API_N(destroyDescriptorSet, backend::DescriptorSetHandle, dsh)
@@ -344,6 +347,8 @@ DECL_DRIVER_API_SYNCHRONOUS_N(void, setStreamDimensions, backend::StreamHandle,
DECL_DRIVER_API_SYNCHRONOUS_N(int64_t, getStreamTimestamp, backend::StreamHandle, stream)
DECL_DRIVER_API_SYNCHRONOUS_N(void, updateStreams, backend::DriverApi*, driver)
DECL_DRIVER_API_SYNCHRONOUS_N(backend::FenceStatus, getFenceStatus, backend::FenceHandle, fh)
DECL_DRIVER_API_SYNCHRONOUS_N(void, getPlatformSync, backend::SyncHandle, sh,
backend::CallbackHandler*, handler, backend::Platform::SyncCallback, cb, void*, userData)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatSupported, backend::TextureFormat, format)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isTextureSwizzleSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isTextureFormatMipmappable, backend::TextureFormat, format)
@@ -353,6 +358,7 @@ DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameBufferFetchMultiSampleSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isFrameTimeSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isAutoDepthResolveSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isSRGBSwapChainSupported)
DECL_DRIVER_API_SYNCHRONOUS_N(bool, isMSAASwapChainSupported, uint32_t, samples)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isProtectedContentSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isStereoSupported)
DECL_DRIVER_API_SYNCHRONOUS_0(bool, isParallelShaderCompileSupported)
+4
View File
@@ -140,6 +140,10 @@ struct HwFence : public HwBase {
Platform::Fence* fence = nullptr;
};
struct HwSync : public HwBase {
Platform::Sync* sync = nullptr;
};
struct HwSwapChain : public HwBase {
Platform::SwapChain* swapChain = nullptr;
};
+1 -1
View File
@@ -153,7 +153,7 @@ private:
Type mType = Type::NONE;
static PlatformMetal* platform;
static std::array<uint64_t, TypeCount> aliveBuffers;
static std::array<std::atomic<uint64_t>, TypeCount> aliveBuffers;
};
class MetalBuffer {
+1 -1
View File
@@ -24,7 +24,7 @@
namespace filament {
namespace backend {
std::array<uint64_t, TrackedMetalBuffer::TypeCount> TrackedMetalBuffer::aliveBuffers = { 0 };
std::array<std::atomic<uint64_t>, TrackedMetalBuffer::TypeCount> TrackedMetalBuffer::aliveBuffers = { 0 };
PlatformMetal* TrackedMetalBuffer::platform = nullptr;
PlatformMetal* ScopedAllocationTimer::platform = nullptr;
+30
View File
@@ -652,6 +652,8 @@ void MetalDriver::createFenceR(Handle<HwFence> fh, utils::CString tag) {
void MetalDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
utils::CString tag) {
// TODO: support MSAA swapchain
if (UTILS_UNLIKELY(flags & SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER)) {
CVPixelBufferRef pixelBuffer = (CVPixelBufferRef) nativeWindow;
construct_handle<MetalSwapChain>(sch, *mContext, mPlatform, pixelBuffer, flags);
@@ -671,6 +673,12 @@ void MetalDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch,
mHandleAllocator.associateTagToHandle(sch.getId(), std::move(tag));
}
void MetalDriver::createSyncR(Handle<HwSync> sh, utils::CString tag) {
// TODO: Ensure sync is active, and then invoke and clear all pending
// callbacks.
mHandleAllocator.associateTagToHandle(sh.getId(), std::move(tag));
}
void MetalDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, utils::CString tag) {
// nothing to do, timer query was constructed in createTimerQueryS
mHandleAllocator.associateTagToHandle(tqh.getId(), std::move(tag));
@@ -816,6 +824,10 @@ Handle<HwSwapChain> MetalDriver::createSwapChainHeadlessS() noexcept {
return alloc_handle<MetalSwapChain>();
}
Handle<HwSync> MetalDriver::createSyncS() noexcept {
return alloc_handle<MetalSync>();
}
Handle<HwTimerQuery> MetalDriver::createTimerQueryS() noexcept {
// The handle must be constructed here, as a synchronous call to getTimerQueryValue might happen
// before createTimerQueryR is executed.
@@ -1017,6 +1029,19 @@ FenceStatus MetalDriver::getFenceStatus(Handle<HwFence> fh) {
return fence->wait(0);
}
void MetalDriver::destroySync(Handle<HwSync> sh) {
if (sh) {
destruct_handle<MetalSync>(sh);
}
}
void MetalDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
Platform::SyncCallback cb, void* userData) {
// TODO: If the sync has been inserted into the command stream, execute
// the callback. Otherwise, enqueue it.
}
bool MetalDriver::isTextureFormatSupported(TextureFormat format) {
return MetalTexture::decidePixelFormat(mContext, format) != MTLPixelFormatInvalid;
}
@@ -1098,6 +1123,11 @@ bool MetalDriver::isSRGBSwapChainSupported() {
return false;
}
bool MetalDriver::isMSAASwapChainSupported(uint32_t) {
// TODO: support MSAA swapchain
return false;
}
bool MetalDriver::isProtectedContentSupported() {
// the SWAP_CHAIN_CONFIG_PROTECTED_CONTENT flag is not supported
return false;
@@ -437,6 +437,10 @@ private:
uint64_t value;
};
// TODO: Provide implementation for MetalSync.
class MetalSync : public HwSync {
};
struct MetalTimerQuery : public HwTimerQuery {
MetalTimerQuery() : status(std::make_shared<Status>()) {}
+12 -2
View File
@@ -16,12 +16,11 @@
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/Platform.h>
#include "noop/NoopDriver.h"
#include "CommandStreamDispatcher.h"
#include <backend/Handle.h>
#include <stdint.h>
namespace filament::backend {
@@ -116,6 +115,9 @@ void NoopDriver::destroySwapChain(Handle<HwSwapChain> sch) {
void NoopDriver::destroyStream(Handle<HwStream> sh) {
}
void NoopDriver::destroySync(Handle<HwSync> sh) {
}
void NoopDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
@@ -147,6 +149,10 @@ int64_t NoopDriver::getStreamTimestamp(Handle<HwStream> sh) {
void NoopDriver::updateStreams(CommandStream* driver) {
}
void NoopDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
Platform::SyncCallback cb, void* userData) {
}
void NoopDriver::destroyFence(Handle<HwFence> fh) {
}
@@ -192,6 +198,10 @@ bool NoopDriver::isSRGBSwapChainSupported() {
return false;
}
bool NoopDriver::isMSAASwapChainSupported(uint32_t) {
return false;
}
bool NoopDriver::isProtectedContentSupported() {
return false;
}
@@ -149,6 +149,18 @@ using namespace utils;
namespace filament::backend {
namespace {
static CallbackHandler::Callback syncCallbackWrapper = [](void* userData) {
std::unique_ptr<OpenGLDriver::GLSyncFence::CallbackData> cbData(
static_cast<OpenGLDriver::GLSyncFence::CallbackData*>(userData));
// This assumes the sync has not yet been destroyed. If it has, this will be
// undefined behavior.
cbData->cb(cbData->sync, cbData->userData);
};
} // namespace
Driver* OpenGLDriverFactory::create(
OpenGLPlatform* platform,
void* sharedGLContext,
@@ -615,6 +627,10 @@ Handle<HwFence> OpenGLDriver::createFenceS() noexcept {
return initHandle<GLFence>();
}
Handle<HwSync> OpenGLDriver::createSyncS() noexcept {
return initHandle<GLSyncFence>();
}
Handle<HwSwapChain> OpenGLDriver::createSwapChainS() noexcept {
return initHandle<GLSwapChain>();
}
@@ -1807,6 +1823,24 @@ void OpenGLDriver::createFenceR(Handle<HwFence> fh, utils::CString tag) {
mHandleAllocator.associateTagToHandle(fh.getId(), std::move(tag));
}
void OpenGLDriver::createSyncR(Handle<HwSync> sh, utils::CString tag) {
DEBUG_MARKER()
GLSyncFence* s = handle_cast<GLSyncFence*>(sh);
{
std::lock_guard<std::mutex> guard(s->lock);
s->sync = mPlatform.createSync();
}
for (auto& cbData : s->conversionCallbacks) {
cbData->sync = s->sync;
scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper);
}
s->conversionCallbacks.clear();
mHandleAllocator.associateTagToHandle(sh.getId(), std::move(tag));
}
void OpenGLDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
utils::CString tag) {
DEBUG_MARKER()
@@ -2080,6 +2114,16 @@ void OpenGLDriver::destroyStream(Handle<HwStream> sh) {
}
}
void OpenGLDriver::destroySync(Handle<HwSync> sh) {
DEBUG_MARKER()
if (sh) {
GLSyncFence* s = handle_cast<GLSyncFence*>(sh);
mPlatform.destroySync(s->sync);
destruct(sh, s);
}
}
void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
DEBUG_MARKER()
@@ -2268,6 +2312,33 @@ FenceStatus OpenGLDriver::getFenceStatus(Handle<HwFence> fh) {
return FenceStatus::ERROR;
}
void OpenGLDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
Platform::SyncCallback cb, void* userData) {
if (!sh) {
return;
}
GLSyncFence* s = handle_cast<GLSyncFence*>(sh);
auto cbData = std::make_unique<GLSyncFence::CallbackData>();
cbData->handler = handler;
cbData->cb = cb;
cbData->userData = userData;
// If we haven't already set the handle, toss the conversion callback in the
// back of the list.
{
std::lock_guard guard(s->lock);
if (s->sync == nullptr) {
s->conversionCallbacks.push_back(std::move(cbData));
return;
}
}
// Otherwise, go ahead and schedule the callback now.
cbData->sync = s->sync;
scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper);
}
bool OpenGLDriver::isTextureFormatSupported(TextureFormat format) {
const auto& ext = mContext.ext;
if (isETC2Compression(format)) {
@@ -2433,6 +2504,10 @@ bool OpenGLDriver::isSRGBSwapChainSupported() {
return mPlatform.isSRGBSwapChainSupported();
}
bool OpenGLDriver::isMSAASwapChainSupported(uint32_t samples) {
return mPlatform.isMSAASwapChainSupported(samples);
}
bool OpenGLDriver::isProtectedContentSupported() {
return mPlatform.isProtectedContextSupported();
}
@@ -198,6 +198,19 @@ public:
std::shared_ptr<State> state{ std::make_shared<State>() };
};
// Note: named "GLSyncFence" to avoid confusion with the GL handle,
// "GLsync" (lowercase S)
struct GLSyncFence : public HwSync {
struct CallbackData {
CallbackHandler* handler;
Platform::SyncCallback cb;
Platform::Sync* sync;
void* userData;
};
std::mutex lock;
std::vector<std::unique_ptr<CallbackData>> conversionCallbacks;
};
OpenGLDriver(OpenGLDriver const&) = delete;
OpenGLDriver& operator=(OpenGLDriver const&) = delete;
@@ -76,6 +76,10 @@ bool OpenGLPlatform::isSRGBSwapChainSupported() const noexcept {
return false;
}
bool OpenGLPlatform::isMSAASwapChainSupported(uint32_t) const noexcept {
return false;
}
uint32_t OpenGLPlatform::getDefaultFramebufferObject() noexcept {
return 0;
}
@@ -117,6 +121,15 @@ FenceStatus OpenGLPlatform::waitFence(
return FenceStatus::ERROR;
}
Platform::Sync* OpenGLPlatform::createSync() noexcept {
return new Platform::Sync();
}
void OpenGLPlatform::destroySync(Platform::Sync* sync) noexcept {
// sync must be a Platform::Sync, since it was created by this platform
// object.
delete sync;
}
Platform::Stream* OpenGLPlatform::createStream(
UTILS_UNUSED void* nativeStream) noexcept {
@@ -448,6 +448,11 @@ EGLConfig PlatformEGL::findSwapChainConfig(uint64_t flags, bool window, bool pbu
configAttribs[EGL_RECORDABLE_ANDROID] = EGL_TRUE;
}
if (flags & SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
configAttribs[EGL_SAMPLE_BUFFERS] = 1;
configAttribs[EGL_SAMPLES] = 4;
}
if (UTILS_UNLIKELY(
!eglChooseConfig(mEGLDisplay, configAttribs.data(), &config, 1, &configsCount))) {
logEglError("eglChooseConfig");
@@ -481,11 +486,48 @@ bool PlatformEGL::isSRGBSwapChainSupported() const noexcept {
return ext.egl.KHR_gl_colorspace;
}
bool PlatformEGL::isMSAASwapChainSupported(uint32_t samples) const noexcept {
if (samples <= 1) {
return true;
}
Config configAttribs = {
{ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT },
{ EGL_RED_SIZE, 8 },
{ EGL_GREEN_SIZE, 8 },
{ EGL_BLUE_SIZE, 8 },
{ EGL_DEPTH_SIZE, 24 },
{ EGL_SAMPLE_BUFFERS, 1 },
{ EGL_SAMPLES, samples },
};
if (!ext.egl.KHR_no_config_context) {
if (isOpenGL()) {
configAttribs[EGL_RENDERABLE_TYPE] = EGL_OPENGL_BIT;
} else {
configAttribs[EGL_RENDERABLE_TYPE] = EGL_OPENGL_ES2_BIT;
if (ext.egl.KHR_create_context) {
configAttribs[EGL_RENDERABLE_TYPE] |= EGL_OPENGL_ES3_BIT_KHR;
}
}
}
EGLConfig config = EGL_NO_CONFIG_KHR;
EGLint configsCount;
if (!eglChooseConfig(mEGLDisplay, configAttribs.data(), &config, 1, &configsCount)) {
return false;
}
return configsCount > 0;
}
Platform::SwapChain* PlatformEGL::createSwapChain(
void* nativeWindow, uint64_t flags) noexcept {
Config attribs;
if (ext.egl.KHR_gl_colorspace) {
// Remove flags for unsupported features.
if (isSRGBSwapChainSupported()) {
if (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
attribs[EGL_GL_COLORSPACE_KHR] = EGL_GL_COLORSPACE_SRGB_KHR;
}
@@ -493,7 +535,7 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
flags &= ~SWAP_CHAIN_CONFIG_SRGB_COLORSPACE;
}
if (ext.egl.EXT_protected_content) {
if (isProtectedContextSupported()) {
if (flags & SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
attribs[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
}
@@ -501,6 +543,13 @@ Platform::SwapChain* PlatformEGL::createSwapChain(
flags &= ~SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
}
if (flags & SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
if (!isMSAASwapChainSupported(4)) {
flags &= ~SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
}
}
// Retrieve a config for the given flags.
EGLConfig config = EGL_NO_CONFIG_KHR;
if (UTILS_LIKELY(ext.egl.KHR_no_config_context)) {
config = findSwapChainConfig(flags, true, false);
@@ -83,6 +83,7 @@ UTILS_PRIVATE PFNEGLGETCOMPOSITORTIMINGANDROIDPROC eglGetCompositorTimingANDROID
UTILS_PRIVATE PFNEGLGETNEXTFRAMEIDANDROIDPROC eglGetNextFrameIdANDROID = {};
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSUPPORTEDANDROIDPROC eglGetFrameTimestampSupportedANDROID = {};
UTILS_PRIVATE PFNEGLGETFRAMETIMESTAMPSANDROIDPROC eglGetFrameTimestampsANDROID = {};
UTILS_PRIVATE PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID = {};
}
using namespace glext;
@@ -226,6 +227,8 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetFrameTimestampSupportedANDROID");
eglGetFrameTimestampsANDROID = (PFNEGLGETFRAMETIMESTAMPSANDROIDPROC)eglGetProcAddress(
"eglGetFrameTimestampsANDROID");
eglDupNativeFenceFDANDROID =
(PFNEGLDUPNATIVEFENCEFDANDROIDPROC) eglGetProcAddress("eglDupNativeFenceFDANDROID");
}
mAssertNativeWindowIsValid = driverConfig.assertNativeWindowIsValid;
@@ -395,6 +398,31 @@ void PlatformEGLAndroid::destroyStream(Platform::Stream* stream) noexcept {
mExternalStreamManager.release(stream);
}
Platform::Sync* PlatformEGLAndroid::createSync() noexcept {
auto sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
return new SyncEGLAndroid{.sync = sync};
}
bool PlatformEGLAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) noexcept {
assert_invariant(sync && fd);
SyncEGLAndroid& eglSync = static_cast<SyncEGLAndroid&>(*sync);
*fd = eglDupNativeFenceFDANDROID(mEGLDisplay, eglSync.sync);
// In the case where there was no native FD, -1 is returned. Return false
// to indicate there was an error in this case.
if (*fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
LOG(ERROR) << "Failed to convert sync to fd: " << eglGetError();
return false;
}
return true;
}
void PlatformEGLAndroid::destroySync(Platform::Sync* sync) noexcept {
assert_invariant(sync);
SyncEGLAndroid& eglSync = static_cast<SyncEGLAndroid&>(*sync);
eglDestroySyncKHR(mEGLDisplay, eglSync.sync);
delete sync;
}
void PlatformEGLAndroid::attach(Stream* stream, intptr_t tname) noexcept {
mExternalStreamManager.attach(stream, tname);
}
@@ -20,6 +20,7 @@
#include <bluevk/BlueVK.h>
#include "DriverBase.h"
#include "backend/Platform.h"
#include "vulkan/memory/Resource.h"
@@ -52,6 +53,19 @@ struct VulkanFence : public HwFence, fvkmemory::ThreadSafeResource {
std::shared_ptr<VulkanCmdFence> fence;
};
struct VulkanSync : fvkmemory::ThreadSafeResource, public HwSync {
struct CallbackData {
CallbackHandler* handler;
Platform::SyncCallback cb;
Platform::Sync* sync;
void* userData;
};
VulkanSync() {}
std::mutex lock;
std::vector<std::unique_ptr<CallbackData>> conversionCallbacks;
};
struct VulkanTimerQuery : public HwTimerQuery, fvkmemory::ThreadSafeResource {
VulkanTimerQuery(uint32_t startingIndex, uint32_t stoppingIndex)
: mStartingQueryIndex(startingIndex),
@@ -96,7 +96,6 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
VulkanLayout oldSrcLayout = src.getLayout();
VulkanLayout oldDstLayout = dst.getLayout();
src.texture->transitionLayout(commands, srcRange, VulkanLayout::TRANSFER_SRC);
dst.texture->transitionLayout(commands, dstRange, VulkanLayout::TRANSFER_DST);
assert_invariant(
@@ -109,10 +108,9 @@ inline void resolveFast(VulkanCommandBuffer* commands, VkImageAspectFlags aspect
.extent = { src.getExtent2D().width, src.getExtent2D().height, 1 },
}};
vkCmdResolveImage(cmdbuffer,
src.getImage(), fvkutils::getVkLayout(VulkanLayout::TRANSFER_SRC),
src.getImage(), fvkutils::getVkLayout(src.getLayout()),
dst.getImage(), fvkutils::getVkLayout(VulkanLayout::TRANSFER_DST),
1, resolveRegions);
if (oldSrcLayout == VulkanLayout::UNDEFINED) {
oldSrcLayout = src.texture->getDefaultLayout();
}
@@ -103,6 +103,12 @@ VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice
vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission);
VkFenceCreateInfo fenceCreateInfo{.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
VkExportFenceCreateInfo exportFenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_EXPORT_FENCE_CREATE_INFO,
.handleTypes = context.getFenceExportFlags()
};
fenceCreateInfo.pNext = &exportFenceCreateInfo;
vkCreateFence(device, &fenceCreateInfo, VKALLOC, &mFence);
}
@@ -425,6 +431,9 @@ bool VulkanCommands::flush() {
VkSemaphore lastSubmit = mLastSubmit;
bool hasFlushed = false;
VkFence flushedFence = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> flushedFenceStatus;
// Note that we've ordered it so that the non-protected commands are followed by the protected
// commands. This assumes that the protected commands will be that one doing the rendering into
// the protected memory (i.e. protected render target).
@@ -446,6 +455,8 @@ bool VulkanCommands::flush() {
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT);
lastSubmit = VK_NULL_HANDLE;
}
flushedFence = pool->getRecording().getVkFence();
flushedFenceStatus = pool->getRecording().getFenceStatus();
dependency = pool->flush();
hasFlushed = true;
}
@@ -453,6 +464,8 @@ bool VulkanCommands::flush() {
if (hasFlushed) {
mInjectedDependency = VK_NULL_HANDLE;
mLastSubmit = dependency;
mLastFence = flushedFence;
mLastFenceStatus = flushedFenceStatus;
}
return true;
+12 -1
View File
@@ -223,11 +223,19 @@ public:
// it from the existing dependency chain. This is especially useful for setting up
// vkQueuePresentKHR.
VkSemaphore acquireFinishedSignal() {
VkSemaphore ret= mLastSubmit;
VkSemaphore ret = mLastSubmit;
mLastSubmit = VK_NULL_HANDLE;
return ret;
}
VkFence getMostRecentFence() {
return mLastFence;
}
std::shared_ptr<VulkanCmdFence> getMostRecentFenceStatus() {
return mLastFenceStatus;
}
// Takes a semaphore that signals when the next flush can occur. Only one injected
// semaphore is allowed per flush. Useful after calling vkAcquireNextImageKHR.
// waitStage
@@ -265,6 +273,9 @@ private:
VkSemaphore mInjectedDependency = VK_NULL_HANDLE;
VkSemaphore mLastSubmit = VK_NULL_HANDLE;
VkFence mLastFence = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> mLastFenceStatus;
VkPipelineStageFlags mInjectedDependencyWaitStage = 0;
};
@@ -106,6 +106,10 @@ public:
return mPhysicalDeviceProperties.properties.vendorID;
}
inline VkExternalFenceHandleTypeFlags getFenceExportFlags() const noexcept {
return mFenceExportFlags;
}
inline bool isImageCubeArraySupported() const noexcept {
return mPhysicalDeviceFeatures.features.imageCubeArray == VK_TRUE;
}
@@ -168,6 +172,9 @@ private:
// non-conformant vulkan implementation).
.imageView2DOn3DImage = VK_TRUE,
};
VkExternalFenceHandleTypeFlags mFenceExportFlags = {};
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mLazilyAllocatedMemorySupported = false;
+111 -8
View File
@@ -35,6 +35,7 @@
#include <backend/DriverEnums.h>
#include <backend/platforms/VulkanPlatform.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/Panic.h>
@@ -136,6 +137,13 @@ VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsCallback(VkDebugUtilsMessageSeverityFla
}
#endif // FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
static CallbackHandler::Callback syncCallbackWrapper = [](void* userData) {
std::unique_ptr<VulkanSync::CallbackData> cbData(
static_cast<VulkanSync::CallbackData*>(userData));
// This assumes the sync has not yet been destroyed. If it has, this will be
// undefined behavior.
cbData->cb(cbData->sync, cbData->userData);
};
}// anonymous namespace
@@ -232,6 +240,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext& context,
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
&mDescriptorSetLayoutCache),
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
mIsMSAASwapChainSupported(false), // TODO: support MSAA swapchain
mStereoscopicType(driverConfig.stereoscopicType) {
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -476,6 +485,16 @@ void VulkanDriver::finish(int dummy) {
FVK_SYSTRACE_SCOPE();
endCommandRecording();
// It's not enough to wait on the fences of the buffers submitted. Present calls are
// submitted to this same queue, so the more correct option is to call vkQueueWaitIdle.
vkQueueWaitIdle(mPlatform->getGraphicsQueue());
if (auto protectedQueue = mPlatform->getProtectedGraphicsQueue();
UTILS_UNLIKELY(protectedQueue != VK_NULL_HANDLE)) {
vkQueueWaitIdle(protectedQueue);
}
// This wait() is still necessary to update the fence status.
mCommands.wait();
mReadPixels.runUntilComplete();
@@ -799,10 +818,8 @@ void VulkanDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
}
auto rt = resource_ptr<VulkanRenderTarget>::cast(&mResourceManager, rth);
if (UTILS_UNLIKELY(rt == mDefaultRenderTarget)) {
// Note that this should only happen on driver shutdown.
mDefaultRenderTarget = {};
} else {
// Note that the default render target will be destroyed on shutdown
if (UTILS_LIKELY(rt != mDefaultRenderTarget)) {
rt.dec();
}
}
@@ -822,6 +839,36 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, utils::CString tag) {
mResourceManager.associateHandle(fh.getId(), std::move(tag));
}
void VulkanDriver::createSyncR(Handle<HwSync> sh, utils::CString tag) {
auto sync = resource_ptr<VulkanSync>::cast(&mResourceManager, sh);
VkFence fence = VK_NULL_HANDLE;
std::shared_ptr<VulkanCmdFence> fenceStatus;
if (mCurrentRenderPass.commandBuffer) {
VulkanCommandBuffer* cmdBuff = mCurrentRenderPass.commandBuffer;
fence = cmdBuff->getVkFence();
fenceStatus = cmdBuff->getFenceStatus();
// If we're currently recording, flush so that the fence only applies
// to commands already issued.
mCommands.flush();
} else {
fence = mCommands.getMostRecentFence();
fenceStatus = mCommands.getMostRecentFenceStatus();
}
{
std::lock_guard<std::mutex> guard(sync->lock);
sync->sync = mPlatform->createSync(fence, fenceStatus);
}
for (auto& cbData : sync->conversionCallbacks) {
cbData->sync = sync->sync;
scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper);
}
sync->conversionCallbacks.clear();
mResourceManager.associateHandle(sh.getId(), std::move(tag));
}
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags,
utils::CString tag) {
FVK_SYSTRACE_SCOPE();
@@ -830,10 +877,16 @@ void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
// vkCreateSwapchainKHR with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR.
mResourceManager.gc();
// TODO: support MSAA swapchain
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 && !isSRGBSwapChainSupported()) {
FVK_LOGW << "sRGB swapchain requested, but Platform does not support it";
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
if ((flags & backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) != 0 && !isMSAASwapChainSupported(4)) {
FVK_LOGW << "MSAAx4 swapchain requested, but Platform does not support it";
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES);
}
if (flags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
if (!isProtectedContentSupported()) {
FVK_LOGW << "protected swapchain requested, but Platform does not support it";
@@ -954,6 +1007,13 @@ Handle<HwFence> VulkanDriver::createFenceS() noexcept {
return handle;
}
Handle<HwSync> VulkanDriver::createSyncS() noexcept {
auto handle = mResourceManager.allocHandle<VulkanSync>();
auto sync = resource_ptr<VulkanSync>::make(&mResourceManager, handle);
sync.inc();
return handle;
}
Handle<HwSwapChain> VulkanDriver::createSwapChainS() noexcept {
return mResourceManager.allocHandle<VulkanSwapChain>();
}
@@ -992,6 +1052,17 @@ void VulkanDriver::destroySwapChain(Handle<HwSwapChain> sch) {
void VulkanDriver::destroyStream(Handle<HwStream> sh) {
}
void VulkanDriver::destroySync(Handle<HwSync> sh) {
if (!sh) {
return;
}
FVK_SYSTRACE_SCOPE();
auto sync = resource_ptr<VulkanSync>::cast(&mResourceManager, sh);
mPlatform->destroySync(sync->sync);
sync.dec();
}
void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
if (!tqh) {
return;
@@ -1010,7 +1081,7 @@ void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
set.dec();
if (mAppState.hasExternalSamplers() && set->getExternalSamplerVkSet() != VK_NULL_HANDLE) {
if (mAppState.hasExternalSamplers()) {
mExternalImageManager.removeDescriptorSet(set);
}
}
@@ -1067,6 +1138,29 @@ FenceStatus VulkanDriver::getFenceStatus(Handle<HwFence> fh) {
return FenceStatus::TIMEOUT_EXPIRED;
}
void VulkanDriver::getPlatformSync(Handle<HwSync> sh, CallbackHandler* handler,
Platform::SyncCallback cb, void* userData) {
auto sync = resource_ptr<VulkanSync>::cast(&mResourceManager, sh);
auto cbData = std::make_unique<VulkanSync::CallbackData>();
cbData->handler = handler;
cbData->cb = cb;
cbData->userData = userData;
// If we haven't already set the handle, toss the conversion callback in the
// back of the list.
{
std::lock_guard guard(sync->lock);
if (sync->sync == nullptr) {
sync->conversionCallbacks.push_back(std::move(cbData));
return;
}
}
// Otherwise, go ahead and schedule the callback now.
cbData->sync = sync->sync;
scheduleCallback(cbData->handler, cbData.release(), syncCallbackWrapper);
}
// We create all textures using VK_IMAGE_TILING_OPTIMAL, so our definition of "supported" is that
// the GPU supports the given texture format with non-zero optimal tiling features.
bool VulkanDriver::isTextureFormatSupported(TextureFormat format) {
@@ -1129,6 +1223,10 @@ bool VulkanDriver::isSRGBSwapChainSupported() {
return mIsSRGBSwapChainSupported;
}
bool VulkanDriver::isMSAASwapChainSupported(uint32_t) {
return mIsMSAASwapChainSupported;
}
bool VulkanDriver::isProtectedContentSupported() {
return mContext.isProtectedMemorySupported();
}
@@ -1813,9 +1911,14 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
VkOffset3D const srcOffsets[2] = { { srcLeft, srcTop, 0 }, { srcRight, srcBottom, 1 }};
VkOffset3D const dstOffsets[2] = { { dstLeft, dstTop, 0 }, { dstRight, dstBottom, 1 }};
mBlitter.blit(vkfilter,
dstTarget->getColor0(), dstOffsets,
srcTarget->getColor0(), srcOffsets);
auto const& dstAttachment = dstTarget->getColor0();
auto const& srcAttachment = srcTarget->getColor0();
if (srcAttachment.texture->samples > 1) {
mBlitter.resolve(dstAttachment, srcAttachment);
} else {
mBlitter.blit(vkfilter, dstAttachment, dstOffsets, srcAttachment, srcOffsets);
}
}
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
@@ -187,6 +187,7 @@ private:
} mAppState;
bool const mIsSRGBSwapChainSupported;
bool const mIsMSAASwapChainSupported;
backend::StereoscopicType const mStereoscopicType;
};
@@ -38,6 +38,7 @@ template ResourceType getTypeEnum<VulkanDescriptorSetLayout>() noexcept;
template ResourceType getTypeEnum<VulkanDescriptorSet>() noexcept;
template ResourceType getTypeEnum<VulkanFence>() noexcept;
template ResourceType getTypeEnum<VulkanBuffer>() noexcept;
template ResourceType getTypeEnum<VulkanSync>() noexcept;
template<typename D>
ResourceType getTypeEnum() noexcept {
@@ -92,6 +93,9 @@ ResourceType getTypeEnum() noexcept {
if constexpr (std::is_same_v<D, VulkanBuffer>) {
return ResourceType::VULKAN_BUFFER;
}
if constexpr (std::is_same_v<D, VulkanSync>) {
return ResourceType::SYNC;
}
return ResourceType::UNDEFINED_TYPE;
}
@@ -131,6 +135,8 @@ std::string_view getTypeStr(ResourceType type) {
return "Fence";
case ResourceType::VULKAN_BUFFER:
return "VulkanBuffer";
case ResourceType::SYNC:
return "Sync";
case ResourceType::UNDEFINED_TYPE:
return "";
}
@@ -52,7 +52,8 @@ enum class ResourceType : uint8_t {
VULKAN_BUFFER = 14,
STAGE_SEGMENT = 15,
STAGE_IMAGE = 16,
UNDEFINED_TYPE = 17, // Must be the last enum because we use it for iterating over the enums.
SYNC = 17,
UNDEFINED_TYPE = 18, // Must be the last enum because we use it for iterating over the enums.
};
template<typename D>
@@ -114,6 +114,9 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
case ResourceType::VULKAN_BUFFER:
destruct<VulkanBuffer>(Handle<VulkanBuffer>(id));
break;
case ResourceType::SYNC:
destruct<VulkanSync>(Handle<VulkanSync>(id));
break;
case ResourceType::UNDEFINED_TYPE:
break;
}
@@ -183,7 +183,6 @@ ExtensionSet getInstanceExtensions(ExtensionSet const& externallyRequiredExts =
ExtensionSet const TARGET_EXTS = {
// Request all cross-platform extensions.
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
// Request these if available.
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
@@ -236,9 +235,6 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
#if defined(__APPLE__)
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME,
#endif
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
VK_KHR_MAINTENANCE3_EXTENSION_NAME,
VK_KHR_MULTIVIEW_EXTENSION_NAME,
};
ExtensionSet exts;
@@ -920,6 +916,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
context.mBlittableDepthStencilFormats =
findBlittableDepthStencilFormats(mImpl->mPhysicalDevice);
context.mFenceExportFlags = getFenceExportFlags();
assert_invariant(context.mDepthStencilFormats.size() > 0);
#if FVK_ENABLED(FVK_DEBUG_VALIDATION)
@@ -1002,6 +1000,17 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
return swapchain;
}
Platform::Sync* VulkanPlatform::createSync(VkFence fence,
std::shared_ptr<VulkanCmdFence> fenceStatus) noexcept {
return new VulkanSync{.fence = fence, .fenceStatus = fenceStatus};
}
void VulkanPlatform::destroySync(Platform::Sync* sync) noexcept {
// Sync must be a VulkanSync*, since it was created by VulkanPlatform's
// createSync object.
delete sync;
}
VkInstance VulkanPlatform::getInstance() const noexcept {
return mImpl->mInstance;
}
@@ -1038,6 +1047,11 @@ VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
return mImpl->mProtectedGraphicsQueue;
}
VkExternalFenceHandleTypeFlagBits VulkanPlatform::getFenceExportFlags() const noexcept {
// By default, fences should not be exportable.
return static_cast<VkExternalFenceHandleTypeFlagBits>(0);
}
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
return getSwapchainInstanceExtensionsImpl();
}
@@ -389,6 +389,37 @@ VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
};
}
bool VulkanPlatformAndroid::convertSyncToFd(Platform::Sync* sync, int* fd) const noexcept {
assert_invariant(sync && fd);
VulkanSync& vulkanSync = static_cast<VulkanSync&>(*sync);
assert_invariant(vulkanSync.fenceStatus);
if (vulkanSync.fenceStatus->getStatus() == VK_SUCCESS) {
// We've already signaled; return -1 so that operations will proceed
// immediately. Also, signal that fence conversion was successful.
*fd = -1;
return true;
}
VkFenceGetFdInfoKHR getFdInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_GET_FD_INFO_KHR,
.fence = vulkanSync.fence,
.handleType = getFenceExportFlags(),
};
VkResult res = vkGetFenceFdKHR(getDevice(), &getFdInfo, fd);
if (res != VK_SUCCESS) {
LOG(ERROR) << "Failed to convert sync to fd: " << res;
return false;
}
return true;
}
VkExternalFenceHandleTypeFlagBits VulkanPlatformAndroid::getFenceExportFlags() const noexcept {
// On android, make fences that can be exported to fd's.
return VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT;
}
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
return {
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
@@ -28,8 +28,8 @@
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#ifndef VK_MVK_macos_surface
#error VK_MVK_macos_surface is not defined
#ifndef VK_EXT_metal_surface
#error VK_EXT_metal_surface is not defined
#endif
using namespace bluevk;
@@ -38,7 +38,7 @@ namespace filament::backend {
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
ExtensionSet const ret = {
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
VK_EXT_METAL_SURFACE_EXTENSION_NAME,
};
return ret;
}
@@ -46,17 +46,18 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
VkInstance instance, uint64_t flags) noexcept {
VkSurfaceKHR surface;
NSView* nsview = (__bridge NSView*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
CAMetalLayer* mlayer = (__bridge CAMetalLayer*) nativeWindow;
FILAMENT_CHECK_POSTCONDITION(mlayer) << "Unable to obtain Metal-backed layer.";
// Create the VkSurface.
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
<< "Unable to load vkCreateMacOSSurfaceMVK.";
VkMacOSSurfaceCreateInfoMVK createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
createInfo.pView = (__bridge void*) nsview;
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
(VkSurfaceKHR*) &surface);
FILAMENT_CHECK_POSTCONDITION(vkCreateMetalSurfaceEXT)
<< "Unable to load vkCreateMetalSurfaceEXT.";
VkMetalSurfaceCreateInfoEXT createInfo = {
.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT,
.pLayer = mlayer,
};
VkResult result =
vkCreateMetalSurfaceEXT(instance, &createInfo, VKALLOC, &surface);
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
return std::make_tuple(surface, VkExtent2D{});
@@ -28,6 +28,7 @@
#include "WebGPURenderTarget.h"
#include "WebGPUStrings.h"
#include "WebGPUSwapChain.h"
#include "WebGPUSync.h"
#include "WebGPUTexture.h"
#include "WebGPUTextureHelpers.h"
#include "WebGPUVertexBuffer.h"
@@ -377,6 +378,10 @@ Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
return allocAndConstructHandle<WebGPUFence, HwFence>();
}
Handle<HwSync> WebGPUDriver::createSyncS() noexcept {
return allocHandle<WebGPUSync>();
}
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
return Handle<HwTimerQuery>((Handle<HwTimerQuery>::HandleId) mNextFakeHandle++);
}
@@ -447,6 +452,9 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
const uint64_t flags, utils::CString tag) {
FWGPU_SYSTRACE_SCOPE();
// TODO: support MSAA swapchain
mNativeWindow = nativeWindow;
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
@@ -650,6 +658,12 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fenceHandle, utils::CString tag)
setDebugTag(fenceHandle.getId(), std::move(tag));
}
void WebGPUDriver::createSyncR(Handle<HwSync> syncHandle, utils::CString tag) {
// TODO: Ensure sync is active, and then invoke and clear all pending
// callbacks.
setDebugTag(syncHandle.getId(), std::move(tag));
}
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, utils::CString tag) {}
void WebGPUDriver::createDescriptorSetLayoutR(
@@ -718,6 +732,18 @@ FenceStatus WebGPUDriver::getFenceStatus(Handle<HwFence> fenceHandle) {
return fence->getStatus();
}
void WebGPUDriver::destroySync(Handle<HwSync> syncHandle) {
if (syncHandle) {
destructHandle<WebGPUSync>(syncHandle);
}
}
void WebGPUDriver::getPlatformSync(Handle<HwSync> syncHandle, CallbackHandler* handler,
Platform::SyncCallback cb, void* userData) {
// TODO: If the sync has been inserted into the command stream, execute
// the callback. Otherwise, enqueue it.
}
bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
return toWGPUTextureFormat(format) != wgpu::TextureFormat::Undefined;
}
@@ -767,6 +793,10 @@ bool WebGPUDriver::isSRGBSwapChainSupported() {
return false;
}
bool WebGPUDriver::isMSAASwapChainSupported(uint32_t) {
return false;
}
bool WebGPUDriver::isProtectedContentSupported() {
return false;
}
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_WEBGPUSYNC_H
#define TNT_FILAMENT_BACKEND_WEBGPUSYNC_H
#include "DriverBase.h"
namespace filament::backend {
// TODO: provide a more complete implementation of Sync.
class WebGPUSync final : public HwSync {
};
}
#endif // TNT_FILAMENT_BACKEND_WEBGPUSYNC_H
+1 -2
View File
@@ -17,8 +17,7 @@
#ifndef TNT_BACKEND_TEST_WORKAROUNDS_H
#define TNT_BACKEND_TEST_WORKAROUNDS_H
#if defined(FILAMENT_SUPPORTS_WEBGPU)
// consider, if needed, adding (as Vulkan also has transient attachments): || defined(FILAMENT_SUPPORTS_VULKAN)
#if defined(FILAMENT_SUPPORTS_WEBGPU) || defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
// TODO: support TextureUsage::READ_PIXELS (or something like that)
// and search-and-replace TEXTURE_USAGE_READ_PIXELS with
// "| TextureUsage::READ_PIXELS" when it is available.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

+1 -1
View File
@@ -47,7 +47,7 @@ test::NativeView getNativeView() {
nativeView.ptr = (void*) view.layer;
}
if (self.backend == test::Backend::VULKAN) {
nativeView.ptr = (void*) view;
nativeView.ptr = (void*) view.layer;
}
if (self.backend == test::Backend::WEBGPU) {
nativeView.ptr = (void*) view.layer;
+25 -21
View File
@@ -22,6 +22,7 @@
#include "SharedShaders.h"
#include "Skip.h"
#include "TrianglePrimitive.h"
#include "Workarounds.h"
#include <utils/Hash.h>
#include <utils/Log.h>
@@ -172,17 +173,17 @@ TEST_F(BlitTest, ColorMagnify) {
// Create a destination texture.
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST TEXTURE_USAGE_READ_PIXELS));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 1, { srcTexture, level, 0 }, {},
{}));
dstRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {},
kDstTexWidth >> level, kDstTexHeight >> level, 1, 1, { dstTexture, level, 0 }, {},
{}));
}
@@ -236,17 +237,18 @@ TEST_F(BlitTest, ColorMinify) {
// Create a destination texture.
Handle<HwTexture> const dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::BLIT_DST TEXTURE_USAGE_READ_PIXELS));
// Create a RenderTarget for each texture's miplevel.
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
Handle<HwRenderTarget> dstRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 1, { srcTexture, level, 0 }, {},
{}));
dstRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0, { dstTexture, level, 0 }, {},
kDstTexWidth >> level, kDstTexHeight >> level, 1, 1, { dstTexture, level, 0 }, {},
{}));
}
@@ -265,8 +267,6 @@ TEST_F(BlitTest, ColorMinify) {
TEST_F(BlitTest, ColorResolve) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Nothing is drawn, see b/417229577");
auto& api = getDriverApi();
constexpr int kSrcTexWidth = 256;
@@ -291,18 +291,21 @@ TEST_F(BlitTest, ColorResolve) {
1, TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_SRC | TextureUsage::UPLOADABLE));
// Create 1-sample texture.
// Note that BLIT_SRC usage is necessary because this texture will be read back.
Handle<HwTexture> const dstColorTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, 1, kColorTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST |
TextureUsage::BLIT_SRC));
// Create a 4-sample render target with the 4-sample texture.
Handle<HwRenderTarget> const srcRenderTarget = mCleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, 0,
TargetBufferFlags::COLOR, kSrcTexWidth, kSrcTexHeight, kSampleCount, 1,
{{ srcColorTexture }}, {}, {}));
// Create a 1-sample render target with the 1-sample texture.
Handle<HwRenderTarget> const dstRenderTarget = mCleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 0,
TargetBufferFlags::COLOR, kDstTexWidth, kDstTexHeight, 1, 1,
{{ dstColorTexture }}, {}, {}));
// Prep for rendering.
@@ -379,17 +382,18 @@ TEST_F(BlitTest, Blit2DTextureArray) {
// Create a destination texture.
Handle<HwTexture> dstTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kDstTexFormat, 1, kDstTexWidth, kDstTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT | TextureUsage::BLIT_DST));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT |
TextureUsage::BLIT_DST TEXTURE_USAGE_READ_PIXELS));
// Create two RenderTargets.
const int level = 0;
Handle<HwRenderTarget> srcRenderTarget = mCleanup.add(
api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 1,
{ srcTexture, level, kSrcTexLayer }, {}, {}));
Handle<HwRenderTarget> dstRenderTarget = mCleanup.add(
api.createRenderTarget(TargetBufferFlags::COLOR,
kDstTexWidth >> level, kDstTexHeight >> level, 1, 0,
kDstTexWidth >> level, kDstTexHeight >> level, 1, 1,
{ dstTexture, level, kDstTexLayer }, {}, {}));
// Do a blit from kSrcTexLayer of the source RT to kDstTexLayer of the destination RT.
@@ -465,10 +469,10 @@ TEST_F(BlitTest, BlitRegion) {
// that this case is handled correctly.
Handle<HwRenderTarget> srcRenderTarget =
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, srcRect.width,
srcRect.height, 1, 0, { srcTexture, kSrcLevel, 0 }, {}, {}));
srcRect.height, 1, 1, { srcTexture, kSrcLevel, 0 }, {}, {}));
Handle<HwRenderTarget> dstRenderTarget =
mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR, kDstTexWidth >> kDstLevel,
kDstTexHeight >> kDstLevel, 1, 0, { dstTexture, kDstLevel, 0 }, {}, {}));
kDstTexHeight >> kDstLevel, 1, 1, { dstTexture, kDstLevel, 0 }, {}, {}));
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget, dstRect, srcRenderTarget,
srcRect,
@@ -491,7 +495,6 @@ TEST_F(BlitTest, BlitRegion) {
}
TEST_F(BlitTest, BlitRegionToSwapChain) {
FAIL_IF(Backend::VULKAN, "Crashes due to not finding color attachment, see b/417481493");
SKIP_IF(Backend::WEBGPU, "WebGPU Crashes due to not finding color attachment");
auto& api = getDriverApi();
mCleanup.addPostCall([&]() { executeCommands(); });
@@ -510,9 +513,10 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
Handle<HwRenderTarget> dstRenderTarget = mCleanup.add(api.createDefaultRenderTarget());
// Create a source texture.
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE | TextureUsage::COLOR_ATTACHMENT));
Handle<HwTexture> srcTexture = mCleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE |
TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
const bool flipY = sBackend == Backend::OPENGL;
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 0, float3(0.5, 0, 0), flipY);
createBitmap(api, srcTexture, kSrcTexWidth, kSrcTexHeight, 1, float3(0, 0, 0.5), flipY);
@@ -521,7 +525,7 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
Handle<HwRenderTarget> srcRenderTargets[kNumLevels];
for (uint8_t level = 0; level < kNumLevels; level++) {
srcRenderTargets[level] = mCleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR,
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 0, { srcTexture, level, 0 }, {},
kSrcTexWidth >> level, kSrcTexHeight >> level, 1, 1, { srcTexture, level, 0 }, {},
{}));
}
+3 -15
View File
@@ -149,8 +149,6 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
api.stopCapture(0);
}
executeCommands();
}
// This test renders two triangles in two separate draw calls. Between the draw calls, a uniform
@@ -182,7 +180,7 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, screenWidth(),
screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));
screenHeight(), 1, 1, { { colorTexture } }, {}, {}));
// Upload uniforms for the first triangle.
// Upload the uniform, but with an offset to accommodate the padding in the shader's
@@ -233,22 +231,12 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.commit(swapChain);
api.endFrame(0);
}
EXPECT_IMAGE(renderTarget, ScreenshotParams(screenWidth(), screenHeight(),
"BufferObjectUpdateWithOffset", 2320747245));
api.flush();
api.commit(swapChain);
api.endFrame(0);
// This ensures all driver commands have finished before exiting the test.
api.finish();
executeCommands();
getDriver().purge();
}
} // namespace test
+1 -1
View File
@@ -97,7 +97,7 @@ TEST_F(BackendTest, MRT) {
screenWidth(), // width
screenHeight(), // height
1, // samples
0, // layerCount
1, // layerCount
{{textureA },{textureB }}, // color
{}, // depth
{})); // stencil
+41 -46
View File
@@ -22,6 +22,7 @@
#include "SharedShaders.h"
#include "Skip.h"
#include "TrianglePrimitive.h"
#include "Workarounds.h"
#include <utils/Hash.h>
@@ -32,8 +33,6 @@ using namespace filament::backend;
TEST_F(BackendTest, ScissorViewportRegion) {
SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731");
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"Affected area in wrong corner, see b/417229118");
auto& api = getDriverApi();
constexpr int kSrcTexWidth = 1024;
@@ -41,31 +40,31 @@ TEST_F(BackendTest, ScissorViewportRegion) {
constexpr auto kSrcTexFormat = TextureFormat::RGBA8;
constexpr int kNumLevels = 3;
constexpr int kSrcLevel = 1;
constexpr int kSrcRtWidth = 384;
constexpr int kSrcRtHeight = 384;
constexpr int kSrcRtWidth = kSrcTexWidth >> kSrcLevel;
constexpr int kSrcRtHeight = kSrcTexHeight >> kSrcLevel;
api.startCapture(0);
Cleanup cleanup(api);
// color texture (mip level 1) 512x512 depth texture (mip level 0) 512x512
// color texture/RT (mip level 1) 512x512 depth texture (mip level 0) 512x512
// +----------------------------------------+ +------------------------------------------+
// | | | |
// | | | |
// | RenderTarget (384x384) | | RenderTarget (384x384) |
// +------------------------------+ | +------------------------------+ |
// | | | | | |
// | +-------------------+ | | | | |
// | | viewport | | | | | |
// | | | | | | | |
// | +---+---------------+ | | | | | |
// | | | | | | | | | |
// | | | | | | | | | |
// | | | (64,64) | | | | | | |
// | | +---------------+---+ | | | | |
// | | scissor | | | | | |
// | +-------------------+ | | | | |
// | (32, 32) | | | | |
// +------------------------------+---------+ +------------------------------+-----------+
// | | | |
// | | | |
// | | | |
// | +-------------------+ | | |
// | | viewport | | | |
// | | | | | |
// | +---+---------------+ | | | |
// | | | | | | | |
// | | | | | | | |
// | | | (64,64) | | | | |
// | | +---------------+---+ | | |
// | | scissor | | | |
// | +-------------------+ | | |
// | (32, 32) | | |
// +----------------------------------------+ +------------------------------_-----------+
// The test is executed within this block scope to force destructors to run before
// executeCommands().
@@ -74,43 +73,39 @@ TEST_F(BackendTest, ScissorViewportRegion) {
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
api.makeCurrent(swapChain, swapChain);
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::None,
});
Shader shader = SharedShaders::makeShader(api, cleanup,
ShaderRequest{
.mVertexType = VertexShaderType::Noop,
.mFragmentType = FragmentShaderType::White,
.mUniformType = ShaderUniformType::None,
});
// Create source color and depth textures.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D,
kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
Handle<HwTexture> depthTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH16, 1, 512, 512, 1,
TextureUsage::DEPTH_ATTACHMENT));
TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT));
// Render into the bottom-left quarter of the texture.
Viewport srcRect = {
.left = 64,
.bottom = 64,
.width = kSrcRtWidth - 64 * 2,
.height = kSrcRtHeight - 64 * 2
.left = 64,
.bottom = 64,
.width = kSrcRtWidth - 64 * 2,
.height = kSrcRtHeight - 64 * 2,
};
Viewport scissor = {
.left = 32,
.bottom = 32,
.width = kSrcRtWidth - 64 * 2,
.height = kSrcRtHeight - 64 * 2
.left = 32,
.bottom = 32,
.width = kSrcRtWidth - 64 * 2,
.height = kSrcRtHeight - 64 * 2,
};
// We purposely set the render target width and height to smaller than the texture, to check
// that this case is handled correctly.
Handle<HwRenderTarget> srcRenderTarget = cleanup.add(api.createRenderTarget(
Handle<HwRenderTarget> rt = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1,
0, { srcTexture, kSrcLevel, 0 }, { depthTexture, 0, 0 }, {}));
Handle<HwRenderTarget> fullRenderTarget = cleanup.add(
api.createRenderTarget(TargetBufferFlags::COLOR, kSrcTexHeight >> kSrcLevel,
kSrcTexWidth >> kSrcLevel, 1, 0, {srcTexture, kSrcLevel, 0}, {}, {}));
1, { srcTexture, kSrcLevel, 0 }, { depthTexture, 0, 0 }, {}));
TrianglePrimitive triangle(api);
@@ -124,7 +119,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
api.beginRenderPass(srcRenderTarget, params);
api.beginRenderPass(rt, params);
api.scissor(scissor);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
@@ -133,7 +128,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(fullRenderTarget,
EXPECT_IMAGE(rt,
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 15842520));
api.commit(swapChain);
@@ -166,7 +161,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
// Create a source color textures.
Handle<HwTexture> srcTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1,
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT));
TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS));
// Render into the bottom-left quarter of the texture, checking 3 special cases.
// 1. negative viewport left/bottom
@@ -189,7 +184,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
(uint32_t)std::numeric_limits<int32_t>::max()};
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR, 512, 512, 1, 0,
TargetBufferFlags::COLOR, 512, 512, 1, 1,
{srcTexture, 0, 0}, {}, {}));
TrianglePrimitive triangle(api);
+13
View File
@@ -63,6 +63,7 @@ class Scene;
class Skybox;
class Stream;
class SwapChain;
class Sync;
class Texture;
class VertexBuffer;
class View;
@@ -886,9 +887,19 @@ public:
*/
Fence* UTILS_NONNULL createFence() noexcept;
/**
* Creates a Sync.
* @param callback A callback that will be invoked when the handle for
* the created sync is set
*
* @return A pointer to the newly created Sync.
*/
Sync* UTILS_NONNULL createSync() noexcept;
bool destroy(const BufferObject* UTILS_NULLABLE p); //!< Destroys a BufferObject object.
bool destroy(const VertexBuffer* UTILS_NULLABLE p); //!< Destroys an VertexBuffer object.
bool destroy(const Fence* UTILS_NULLABLE p); //!< Destroys a Fence object.
bool destroy(const Sync* UTILS_NULLABLE p); //!< Destroys a Sync object.
bool destroy(const IndexBuffer* UTILS_NULLABLE p); //!< Destroys an IndexBuffer object.
bool destroy(const SkinningBuffer* UTILS_NULLABLE p); //!< Destroys a SkinningBuffer object.
bool destroy(const MorphTargetBuffer* UTILS_NULLABLE p); //!< Destroys a MorphTargetBuffer object.
@@ -922,6 +933,8 @@ public:
bool isValid(const VertexBuffer* UTILS_NULLABLE p) const;
/** Tells whether a Fence object is valid */
bool isValid(const Fence* UTILS_NULLABLE p) const;
/** Tells whether a Sync object is valid */
bool isValid(const Sync* UTILS_NULLABLE p) const;
/** Tells whether an IndexBuffer object is valid */
bool isValid(const IndexBuffer* UTILS_NULLABLE p) const;
/** Tells whether a SkinningBuffer object is valid */
+22
View File
@@ -233,6 +233,18 @@ public:
*/
static constexpr uint64_t CONFIG_PROTECTED_CONTENT = backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT;
/**
* Indicates that the SwapChain is configured to use Multi-Sample Anti-Aliasing (MSAA) with the
* given sample points within each pixel. Only supported when isMSAASwapChainSupported(4) is
* true.
*
* This is only supported by EGL(Android). Other GL platforms (GLX, WGL, etc) don't support it
* because the swapchain MSAA settings must be configured before window creation.
*
* @see isMSAASwapChainSupported(4)
*/
static constexpr uint64_t CONFIG_MSAA_4_SAMPLES = backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES;
/**
* Return whether createSwapChain supports the CONFIG_PROTECTED_CONTENT flag.
* The default implementation returns false.
@@ -251,6 +263,16 @@ public:
*/
static bool isSRGBSwapChainSupported(Engine& engine) noexcept;
/**
* Return whether createSwapChain supports the CONFIG_MSAA_*_SAMPLES flag.
* The default implementation returns false.
*
* @param engine A pointer to the filament Engine
* @param samples The number of samples
* @return true if CONFIG_MSAA_*_SAMPLES is supported, false otherwise.
*/
static bool isMSAASwapChainSupported(Engine& engine, uint32_t samples) noexcept;
void* UTILS_NULLABLE getNativeWindow() const noexcept;
/**
+53
View File
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_SYNC_H
#define TNT_FILAMENT_SYNC_H
#include <filament/FilamentAPI.h>
#include <backend/CallbackHandler.h>
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
namespace filament {
class UTILS_PUBLIC Sync : public FilamentAPI {
public:
using CallbackHandler = backend::CallbackHandler;
using Callback = backend::Platform::SyncCallback;
/**
* Fetches a handle to the external, platform-specific representation of
* this sync object.
*
* @param handler A handler for the callback that will receive the handle
* @param callback A callback that will receive the handle when ready
* @param userData Data to be passed to the callback so that the application
* can identify what frame the sync is relevant to.
* @return The external handle for the Sync. This is valid destroy() is
* called on this Sync object.
*/
void getExternalHandle(CallbackHandler* handler, Callback callback, void* userData) noexcept;
protected:
// prevent heap allocation
~Sync() = default;
};
} // namespace filament
#endif // TNT_FILAMENT_SYNC_H
+12
View File
@@ -30,6 +30,7 @@
#include "details/Skybox.h"
#include "details/Stream.h"
#include "details/SwapChain.h"
#include "details/Sync.h"
#include "details/Texture.h"
#include "details/VertexBuffer.h"
#include "details/View.h"
@@ -132,6 +133,10 @@ SwapChain* Engine::createSwapChain(uint32_t const width, uint32_t const height,
return downcast(this)->createSwapChain(width, height, flags);
}
Sync* Engine::createSync() noexcept {
return downcast(this)->createSync();
}
bool Engine::destroy(const BufferObject* p) {
return downcast(this)->destroy(downcast(p));
}
@@ -204,6 +209,10 @@ bool Engine::destroy(const SwapChain* p) {
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const Sync* p) {
return downcast(this)->destroy(downcast(p));
}
bool Engine::destroy(const InstanceBuffer* p) {
return downcast(this)->destroy(downcast(p));
}
@@ -221,6 +230,9 @@ bool Engine::isValid(const VertexBuffer* p) const {
bool Engine::isValid(const Fence* p) const {
return downcast(this)->isValid(downcast(p));
}
bool Engine::isValid(const Sync* p) const {
return downcast(this)->isValid(downcast(p));
}
bool Engine::isValid(const IndexBuffer* p) const {
return downcast(this)->isValid(downcast(p));
}
+2 -3
View File
@@ -670,7 +670,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
cmd.key |= makeField(soaVisibility[i].priority, PRIORITY_MASK, PRIORITY_SHIFT);
cmd.key |= makeField(soaVisibility[i].channel, CHANNEL_MASK, CHANNEL_SHIFT);
cmd.info.index = i;
cmd.info.index = soaInstanceInfo[i].buffer ? soaInstanceInfo[i].buffer->getIndex() : i;
cmd.info.hasHybridInstancing = bool(soaInstanceInfo[i].buffer);
cmd.info.instanceCount = soaInstanceInfo[i].count;
cmd.info.hasMorphing = bool(morphing.handle);
@@ -1110,8 +1110,7 @@ void RenderPass::Executor::execute(FEngine const& engine, DriverApi& driver,
// Bind per-renderable uniform block. There is no need to attempt to skip this command
// because the backends already do this.
uint32_t const offset = info.hasHybridInstancing ?
0 : info.index * sizeof(PerRenderableData);
uint32_t const offset = info.index * sizeof(PerRenderableData);
assert_invariant(info.dsh);
driver.bindDescriptorSet(info.dsh,
+1 -1
View File
@@ -264,7 +264,7 @@ public:
bool hasMorphing : 1; // 1 bit
bool hasHybridInstancing : 1; // 1 bit
uint32_t rfu[2]; // 16 bytes
uint32_t rfu[2]; // 8 bytes
};
static_assert(sizeof(PrimitiveInfo) == 56);
+4
View File
@@ -46,6 +46,10 @@ bool SwapChain::isSRGBSwapChainSupported(Engine& engine) noexcept {
return FSwapChain::isSRGBSwapChainSupported(downcast(engine));
}
bool SwapChain::isMSAASwapChainSupported(Engine& engine, uint32_t samples) noexcept {
return FSwapChain::isMSAASwapChainSupported(downcast(engine), samples);
}
bool SwapChain::isProtectedContentSupported(Engine& engine) noexcept {
return FSwapChain::isProtectedContentSupported(downcast(engine));
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "details/Sync.h"
#include <backend/Platform.h>
namespace filament {
void Sync::getExternalHandle(Sync::CallbackHandler* handler, Sync::Callback callback,
void* userData) noexcept {
downcast(this)->getExternalHandle(handler, callback, userData);
}
} // namespace filament
+19
View File
@@ -34,6 +34,7 @@
#include "details/Skybox.h"
#include "details/Stream.h"
#include "details/SwapChain.h"
#include "details/Sync.h"
#include "details/Texture.h"
#include "details/VertexBuffer.h"
#include "details/View.h"
@@ -1011,6 +1012,15 @@ FSwapChain* FEngine::createSwapChain(uint32_t width, uint32_t height, uint64_t f
return p;
}
FSync* FEngine::createSync() noexcept {
FSync* p = mHeapAllocator.make<FSync>(*this);
if (UTILS_LIKELY(p)) {
std::lock_guard const guard(mSyncListLock);
mSyncs.insert(p);
}
return p;
}
/*
* Objects created with a component manager
*/
@@ -1200,6 +1210,11 @@ bool FEngine::destroy(const FSwapChain* p) {
return terminateAndDestroy(p, mSwapChains);
}
UTILS_NOINLINE
bool FEngine::destroy(const FSync* p) {
return terminateAndDestroyLocked(mSyncListLock, p, mSyncs);
}
UTILS_NOINLINE
bool FEngine::destroy(const FStream* p) {
return terminateAndDestroy(p, mStreams);
@@ -1281,6 +1296,10 @@ bool FEngine::isValid(const FFence* p) const {
return isValid(p, mFences);
}
bool FEngine::isValid(const FSync* p) const {
return isValid(p, mSyncs);
}
bool FEngine::isValid(const FIndexBuffer* p) const {
return isValid(p, mIndexBuffers);
}
+11 -1
View File
@@ -40,10 +40,11 @@
#include "details/Fence.h"
#include "details/IndexBuffer.h"
#include "details/InstanceBuffer.h"
#include "details/MorphTargetBuffer.h"
#include "details/RenderTarget.h"
#include "details/SkinningBuffer.h"
#include "details/MorphTargetBuffer.h"
#include "details/Skybox.h"
#include "details/Sync.h"
#include "private/backend/CommandBufferQueue.h"
#include "private/backend/CommandStream.h"
@@ -122,6 +123,7 @@ class FMaterialInstance;
class FRenderer;
class FScene;
class FSwapChain;
class FSync;
class FView;
class ResourceAllocator;
@@ -331,6 +333,7 @@ public:
FScene* createScene() noexcept;
FView* createView() noexcept;
FFence* createFence() noexcept;
FSync* createSync() noexcept;
FSwapChain* createSwapChain(void* nativeWindow, uint64_t flags) noexcept;
FSwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept;
@@ -342,6 +345,7 @@ public:
bool destroy(const FBufferObject* p);
bool destroy(const FVertexBuffer* p);
bool destroy(const FFence* p);
bool destroy(const FSync* p);
bool destroy(const FIndexBuffer* p);
bool destroy(const FSkinningBuffer* p);
bool destroy(const FMorphTargetBuffer* p);
@@ -362,6 +366,7 @@ public:
bool isValid(const FBufferObject* p) const;
bool isValid(const FVertexBuffer* p) const;
bool isValid(const FFence* p) const;
bool isValid(const FSync* p) const;
bool isValid(const FIndexBuffer* p) const;
bool isValid(const FSkinningBuffer* p) const;
bool isValid(const FMorphTargetBuffer* p) const;
@@ -613,6 +618,11 @@ private:
utils::Mutex mFenceListLock;
ResourceList<FFence> mFences{"Fence"};
// the sync list is accessed from multiple threads, because they are
// synchronization objects.
utils::Mutex mSyncListLock;
ResourceList<FSync> mSyncs{ "Sync" };
mutable uint32_t mMaterialId = 0;
// FMaterialInstance are handled directly by FMaterial
+6 -9
View File
@@ -103,8 +103,7 @@ FInstanceBuffer::FInstanceBuffer(FEngine&, const Builder& builder)
}
void FInstanceBuffer::terminate(FEngine&) {
mHandle.clear();
mOffset = 0;
mIndex = 0;
}
FInstanceBuffer::~FInstanceBuffer() noexcept = default;
@@ -126,8 +125,7 @@ math::mat4f const& FInstanceBuffer::getLocalTransform(size_t index) const noexce
}
void FInstanceBuffer::prepare(
BufferObjectHandle ubh,
PerRenderableData* const UTILS_RESTRICT buffer, uint32_t const offset, uint32_t const count,
PerRenderableData* const UTILS_RESTRICT buffer, uint32_t const index, uint32_t const count,
math::mat4f const& rootTransform, PerRenderableData const& ubo) {
// there is a precondition check for this, so this assert really should never trigger
@@ -136,12 +134,11 @@ void FInstanceBuffer::prepare(
for (size_t i = 0, c = count; i < c; i++) {
math::mat4f const model = rootTransform * mLocalTransforms[i];
math::mat3f const m = math::mat3f::getTransformForNormals(model.upperLeft());
buffer[offset + i] = ubo;
buffer[offset + i].worldFromModelMatrix = model;
buffer[offset + i].worldFromModelNormalMatrix = math::prescaleForNormals(m);
buffer[index + i] = ubo;
buffer[index + i].worldFromModelMatrix = model;
buffer[index + i].worldFromModelNormalMatrix = math::prescaleForNormals(m);
}
mHandle = ubh;
mOffset = offset * sizeof(PerRenderableData);
mIndex = index;
}
} // namespace filament
+4 -7
View File
@@ -52,23 +52,20 @@ public:
math::mat4f const& getLocalTransform(size_t index) const noexcept;
void prepare(
backend::BufferObjectHandle ubh,
PerRenderableData* buffer, uint32_t offset, uint32_t count,
PerRenderableData* buffer, uint32_t index, uint32_t count,
math::mat4f const& rootTransform, PerRenderableData const& ubo);
utils::CString const& getName() const noexcept { return mName; }
backend::BufferObjectHandle getHandle() const noexcept { return mHandle; }
uint32_t getOffset() const noexcept { return mOffset; }
uint32_t getIndex() const noexcept { return mIndex; }
private:
friend class RenderableManager;
utils::FixedCapacityVector<math::mat4f> mLocalTransforms;
utils::CString mName;
size_t mInstanceCount;
backend::BufferObjectHandle mHandle;
uint32_t mOffset = 0;
uint32_t mInstanceCount;
uint32_t mIndex = 0;
};
FILAMENT_DOWNCAST(InstanceBuffer)
+35 -2
View File
@@ -22,7 +22,9 @@
#include <backend/CallbackHandler.h>
#include <utils/CString.h>
#include <utils/Invocable.h>
#include <utils/Logger.h>
#include <new>
#include <utility>
@@ -31,14 +33,33 @@
namespace filament {
namespace {
utils::CString getRemovedFlags(uint64_t originalFlags, uint64_t modifiedFlags) {
const uint64_t diffFlags = originalFlags ^ modifiedFlags;
utils::CString removed;
if (diffFlags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) {
removed += "SRGB_COLORSPACE ";
}
if (diffFlags & backend::SWAP_CHAIN_CONFIG_MSAA_4_SAMPLES) {
removed += "MSAA_4_SAMPLES ";
}
if (diffFlags & backend::SWAP_CHAIN_CONFIG_PROTECTED_CONTENT) {
removed += "PROTECTED_CONTENT ";
}
return removed;
}
} // anonymous namespace
FSwapChain::FSwapChain(FEngine& engine, void* nativeWindow, uint64_t const flags)
: mEngine(engine), mNativeWindow(nativeWindow), mConfigFlags(initFlags(engine, flags)) {
mHwSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, flags);
mHwSwapChain = engine.getDriverApi().createSwapChain(nativeWindow, mConfigFlags);
}
FSwapChain::FSwapChain(FEngine& engine, uint32_t const width, uint32_t const height, uint64_t const flags)
: mEngine(engine), mWidth(width), mHeight(height), mConfigFlags(initFlags(engine, flags)) {
mHwSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, flags);
mHwSwapChain = engine.getDriverApi().createSwapChainHeadless(width, height, mConfigFlags);
}
void FSwapChain::recreateWithNewFlags(FEngine& engine, uint64_t flags) noexcept {
@@ -56,12 +77,20 @@ void FSwapChain::recreateWithNewFlags(FEngine& engine, uint64_t flags) noexcept
}
uint64_t FSwapChain::initFlags(FEngine& engine, uint64_t flags) noexcept {
const uint64_t originalFlags = flags;
if (!isSRGBSwapChainSupported(engine)) {
flags &= ~CONFIG_SRGB_COLORSPACE;
}
if (!isMSAASwapChainSupported(engine, 4)) {
flags &= ~CONFIG_MSAA_4_SAMPLES;
}
if (!isProtectedContentSupported(engine)) {
flags &= ~CONFIG_PROTECTED_CONTENT;
}
if (originalFlags != flags) {
LOG(WARNING) << "SwapChain flags were modified to remove features that are not supported. "
<< "Removed: " << getRemovedFlags(originalFlags, flags).c_str_safe();
}
return flags;
}
@@ -91,6 +120,10 @@ bool FSwapChain::isSRGBSwapChainSupported(FEngine& engine) noexcept {
return engine.getDriverApi().isSRGBSwapChainSupported();
}
bool FSwapChain::isMSAASwapChainSupported(FEngine& engine, uint32_t samples) noexcept {
return engine.getDriverApi().isMSAASwapChainSupported(samples);
}
bool FSwapChain::isProtectedContentSupported(FEngine& engine) noexcept {
return engine.getDriverApi().isProtectedContentSupported();
}
+2
View File
@@ -88,6 +88,8 @@ public:
static bool isSRGBSwapChainSupported(FEngine& engine) noexcept;
static bool isMSAASwapChainSupported(FEngine& engine, uint32_t samples) noexcept;
static bool isProtectedContentSupported(FEngine& engine) noexcept;
// This is currently only used for debugging. This allows to recreate the HwSwapChain with
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "details/Sync.h"
#include "details/Engine.h"
#include <backend/Platform.h>
#include <filament/Sync.h>
namespace filament {
using DriverApi = backend::DriverApi;
FSync::FSync(FEngine& engine)
: mEngine(engine) {
DriverApi& driverApi = engine.getDriverApi();
mHwSync = driverApi.createSync();
}
void FSync::terminate(FEngine& engine) noexcept {
engine.getDriverApi().destroySync(mHwSync);
}
void FSync::getExternalHandle(Sync::CallbackHandler* handler, Sync::Callback callback,
void* userData) noexcept {
mEngine.getDriverApi().getPlatformSync(mHwSync, handler, callback, userData);
}
} // namespace filament
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_DETAILS_SYNC_H
#define TNT_FILAMENT_DETAILS_SYNC_H
#include "downcast.h"
#include <filament/Sync.h>
#include <backend/CallbackHandler.h>
#include <backend/Handle.h>
#include <backend/Platform.h>
namespace filament {
class FEngine;
class FSync : public Sync {
public:
FSync(FEngine& engine);
void terminate(FEngine& engine) noexcept;
backend::SyncHandle getHwHandle() const noexcept { return mHwSync; }
/**
* Fetches a handle to the external, platform-specific representation of
* this sync object.
*
* @param handler A handler for the callback that will receive the handle
* @param callback A callback that will receive the handle when ready
* @param userData Data to be passed to the callback so that the application
* can identify what frame the sync is relevant to.
* @return The external handle for the Sync. This is valid destroy() is
* called on this Sync object.
*/
void getExternalHandle(Sync::CallbackHandler* handler, Sync::Callback callback,
void* userData) noexcept;
private:
FEngine& mEngine;
backend::SyncHandle mHwSync;
};
FILAMENT_DOWNCAST(Sync)
} // namespace filament
#endif // TNT_FILAMENT_DETAILS_SYNC_H
+4 -8
View File
@@ -778,11 +778,10 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
for (uint32_t const i : merged) {
auto const& skinning = sceneData.elementAt<FScene::SKINNING_BUFFER>(i);
auto const& morphing = sceneData.elementAt<FScene::MORPHING_BUFFER>(i);
auto const& instance = sceneData.elementAt<FScene::INSTANCES>(i);
// FIXME: when only one is active the UBO handle of the other is null
// (probably a problem on vulkan)
if (UTILS_UNLIKELY(skinning.handle || morphing.handle || instance.buffer)) {
if (UTILS_UNLIKELY(skinning.handle || morphing.handle)) {
auto const ci = sceneData.elementAt<FScene::RENDERABLE_INSTANCE>(i);
FRenderableManager& rcm = engine.getRenderableManager();
auto& descriptorSet = rcm.getDescriptorSet(ci);
@@ -795,10 +794,8 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
}
descriptorSet.setBuffer(layout,
+PerRenderableBindingPoints::OBJECT_UNIFORMS,
instance.buffer ? instance.buffer->getHandle() : mRenderableUbh,
instance.buffer ? instance.buffer->getOffset() : 0,
sizeof(PerRenderableUib));
+PerRenderableBindingPoints::OBJECT_UNIFORMS, mRenderableUbh,
0, sizeof(PerRenderableUib));
descriptorSet.setBuffer(layout,
+PerRenderableBindingPoints::BONES_UNIFORMS,
@@ -929,7 +926,7 @@ void FView::updateUBOs(
for (uint32_t const i : visibleRenderables) {
auto& instancesInfo = instancesData[i];
if (instancesInfo.buffer) {
assert_invariant(instancesInfo.count == instancesInfo.buffer->getInstanceCount());
assert_invariant(instancesInfo.count <= instancesInfo.buffer->getInstanceCount());
icount += instancesInfo.count;
}
}
@@ -977,7 +974,6 @@ void FView::updateUBOs(
auto& instancesInfo = instancesData[i];
if (instancesInfo.buffer) {
instancesInfo.buffer->prepare(
mRenderableUbh,
buffer, j, instancesInfo.count,
worldTransformData[i], uboData[i]);
j += instancesInfo.count;
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.65.1"
spec.version = "1.65.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.65.1/filament-v1.65.1-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.65.0/filament-v1.65.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
+1 -1
View File
@@ -53,7 +53,7 @@
#define VK_USE_PLATFORM_WAYLAND_KHR 1
#endif
#elif defined(__APPLE__)
#define VK_USE_PLATFORM_MACOS_MVK 1
#define VK_USE_PLATFORM_METAL_EXT 1
#elif defined(WIN32)
#define VK_USE_PLATFORM_WIN32_KHR 1
#endif
+6 -2
View File
@@ -239,7 +239,9 @@ public:
void push(void* p) noexcept {
assert(p);
assert(p >= mBegin && p < mEnd);
Node* const head = static_cast<Node*>(p);
// we use placement-new to properly manage the lifetime
// this is noop already under O1
Node* const head = new (p) Node;
head->next = mHead;
mHead = head;
}
@@ -308,7 +310,9 @@ public:
void push(void* p) noexcept {
Node* const storage = mStorage;
assert(p && p >= storage);
Node* const node = static_cast<Node*>(p);
// we use placement-new to properly manage the lifetime
// this is noop already under O1
Node* const node = new (p) Node;
HeadPtr currentHead = mHead.load(std::memory_order_relaxed);
HeadPtr newHead = { int32_t(node - storage), currentHead.tag + 1 };
do {
+336 -61
View File
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { LitElement, html, css } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
import { LitElement, html, css, repeat } from "https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js";
import './tools.js';
import './tiff-viewer.js';
@@ -32,6 +32,12 @@ const DIFF_VIEW = 'diff'
const GOLDEN_VIEW = 'golden'
const RENDERED_VIEW = 'rendered'
const RES_EQUAL = "mismatched dimensions";
const RES_MISMATCHED_DIMENSIONS = "mismatched dimensions";
const RES_DIFFERENT_PIXELS = "different pixels";
const RES_NOT_READY = "not ready";
const FAILED_POSTIFX = ".failed";
class ExpandedPassedResult extends LitElement {
static get properties() {
return {
@@ -58,67 +64,264 @@ class ExpandedFailedResult extends LitElement {
static get properties() {
return {
content: { type: Object, attribute: 'content' },
leftViewSelected: { type: String },
rightViewSelected: { type: String },
tests: { type: Array },
};
}
static styles = css`
.viewer-container {
display: flex;
flex-direction: row;
}
.viewer {
margin: 0 5px;
}
`;
constructor() {
super();
this.content = null;
this.leftViewSelected = GOLDEN_VIEW;
this.rightViewSelected = DIFF_VIEW;
this.addEventListener('radio-change', (ev) => {
if (ev.detail.radioId == 'right') {
this.rightViewSelected = ev.detail.value;
}
if (ev.detail.radioId == 'left') {
this.leftViewSelected = ev.detail.value;
}
});
}
_viewer(name, choices, current) {
const url = {
[GOLDEN_VIEW]: getGoldenUrl,
[RENDERED_VIEW]: getCompUrl,
[DIFF_VIEW]: getDiffUrl,
}[current](this.content);
return html`
<div>
<radio-button-group id="${name}" .choices=${choices} value=${current}></radio-button-group>
<tiff-viewer class="viewer" fileurl="${url}"></tiff-viewer>
</div>
`;
}
render() {
if (!this.content) {
return html``;
}
const v1 = this._viewer("left", [GOLDEN_VIEW, RENDERED_VIEW], this.leftViewSelected);
const v2 = this._viewer("right", [RENDERED_VIEW, DIFF_VIEW], this.rightViewSelected);
return html`
<div class="viewer-container">
${v1}
${v2}
</div>
<expanded-comparison-result
.left=${this.content}
.right=${this.content}
leftViewType="golden"
rightViewType="rendered"
.tests=${this.tests}
?disableDropdowns=${true}>
</expanded-comparison-result>
`;
return html``;
}
}
customElements.define('expanded-failed-result', ExpandedFailedResult);
class ExpandedComparisonResult extends LitElement {
static get properties() {
return {
left: { type: Object },
right: { type: Object },
tests: { type: Array },
diffResult: { type: Object },
showDiff: {type: Boolean },
leftViewType: { type: String },
rightViewType: { type: String },
disableDropdowns: { type: Boolean },
};
}
static styles = css`
.main-container {
display: flex;
flex-direction: column;
align-items: center;
}
.viewer-container {
display: flex;
flex-direction: row;
}
#diffCanvas {
width: 100%;
height: 100%;
margin-top: 35px;
margin-bottom: 5px;
}
.selector {
margin: 8px 0;
}
`;
constructor() {
super();
this.left = null;
this.right = null;
this.tests = [];
this.showDiff = false;
this.leftImageLoaded = false;
this.rightImageLoaded = false;
this.leftViewType = 'golden';
this.rightViewType = 'golden';
this.disableDropdowns = false;
this.addEventListener(
'image-loaded',
(ev) => {
if (ev.detail.name == this.left.name) {
this.leftImageLoaded = true;
}
if (ev.detail.name == this.right.name) {
this.rightImageLoaded = true;
}
if (this.leftImageLoaded && this.rightImageLoaded) {
this._triggerDiff();
}
}
);
}
_viewer(name, choices, current, viewType) {
const url = viewType == 'rendered' ? getCompUrl(current) : getGoldenUrl(current);
const onSelect = (ev) => {
const testName = ev.target.value;
const test = this.tests.find((t) => t.name == testName);
if (name == 'left') {
this.left = test;
this.leftImageLoaded = false;
} else {
this.right = test;
this.rightImageLoaded = false;
}
this.showDiff = false;
};
const dropdown = () => {
if (this.disableDropdowns) {
return html`<div class="selector">${current.name} (${viewType})</div>`;
}
return html`
<select class="selector" @change=${onSelect}>
${choices.map((c) => html`<option value=${c.name} ?selected=${c.name == current.name}>${c.name}</option>`)}
</select>
`;
}
return html`
<div style="flex: 1; margin: 0 5px;">
${dropdown()}
<tiff-viewer id="viewer-${name}" class="viewer"
name="${current.name}"
fileurl="${url}"></tiff-viewer>
</div>
`;
}
_computeDiff() {
const tiffViewerLeft = this.shadowRoot.querySelector('#viewer-left');
const tiffViewerRight = this.shadowRoot.querySelector('#viewer-right');
if (!tiffViewerLeft || !tiffViewerRight) {
return {
"result": RES_NOT_READY,
};
}
const canvasLeft = tiffViewerLeft.shadowRoot.querySelector('canvas');
const canvasRight = tiffViewerRight.shadowRoot.querySelector('canvas');
if (!canvasLeft || !canvasRight) {
return {
"result": RES_NOT_READY,
};
}
const ctxLeft = canvasLeft.getContext('2d');
const ctxRight = canvasRight.getContext('2d');
const imgLeft = ctxLeft.getImageData(0, 0, canvasLeft.width, canvasLeft.height);
const imgRight = ctxRight.getImageData(0, 0, canvasRight.width, canvasRight.height);
if (imgLeft.width !== imgRight.width || imgLeft.height !== imgRight.height) {
console.error("Images have different dimensions");
return {
"result": RES_MISMATCHED_DIMENSIONS,
"explanation": "Images have different dimensions " +
"left=(" + imgLeft.width + ", " + imgLeft.height + ") " +
"right=(" + imgRight.width + ", " + imgRight.height + ")",
};
}
const imgDiff = new Uint8ClampedArray(imgLeft.width * imgLeft.height * 4);
const maxDiff = [0, 0, 0, 0];
for (let i = 0; i < imgLeft.data.length; i += 4) {
for (let j = 0; j < 4; j++) {
maxDiff[j] = Math.max(
maxDiff[j],
Math.min(255, Math.abs(imgLeft.data[i + j] - imgRight.data[i + j]))
);
imgDiff[i + j] = Math.abs(imgLeft.data[i + j] - imgRight.data[i + j]);
}
imgDiff[i + 3] = 255;
}
if (maxDiff[0] == 0 &&
maxDiff[1] == 0 &&
maxDiff[2] == 0 &&
maxDiff[3] == 0) {
return {
"result": RES_EQUAL,
"explanation": "Equal",
"dim": {"width": imgLeft.width, "height": imgLeft.height },
}
}
return {
"result": RES_DIFFERENT_PIXELS,
"explanation": "Images are different",
"dim": {"width": imgLeft.width, "height": imgLeft.height },
"maxDiff": maxDiff,
"diffImg": imgDiff,
};
}
_triggerDiff() {
const diff = this._computeDiff();
if (diff.result == RES_DIFFERENT_PIXELS) {
this.diffResult = diff;
this.showDiff = true;
}
}
updated(props) {
if (this.showDiff && this.diffResult) {
const mult = this.shadowRoot.querySelector('#diffMultiplier').value;
this._updateDiffCanvas(this.diffResult, mult);
}
}
_updateDiffCanvas(diffResult, mult) {
const diffCanvas = this.shadowRoot.querySelector('#diffCanvas');
const diffCtx = diffCanvas.getContext('2d');
diffCanvas.width = diffResult.dim.width;
diffCanvas.height = diffResult.dim.height;
// Create a fresh copy of the original diff data to avoid mutation.
const diffImgCopy = diffResult.diffImg.slice();
// Modify the copy, not the original.
for (let i = 0; i < diffImgCopy.length; i += 4) {
for (let j = 0; j < 3; j++) {
diffImgCopy[i + j] = Math.min(255, mult * diffImgCopy[i + j]);
}
diffImgCopy[i + 3] = 255; // Ensure alpha is always 255
}
// Create the ImageData from the modified copy.
const imgData = new ImageData(diffImgCopy, diffResult.dim.width, diffResult.dim.height);
diffCtx.putImageData(imgData, 0, 0);
}
render() {
if (!this.left || !this.right) {
return html``;
}
const diffStyle = !this.showDiff ? "display:none;" : "display:flex; flex-direction: column;";
const v1 = this._viewer("left", this.tests, this.left, this.leftViewType);
const v2 = this._viewer("right", this.tests, this.right, this.rightViewType);
const onMultiplierChange = (ev) => {
this._updateDiffCanvas(this.diffResult, ev.target.value);
};
return html`
<div class="main-container">
<div class="viewer-container">
${v1}
<div style="flex: 1; ${diffStyle}">
<canvas id="diffCanvas"></canvas>
</div>
${v2}
</div>
<div class="control" style="${diffStyle}">
<div>
<div>Difference Multiplier</div>
<input type="range" min="1" max="100" value="1" id="diffMultiplier" @input=${onMultiplierChange}>
</div>
</div>
</div>
`;
}
}
customElements.define('expanded-comparison-result', ExpandedComparisonResult);
class App extends LitElement {
static styles = css`
.test-label {
@@ -146,8 +349,9 @@ class App extends LitElement {
padding-top: 20px;
}
.results {
display: inline-flex;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.results-container {
display: inline-flex;
@@ -157,7 +361,7 @@ class App extends LitElement {
background: #cbf1c9;
padding: 5px;
align-items: center;
margin: 10px 0;
margin: 10px 10px;
}
.container-label {
margin-bottom: 10px;
@@ -180,11 +384,27 @@ class App extends LitElement {
font-size: 18px;
margin-bottom: 10px;
}
.compare-button {
margin: 10px;
padding: 10px;
border-radius: 5px;
background: #4285F4;
color: white;
cursor: pointer;
}
.compare-button[disabled] {
background: #ccc;
cursor: not-allowed;
margin: 10px 0;
}
`;
static properties = {
tests: {type: Array},
dialogContent: {type: Object},
selectedTests: {type: Array},
comparisonContent: {type: Object},
compareMode: {type: Boolean},
};
async _init() {
@@ -197,42 +417,76 @@ class App extends LitElement {
super();
this.tests = [];
this.dialogContent = null;
this.selectedTests = [];
this.comparisonContent = null;
this.compareMode = false;
this._init();
this.addEventListener('dialog-closed', () => {
this.dialogContent = null;
this.comparisonContent = null;
});
}
updated(props) {
if (props.has('dialogContent')) {
if (props.has('dialogContent') || props.has('comparisonContent')) {
let dialog = this.shadowRoot.querySelector("#dialog");
dialog.open = !!this.dialogContent;
dialog.open = !!this.dialogContent || !!this.comparisonContent;
}
}
_onClick(testDetail) {
_onClick(testDetail, ev) {
if (this.compareMode) {
const index = this.selectedTests.indexOf(testDetail);
if (index > -1) {
this.selectedTests.splice(index, 1);
} else {
if (this.selectedTests.length >= 2) {
this.selectedTests.shift();
}
this.selectedTests.push(testDetail);
}
this.requestUpdate();
return;
}
this.dialogContent = testDetail;
this.shadowRoot.querySelector("#dialog").open = true;
}
_onCompare() {
if (this.selectedTests.length == 2) {
this.comparisonContent = {
left: this.selectedTests[0],
right: this.selectedTests[1],
};
}
}
_onCompareModeChange(e) {
this.compareMode = e.target.checked;
if (!this.compareMode) {
this.selectedTests = [];
}
}
render() {
let passed = this.tests.filter((t) => t.result == 'ok');
let failed = this.tests.filter((t) => t.result != 'ok');
const singleTiff = (url) => {
return html`<tiff-viewer style="max-width:100px" fileurl="${url}"></tiff-viewer>`;
};
const buildTiffs = (ts) => ts.map((t) => {
const buildTiffs = (ts) => repeat(ts, (t) => t.name, (t) => {
const printName = t.name.replace('.tif', '');
const tiff = singleTiff(getGoldenUrl(t));
const selected = this.selectedTests.includes(t);
return html`
<div class="test-item" @click="${()=>this._onClick(t)}">
<div class="test-item" @click="${(e)=>this._onClick(t, e)}" style=${selected ? "background: #eee" : ""}>
<span class="test-label">${printName}</span>
${tiff}
</div>
`;
});
const buildFailedTiffs = (ts) => ts.map((t) => {
const buildFailedTiffs = (ts) => repeat(ts, (t) => t.name, (t) => {
const printName = t.name.replace('.tif', '');
const goldenUrl = getGoldenUrl(t);
const compUrl = getCompUrl(t);
@@ -251,8 +505,8 @@ class App extends LitElement {
].map((a) => [singleTiff(a[0]), a[1]])
.map((a) => wrap(...a));
return html`
<div class="test-item" @click="${()=>this._onClick(t)}">
<span class="test-label">${printName}</span>
<div class="test-item" @click="${(e)=>this._onClick(t, e)}" >
<span class="test-label" title=${t.name}>${printName}</span>
<div class="failed-tiffs">
${tiffs}
</div>
@@ -269,14 +523,30 @@ class App extends LitElement {
if (this.dialogContent.result == 'ok') {
return html`<expanded-passed-result .content=${this.dialogContent}></expanded-passed-result>`;
}
return html`<expanded-failed-result .content=${this.dialogContent}></expanded-failed-result>`;
return html`<expanded-failed-result .content=${this.dialogContent} .tests=${this.tests}></expanded-failed-result>`;
})();
dialogHeader = this.dialogContent.name;
} else if (this.comparisonContent) {
dialogSlot = html`
<expanded-comparison-result
.left=${this.comparisonContent.left}
.right=${this.comparisonContent.right}
.tests=${this.tests}>
</expanded-comparison-result>
`;
dialogHeader = 'Comparison';
}
const compareButton = () => {
if (!this.compareMode) {
return html``;
}
return html`<button class="compare-button" @click=${this._onCompare} ?disabled=${this.selectedTests.length != 2}>Compare</button>`;
}
return html`
<div class="app">
<div class="results-container" style="background:#f5a5a4">
<div class="results-container" style="background:#f5a5a4; ${this.compareMode ? 'display:none' : ''}">
<span class="container-label">Failed</span>
<div class="results">
${failedTiffs}
@@ -288,6 +558,11 @@ class App extends LitElement {
${passedTiffs}
</div>
</div>
<label>
<input type="checkbox" .checked=${this.compareMode} @change=${this._onCompareModeChange}>
Compare Mode
</label>
${compareButton()}
</div>
<modal-dialog id="dialog">
<div class="dialog-header" slot="header">${dialogHeader}</div>
@@ -29,11 +29,13 @@ export class TiffViewer extends LitElement {
static properties = {
fileurl: {type: String, attribute: 'fileurl'},
name: {type: String, attribute: 'name'},
};
constructor() {
super();
this.fileurl = null;
this.name = null;
}
render() {
@@ -78,6 +80,15 @@ export class TiffViewer extends LitElement {
const imageData = new ImageData(new Uint8ClampedArray(rgba), firstImage.width, firstImage.height);
ctx.putImageData(imageData, 0, 0);
this.dispatchEvent(new CustomEvent('image-loaded', {
bubbles: true,
composed: true,
detail: {
name: this.name,
img: imageData,
}
}));
} catch (error) {
console.error('Error processing TIFF file:', error);
this._clearCanvas();
+10 -44
View File
@@ -28,7 +28,15 @@ endif()
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
enable_testing()
set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../spirv-headers)
if (APPLE)
set(CMAKE_MACOSX_RPATH ON)
endif (APPLE)
set(SPIRV_SKIP_EXECUTABLES_OPTION ON)
set(SPIRV_SKIP_TESTS_OPTION ON)
set(SKIP_SPIRV_TOOLS_INSTALL ON)
set(SPIRV_TOOLS "SPIRV-Tools")
include(GNUInstallDirs)
@@ -67,7 +75,6 @@ if ("${CMAKE_BUILD_TYPE}" STREQUAL "")
set(CMAKE_BUILD_TYPE "Debug")
endif()
option(SKIP_SPIRV_TOOLS_INSTALL "Skip installation" ${SKIP_SPIRV_TOOLS_INSTALL})
if(NOT ${SKIP_SPIRV_TOOLS_INSTALL})
set(ENABLE_SPIRV_TOOLS_INSTALL ON)
endif()
@@ -84,7 +91,7 @@ set(SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS "" CACHE STRING "Used by OSS-Fuzz to c
option(SPIRV_BUILD_LIBFUZZER_TARGETS "Build libFuzzer targets" OFF)
option(SPIRV_WERROR "Enable error on warning" ON)
set(SPIRV_WERROR OFF)
if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
set(COMPILER_IS_LIKE_GNU TRUE)
endif()
@@ -351,8 +358,6 @@ endif()
add_subdirectory(source)
add_subdirectory(tools)
add_subdirectory(test)
add_subdirectory(examples)
if(ENABLE_SPIRV_TOOLS_INSTALL)
install(
@@ -374,42 +379,3 @@ endif()
set(SPIRV_LIBRARIES "-lSPIRV-Tools-opt -lSPIRV-Tools -lSPIRV-Tools-link")
set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared")
# Build pkg-config file
# Use a first-class target so it's regenerated when relevant files are updated.
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc
COMMAND ${CMAKE_COMMAND}
-DCHANGES_FILE=${CMAKE_CURRENT_SOURCE_DIR}/CHANGES
-DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools.pc.in
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}
-DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR}
-DSPIRV_LIBRARIES=${SPIRV_LIBRARIES}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake
DEPENDS "CHANGES" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools.pc.in" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake")
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc
COMMAND ${CMAKE_COMMAND}
-DCHANGES_FILE=${CMAKE_CURRENT_SOURCE_DIR}/CHANGES
-DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools-shared.pc.in
-DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc
-DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}
-DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR}
-DSPIRV_SHARED_LIBRARIES=${SPIRV_SHARED_LIBRARIES}
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake
DEPENDS "CHANGES" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools-shared.pc.in" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake")
add_custom_target(spirv-tools-pkg-config
ALL
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc ${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc)
# Install pkg-config file
if (ENABLE_SPIRV_TOOLS_INSTALL)
install(
FILES
${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc
${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc
DESTINATION
${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()
+3 -12
View File
@@ -338,16 +338,7 @@ endfunction()
# Always build ${SPIRV_TOOLS}-shared. This is expected distro packages, and
# unlike the other SPIRV_TOOLS target, defaults to hidden symbol visibility.
add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES})
if (SPIRV_TOOLS_USE_MIMALLOC)
target_link_libraries(${SPIRV_TOOLS}-shared PRIVATE mimalloc-static)
endif()
spirv_tools_default_target_options(${SPIRV_TOOLS}-shared)
set_target_properties(${SPIRV_TOOLS}-shared PROPERTIES CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${SPIRV_TOOLS}-shared
PRIVATE SPIRV_TOOLS_IMPLEMENTATION
PUBLIC SPIRV_TOOLS_SHAREDLIB
)
if(SPIRV_TOOLS_BUILD_STATIC)
add_library(${SPIRV_TOOLS}-static STATIC ${SPIRV_SOURCES})
@@ -363,14 +354,14 @@ if(SPIRV_TOOLS_BUILD_STATIC)
add_library(${SPIRV_TOOLS} ALIAS ${SPIRV_TOOLS}-static)
endif()
set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS}-static ${SPIRV_TOOLS}-shared)
set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS}-static)
else()
add_library(${SPIRV_TOOLS} ${SPIRV_TOOLS_LIBRARY_TYPE} ${SPIRV_SOURCES})
if (SPIRV_TOOLS_USE_MIMALLOC)
target_link_libraries(${SPIRV_TOOLS} PRIVATE mimalloc-static)
endif()
spirv_tools_default_target_options(${SPIRV_TOOLS})
set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared)
set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS})
endif()
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
+69
View File
@@ -0,0 +1,69 @@
import re
import sys
import os
def process_file(filepath, replacements):
"""Reads a file, applies a series of regex replacements, and writes it back."""
try:
# Create a backup before modifying
backup_path = filepath + '.bak'
with open(filepath, 'r') as f_in, open(backup_path, 'w') as f_out:
content = f_in.read()
f_out.write(content)
except FileNotFoundError:
print(f"Error: File not found at {filepath}", file=sys.stderr)
sys.exit(1)
# Apply all replacements
for pattern, replacement in replacements:
content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
# Write the modified content back to the original file
with open(filepath, 'w') as f:
f.write(content)
def main():
"""Main function to define and process all file changes."""
# Make the script location-aware. It's in third_party/spirv-tools/tnt.
# The files to patch are in third_party/spirv-tools.
script_dir = os.path.dirname(os.path.abspath(__file__))
spirv_tools_dir = os.path.abspath(os.path.join(script_dir, '..'))
# Define replacements for third_party/spirv-tools/CMakeLists.txt
spirv_tools_cmakelist = os.path.join(spirv_tools_dir, "CMakeLists.txt")
spirv_tools_replacements = [
(r"^(set_property\(GLOBAL PROPERTY USE_FOLDERS ON\))",
r"\1\n\n"
r"set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../spirv-headers)\n\n"
r"if (APPLE)\n"
r" set(CMAKE_MACOSX_RPATH ON)\n"
r"endif (APPLE)\n"
r"set(SPIRV_SKIP_EXECUTABLES_OPTION ON)\n"
r"set(SPIRV_SKIP_TESTS_OPTION ON)\n"
r"set(SKIP_SPIRV_TOOLS_INSTALL ON)"),
(r"enable_testing\(\)\n", ""),
(r'option\(SKIP_SPIRV_TOOLS_INSTALL "Skip installation" \$\{SKIP_SPIRV_TOOLS_INSTALL\}\)\n', ""),
(r'option\(SPIRV_WERROR "Enable error on warning" ON\)',
r"set(SPIRV_WERROR OFF)"),
(r"add_subdirectory\(test\)\n", ""),
(r"add_subdirectory\(examples\)\n", ""),
(r"^# Build pkg-config file[\s\S]*?^endif\(\)\n", ""),
]
process_file(spirv_tools_cmakelist, spirv_tools_replacements)
# Define replacements for third_party/spirv-tools/source/CMakeLists.txt
spirv_source_cmakelist = os.path.join(spirv_tools_dir, "source", "CMakeLists.txt")
spirv_source_replacements = [
(r"^add_library\(\$\{SPIRV_TOOLS\}\-shared[\s\S]*?^\)", ""),
(r"set\(SPIRV_TOOLS_TARGETS \$\{SPIRV_TOOLS\}-static \$\{SPIRV_TOOLS\}\-shared\)",
r"set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS}-static)"),
(r"set\(SPIRV_TOOLS_TARGETS \$\{SPIRV_TOOLS\} \$\{SPIRV_TOOLS\}\-shared\)",
r"set(SPIRV_TOOLS_TARGETS ${SPIRV_TOOLS})"),
]
process_file(spirv_source_cmakelist, spirv_source_replacements)
print("Changes applied successfully using the corrected Python script.")
print("Backup files with .bak extension have been created.")
if __name__ == "__main__":
main()
+3
View File
@@ -91,6 +91,9 @@ mv "${EXTRACTED_DIR}" spirv-tools_new
rsync -a --delete --exclude=tnt/ spirv-tools_new/ spirv-tools/
echo "Applying Filament-specific patches..."
python3 "${SCRIPT_DIR}/filament_changes.py"
echo "Cleaning up..."
rm -rf "${ZIP_FILE_NAME}" spirv-tools_new "${TEMP_DIR}"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.65.1",
"version": "1.65.0",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",