Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15424c85b5 | ||
|
|
0dd1d3211b | ||
|
|
33249c6eb8 | ||
|
|
59f19aee0e | ||
|
|
06c8fc39c2 | ||
|
|
a36ad004b7 | ||
|
|
6ac5f26bb5 | ||
|
|
bce8320f06 | ||
|
|
f3a4e21fe5 | ||
|
|
6838d8602e | ||
|
|
fe9033c7db | ||
|
|
480b0878d7 | ||
|
|
57d1a6a165 | ||
|
|
fe81493052 | ||
|
|
9c52ec32ff | ||
|
|
5cf091220c | ||
|
|
f6892a111c | ||
|
|
63f877a68e | ||
|
|
3d8fb3bcea | ||
|
|
e218eca6b0 | ||
|
|
17b8b9ff65 | ||
|
|
49b8a17b1f | ||
|
|
f89c105941 | ||
|
|
3d9da8ab3b |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+11
-2
@@ -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,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()
|
||||
Vendored
+267
@@ -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=<username>
|
||||
sonatypePassword=<password>
|
||||
</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=<key id>
|
||||
signing.password=<key password>
|
||||
signing.secretKeyRingFile=<secret key ring file>
|
||||
</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>
|
||||
Vendored
+2
-2
@@ -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>
|
||||
|
||||
@@ -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
@@ -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', '~> 1.64.1'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.65.0'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>()) {}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 |
@@ -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;
|
||||
|
||||
@@ -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 }, {},
|
||||
{}));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -97,7 +97,7 @@ TEST_F(BackendTest, MRT) {
|
||||
screenWidth(), // width
|
||||
screenHeight(), // height
|
||||
1, // samples
|
||||
0, // layerCount
|
||||
1, // layerCount
|
||||
{{textureA },{textureB }}, // color
|
||||
{}, // depth
|
||||
{})); // stencil
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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,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",
|
||||
|
||||
Reference in New Issue
Block a user