Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b9308817c | ||
|
|
e4ae96a2a1 | ||
|
|
56ac08e353 | ||
|
|
52b0b553b4 | ||
|
|
00f3c7175c | ||
|
|
e4fa86fb01 | ||
|
|
7da2a08df6 | ||
|
|
82246d934d | ||
|
|
c60969ef67 | ||
|
|
ce37f216bc | ||
|
|
81c71fbbb9 | ||
|
|
07a7c6003a | ||
|
|
804a74c205 | ||
|
|
dde49a410a | ||
|
|
770ce7f8ec | ||
|
|
11714d3adc | ||
|
|
6aac9071b3 | ||
|
|
da9173e9dc | ||
|
|
cd64d50408 | ||
|
|
a3145cb96f | ||
|
|
cdfb92e14a | ||
|
|
55c16e6e7a | ||
|
|
65e3c3bfb9 | ||
|
|
902f869721 | ||
|
|
ad1bc6f360 | ||
|
|
73c343635e | ||
|
|
432e672022 | ||
|
|
b56b04c5f8 | ||
|
|
99816d67c2 | ||
|
|
d6d4f92922 | ||
|
|
6a59a68622 | ||
|
|
4580f57987 |
@@ -0,0 +1,26 @@
|
||||
name: 'Renderdiff Generate'
|
||||
description: 'Sets up prerequisites and runs the generate script for renderdiff'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- uses: ./.github/actions/get-gltf-assets
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- uses: ./.github/actions/get-vulkan-sdk
|
||||
- name: Prerequisites
|
||||
run: |
|
||||
# Must have at least clang-16 for a webgpu/dawn build.
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
shell: bash
|
||||
- name: Generate images
|
||||
run: |
|
||||
TEST_DIR=test/renderdiff
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
set -eux
|
||||
bash ${TEST_DIR}/generate.sh
|
||||
set +eux
|
||||
shell: bash
|
||||
- name: Build diffimg tool
|
||||
run: |
|
||||
./build.sh release diffimg
|
||||
shell: bash
|
||||
@@ -10,16 +10,27 @@ jobs:
|
||||
# a branch on filament-assets.
|
||||
update-renderdiff-goldens:
|
||||
name: update-renderdiff-goldens
|
||||
runs-on: 'ubuntu-24.04-4core'
|
||||
runs-on: 'macos-14-xlarge'
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/linux-prereq
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Build diffimg
|
||||
run: ./build.sh release diffimg
|
||||
- name: Check if accepting new goldens
|
||||
id: check_accept
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- name: Renderdiff Generate for new goldens
|
||||
if: steps.check_accept.outputs.accept == 'true'
|
||||
uses: ./.github/actions/renderdiff-generate
|
||||
- name: Run update script
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.FILAMENTBOT_TOKEN }}
|
||||
@@ -27,10 +38,24 @@ jobs:
|
||||
run: |
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py)
|
||||
COMMIT_HASH="${{ steps.get_commit_msg.outputs.hash }}"
|
||||
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
|
||||
if [[ "${{ steps.check_accept.outputs.accept }}" == "true" ]]; then
|
||||
SHORT_HASH="${COMMIT_HASH:0:8}"
|
||||
GOLDEN_BRANCH="accept-goldens-${SHORT_HASH}"
|
||||
echo "Generating new goldens for branch ${GOLDEN_BRANCH}"
|
||||
python3 test/renderdiff/src/update_golden.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
--source=$(pwd)/out/renderdiff/renders \
|
||||
--commit-msg="Auto-update goldens from ${COMMIT_HASH}" \
|
||||
--push-to-remote \
|
||||
--golden-repo-token=${GH_TOKEN}
|
||||
fi
|
||||
|
||||
if [[ "${GOLDEN_BRANCH}" != "main" ]]; then
|
||||
git config --global user.email "filament.bot@gmail.com"
|
||||
git config --global user.name "Filament Bot"
|
||||
git config --global credential.helper cache
|
||||
echo "branch==${GOLDEN_BRANCH}"
|
||||
echo "hash==${COMMIT_HASH}"
|
||||
python3 test/renderdiff/src/update_golden.py --branch=${GOLDEN_BRANCH} \
|
||||
|
||||
@@ -126,18 +126,24 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: ./.github/actions/mac-prereq
|
||||
- uses: ./.github/actions/get-gltf-assets
|
||||
- uses: ./.github/actions/get-mesa
|
||||
- uses: ./.github/actions/get-vulkan-sdk
|
||||
- id: get_commit_msg
|
||||
uses: ./.github/actions/get-commit-msg
|
||||
- name: Prerequisites
|
||||
- name: Check if accepting new goldens
|
||||
id: check_accept
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
run: |
|
||||
# Must have at least clang-16 for a webgpu/dawn build.
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer
|
||||
if echo "${COMMIT_MESSAGE}" | python3 test/renderdiff/src/commit_msg.py --mode=accept_new_goldens; then
|
||||
echo "accept=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "accept=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
- name: Renderdiff Generate
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
uses: ./.github/actions/renderdiff-generate
|
||||
- name: Render and compare
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
id: render_compare
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.get_commit_msg.outputs.msg }}
|
||||
@@ -146,9 +152,6 @@ jobs:
|
||||
source ${TEST_DIR}/src/preamble.sh
|
||||
set -eux
|
||||
GOLDEN_BRANCH=$(echo "${COMMIT_MESSAGE}" | python3 ${TEST_DIR}/src/commit_msg.py)
|
||||
bash ${TEST_DIR}/generate.sh
|
||||
# Build diffimg tool
|
||||
./build.sh release diffimg
|
||||
|
||||
python3 ${TEST_DIR}/src/golden_manager.py \
|
||||
--branch=${GOLDEN_BRANCH} \
|
||||
@@ -172,10 +175,12 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff
|
||||
- name: Compare result
|
||||
if: steps.check_accept.outputs.accept != 'true'
|
||||
run: |
|
||||
ERROR_STR="${{ steps.render_compare.outputs.err }}"
|
||||
if [ -n "${ERROR_STR}" ]; then
|
||||
|
||||
@@ -8,3 +8,4 @@ appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
## Release notes for next branch cut
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
- materials: better shadow normal-bias calculations [⚠️ **New Material Version**]
|
||||
|
||||
@@ -7,6 +7,10 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.69.5
|
||||
|
||||
- engine: fix crash when using variance shadow maps
|
||||
|
||||
## v1.69.4
|
||||
|
||||
|
||||
|
||||
+6
-6
@@ -235,9 +235,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -429,9 +429,9 @@
|
||||
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz",
|
||||
"integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
||||
+2
-2
@@ -181,7 +181,7 @@ important for <code>matc</code> (material compiler).</p>
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.69.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.69.4'
|
||||
}
|
||||
</code></pre>
|
||||
<p>Here are all the libraries available in the group <code>com.google.android.filament</code>:</p>
|
||||
@@ -195,7 +195,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.69.2'
|
||||
<pre><code class="language-shell">pod 'Filament', '~> 1.69.4'
|
||||
</code></pre>
|
||||
<h2 id="documentation"><a class="header" href="#documentation">Documentation</a></h2>
|
||||
<ul>
|
||||
|
||||
@@ -225,9 +225,10 @@ into <strong>branch</strong> of <code>filament-assets</code>. This branch is pai
|
||||
<code>filament</code> repo.</p>
|
||||
<p>As an example, imagine I am working on a PR, and I've uploaded my change, which is in a
|
||||
branch called <code>my-pr-branch</code>, to <code>filament</code>. This PR requires updating the golden. We would do
|
||||
it in the following fashion</p>
|
||||
it in the following fashion on a macOS machine:</p>
|
||||
<h3 id="using-a-script-to-update-the-golden-repo"><a class="header" href="#using-a-script-to-update-the-golden-repo">Using a script to update the golden repo</a></h3>
|
||||
<ul>
|
||||
<li>Make sure you've completed the steps in 'Setting up python'</li>
|
||||
<li>Run interactive mode in the <code>update_golden.py</code> script.
|
||||
<pre><code>python3 test/renderdiff/src/update_golden.py
|
||||
</code></pre>
|
||||
@@ -263,6 +264,27 @@ branch of the golden repo (i.e. <code>my-pr-branch-golden</code>).</li>
|
||||
<li>If the PR is merged, then there is another workflow that will merge <code>my-pr-branch-golden</code>
|
||||
to the <code>main</code> branch of the golden repo.</li>
|
||||
</ul>
|
||||
<h3 id="automated-update-via-commit-message"><a class="header" href="#automated-update-via-commit-message">Automated update via commit message</a></h3>
|
||||
<p>Alternatively, if you are confident in your changes and want the CI to handle the update
|
||||
for you, you can use the following tag in your commit message:</p>
|
||||
<ul>
|
||||
<li>In the commit message of your working branch on <code>filament</code>, add the following line:
|
||||
<pre><code>RDIFF_ACCEPT_NEW_GOLDENS
|
||||
</code></pre>
|
||||
</li>
|
||||
</ul>
|
||||
<p>This has the following effects:</p>
|
||||
<ul>
|
||||
<li>The presubmit test <code>test-renderdiff</code> will be bypassed (it will not perform rendering or
|
||||
comparison).</li>
|
||||
<li>When the PR is merged, the postsubmit CI will automatically:
|
||||
<ol>
|
||||
<li>Build Filament and generate the new images.</li>
|
||||
<li>Upload them to a temporary branch in <code>filament-assets</code>.</li>
|
||||
<li>Merge that branch into <code>main</code>.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 id="viewing-test-results"><a class="header" href="#viewing-test-results">Viewing test results</a></h2>
|
||||
<p>We provide a viewer for looking at the result of a test run. The viewer is a webapp that can
|
||||
be used by pointing your browser to a localhost port. If you input the viewer with a PR or a
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,12 +16,13 @@ class SimulatedSkybox {
|
||||
this.ozone = 0.0;
|
||||
this.msFactors = [0.1, 0.5, 0.0];
|
||||
this.contrast = 1.0;
|
||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
||||
this.nightColor = [0.0, 3.0e-9, 7.5e-9];
|
||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
this.starIntensity = 1.0;
|
||||
this.focalLength = 24.0;
|
||||
this.height = 1000.0;
|
||||
this.planetRadius = 6360.0;
|
||||
@@ -43,7 +44,7 @@ class SimulatedSkybox {
|
||||
|
||||
// Milky Way Parameters
|
||||
// x=Intensity, y=Saturation, z=Unused
|
||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
||||
this.milkyWayControl = [1.0, 1.2, 0.05];
|
||||
this.milkyWayEnabled = true;
|
||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
||||
this.initEntity();
|
||||
@@ -453,6 +454,11 @@ class SimulatedSkybox {
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setStarIntensity(intensity) {
|
||||
this.starIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
@@ -500,6 +506,13 @@ class SimulatedSkybox {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
setExposure(exposure) {
|
||||
this.exposure = exposure;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
@@ -585,6 +598,7 @@ class SimulatedSkybox {
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
||||
this.materialInstance.setFloatParameter('starIntensity', this.starIntensity);
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
|
||||
@@ -615,12 +629,13 @@ class SimulatedSkybox {
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||
|
||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
||||
// Scale Milky Way by Sun Intensity
|
||||
// Calibration: 1.0 User Intensity = 1.5e-3 cd/m^2 (Nits) approx.
|
||||
// Target: 1.5e-3 Nits. SunIntensity = 100,000.
|
||||
// Scale = 1.5e-3 / 1.0e5 = 1.5e-8.
|
||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
||||
const mwUniform = [
|
||||
mwIntensity * this.sunIntensity * 0.025,
|
||||
mwIntensity * this.sunIntensity * 1.5e-8,
|
||||
this.milkyWayControl[1],
|
||||
this.milkyWayControl[2]
|
||||
];
|
||||
@@ -634,6 +649,7 @@ class SimulatedSkybox {
|
||||
const moonHaloUpload = [...this.moonHalo];
|
||||
moonHaloUpload[2] *= moonRadConv;
|
||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||
this.materialInstance.setFloatParameter('exposure', this.exposure !== undefined ? this.exposure : 1.0);
|
||||
|
||||
// Solar Eclipse (CPU Calculation)
|
||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||
|
||||
Binary file not shown.
+26
-5
@@ -218,6 +218,7 @@ class App {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
this.skybox.setExposure(exposure); // Update Skybox Exposure Uniform
|
||||
|
||||
// Moon Exposure
|
||||
if (this.mParams) {
|
||||
@@ -389,7 +390,7 @@ class App {
|
||||
};
|
||||
|
||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 100.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
||||
@@ -464,18 +465,23 @@ class App {
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
this.sParams = {
|
||||
enabled: true,
|
||||
density: 0.001
|
||||
density: 0.001,
|
||||
intensity: 0.0 // 2^0 = 1.0
|
||||
};
|
||||
// Initialize defaults (Density 0.001, Enabled True)
|
||||
sky.setStarControl(0.001, true);
|
||||
sky.setStarIntensity(Math.pow(2.0, 0.0));
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||
sky.setStarIntensity(Math.pow(2.0, this.sParams.intensity));
|
||||
};
|
||||
|
||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'intensity', 0.0, 24.0).name('Intensity (Exp)').onChange(updateStars);
|
||||
starFolder.close();
|
||||
this.updateStars = updateStars;
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 0.0
|
||||
@@ -489,7 +495,7 @@ class App {
|
||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||
|
||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
||||
|
||||
|
||||
const shmFolder = artFolder.addFolder('Shimmer');
|
||||
// Set Shimmer Strength default to 0.0
|
||||
@@ -502,7 +508,7 @@ class App {
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 0.05, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||
|
||||
const bloomFolder = camFolder.addFolder('Bloom');
|
||||
@@ -692,13 +698,15 @@ class App {
|
||||
const s = this.sParams;
|
||||
const b = this.bParams;
|
||||
const m = this.mParams;
|
||||
const mw = this.mwParams;
|
||||
const sk = this.skybox;
|
||||
|
||||
return {
|
||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||
s: { e: s.enabled, d: s.density },
|
||||
s: { e: s.enabled, d: s.density, i: s.intensity },
|
||||
mw: { e: mw.enabled, i: mw.intensity, s: mw.saturation, bp: mw.blackPoint, st: mw.siderealTime, l: mw.latitude },
|
||||
b: { e: b.enabled, lf: b.lensFlare },
|
||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||
@@ -723,6 +731,7 @@ class App {
|
||||
const c = state.c;
|
||||
const w = state.w;
|
||||
const s = state.s;
|
||||
const mw = state.mw;
|
||||
const b = state.b;
|
||||
const m = state.m;
|
||||
const k = state.k;
|
||||
@@ -757,6 +766,18 @@ class App {
|
||||
if (s) {
|
||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
||||
if (s.d !== undefined) this.sParams.density = s.d;
|
||||
if (s.i !== undefined) this.sParams.intensity = s.i;
|
||||
if (this.updateStars) this.updateStars();
|
||||
}
|
||||
|
||||
if (mw) {
|
||||
if (mw.e !== undefined) this.mwParams.enabled = mw.e;
|
||||
if (mw.i !== undefined) this.mwParams.intensity = mw.i;
|
||||
if (mw.s !== undefined) this.mwParams.saturation = mw.s;
|
||||
if (mw.bp !== undefined) this.mwParams.blackPoint = mw.bp;
|
||||
if (mw.st !== undefined) this.mwParams.siderealTime = mw.st;
|
||||
if (mw.l !== undefined) this.mwParams.latitude = mw.l;
|
||||
if (this.updateMW) this.updateMW();
|
||||
}
|
||||
|
||||
if (b) {
|
||||
|
||||
@@ -96,10 +96,20 @@ material {
|
||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : starIntensity,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonTexture
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : exposure,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonNormal
|
||||
@@ -162,9 +172,9 @@ fragment {
|
||||
// --- CONFIGURATION ---
|
||||
|
||||
// Stars
|
||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 15.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||
@@ -961,7 +971,11 @@ fragment {
|
||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||
sampler2D moonTex, sampler2D moonNormal) {
|
||||
sampler2D moonTex, sampler2D moonNormal,
|
||||
highp vec3 nightColor,
|
||||
highp mat3 milkyWayRotation,
|
||||
highp vec3 milkyWayControl,
|
||||
sampler2D milkyWayTexture) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
@@ -1057,7 +1071,7 @@ fragment {
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
||||
}
|
||||
|
||||
// Sun Disk Reflection
|
||||
@@ -1088,6 +1102,29 @@ fragment {
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Add Milky Way to Reflection
|
||||
// Calculate Fade based on Sun Elevation
|
||||
highp float sunElvSin = L.y;
|
||||
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
||||
|
||||
if (mwFade > 0.0) {
|
||||
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
||||
|
||||
// Apply Atmosphere Transmittance (approximate)
|
||||
mwColor *= outTransmittance;
|
||||
|
||||
// Apply Fades
|
||||
mwColor *= mwFade;
|
||||
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
||||
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
||||
|
||||
reflection += mwColor;
|
||||
}
|
||||
|
||||
|
||||
// Add Night Color to Reflection
|
||||
reflection += nightColor;
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
@@ -1187,7 +1224,7 @@ fragment {
|
||||
|
||||
// 7. Stars
|
||||
// Add stars before clouds (clouds cover stars)
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl);
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
||||
|
||||
// 7b. Milky Way
|
||||
// Add Milky Way behind stars (conceptually) but handled similarly
|
||||
@@ -1232,7 +1269,11 @@ fragment {
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||
materialParams_moonTexture, materialParams_moonNormal);
|
||||
materialParams_moonTexture, materialParams_moonNormal,
|
||||
materialParams.nightColor,
|
||||
materialParams.milkyWayRotation,
|
||||
materialParams.milkyWayControl,
|
||||
materialParams_milkyWayTexture);
|
||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,13 @@ class SimulatedSkybox {
|
||||
this.ozone = 0.0;
|
||||
this.msFactors = [0.1, 0.5, 0.0];
|
||||
this.contrast = 1.0;
|
||||
this.nightColor = [0.0, 0.0003, 0.00075];
|
||||
this.nightColor = [0.0, 3.0e-9, 7.5e-9];
|
||||
this.shimmerControl = [0.0, 20.0, 0.1];
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
this.starIntensity = 1.0;
|
||||
this.focalLength = 24.0;
|
||||
this.height = 1000.0;
|
||||
this.planetRadius = 6360.0;
|
||||
@@ -43,7 +44,7 @@ class SimulatedSkybox {
|
||||
|
||||
// Milky Way Parameters
|
||||
// x=Intensity, y=Saturation, z=Unused
|
||||
this.milkyWayControl = [1.0, 1.0, 0.07];
|
||||
this.milkyWayControl = [1.0, 1.2, 0.05];
|
||||
this.milkyWayEnabled = true;
|
||||
this.milkyWayRotation = [1, 0, 0, 0, 1, 0, 0, 0, 1]; // Identity by default
|
||||
this.initEntity();
|
||||
@@ -453,6 +454,11 @@ class SimulatedSkybox {
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setStarIntensity(intensity) {
|
||||
this.starIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
@@ -500,6 +506,13 @@ class SimulatedSkybox {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
setExposure(exposure) {
|
||||
this.exposure = exposure;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
@@ -585,6 +598,7 @@ class SimulatedSkybox {
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat4Parameter('starControl', new Float32Array(this.starControl));
|
||||
this.materialInstance.setFloatParameter('starIntensity', this.starIntensity);
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
|
||||
@@ -615,12 +629,13 @@ class SimulatedSkybox {
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||
|
||||
// Scale Milky Way by Sun Intensity (Pre-exposed) to match dynamic range
|
||||
// Calibration: 1.0 User Intensity ~ 0.025 Lux Relative (approx 2.5% of Sun Pixel Value at Day)
|
||||
// At Sunny 16 (Sun ~ 2.6), this gives ~0.065 pixel brightness, which is visible but dim.
|
||||
// Scale Milky Way by Sun Intensity
|
||||
// Calibration: 1.0 User Intensity = 1.5e-3 cd/m^2 (Nits) approx.
|
||||
// Target: 1.5e-3 Nits. SunIntensity = 100,000.
|
||||
// Scale = 1.5e-3 / 1.0e5 = 1.5e-8.
|
||||
const mwIntensity = this.milkyWayEnabled ? this.milkyWayControl[0] : 0.0;
|
||||
const mwUniform = [
|
||||
mwIntensity * this.sunIntensity * 0.025,
|
||||
mwIntensity * this.sunIntensity * 1.5e-8,
|
||||
this.milkyWayControl[1],
|
||||
this.milkyWayControl[2]
|
||||
];
|
||||
@@ -634,6 +649,7 @@ class SimulatedSkybox {
|
||||
const moonHaloUpload = [...this.moonHalo];
|
||||
moonHaloUpload[2] *= moonRadConv;
|
||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||
this.materialInstance.setFloatParameter('exposure', this.exposure !== undefined ? this.exposure : 1.0);
|
||||
|
||||
// Solar Eclipse (CPU Calculation)
|
||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||
|
||||
Binary file not shown.
@@ -218,6 +218,7 @@ class App {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
this.skybox.setExposure(exposure); // Update Skybox Exposure Uniform
|
||||
|
||||
// Moon Exposure
|
||||
if (this.mParams) {
|
||||
@@ -389,7 +390,7 @@ class App {
|
||||
};
|
||||
|
||||
mwFolder.add(this.mwParams, 'enabled').name('Enabled').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 5.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'intensity', 0.0, 100.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'saturation', 0.0, 2.0).onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'blackPoint', 0.0, 0.5).name('Black Point').onChange(updateMW);
|
||||
mwFolder.add(this.mwParams, 'siderealTime', 0.0, 24.0).name('Sidereal Time').listen().onChange(updateMW);
|
||||
@@ -464,18 +465,23 @@ class App {
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
this.sParams = {
|
||||
enabled: true,
|
||||
density: 0.001
|
||||
density: 0.001,
|
||||
intensity: 0.0 // 2^0 = 1.0
|
||||
};
|
||||
// Initialize defaults (Density 0.001, Enabled True)
|
||||
sky.setStarControl(0.001, true);
|
||||
sky.setStarIntensity(Math.pow(2.0, 0.0));
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||
sky.setStarIntensity(Math.pow(2.0, this.sParams.intensity));
|
||||
};
|
||||
|
||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'intensity', 0.0, 24.0).name('Intensity (Exp)').onChange(updateStars);
|
||||
starFolder.close();
|
||||
this.updateStars = updateStars;
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 0.0
|
||||
@@ -489,7 +495,7 @@ class App {
|
||||
artFolder.add(sky.msFactors, 2, 0.0, 1.0).name('Horizon Glow').onChange(v => sky.setHorizonGlow(v));
|
||||
artFolder.add(sky, 'contrast', 0.1, 2.0).onChange(v => sky.setContrast(v));
|
||||
|
||||
artFolder.addColor(sky, 'nightColor').onChange(v => sky.setNightColor(v));
|
||||
|
||||
|
||||
const shmFolder = artFolder.addFolder('Shimmer');
|
||||
// Set Shimmer Strength default to 0.0
|
||||
@@ -502,7 +508,7 @@ class App {
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
camFolder.add(this.params, 'focalLength', 8.0, 300.0).name('Focal Length').onChange(() => this.updateCameraProjection());
|
||||
camFolder.add(this.params, 'aperture', 1.4, 32.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 1.0, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'shutterSpeed', 0.05, 1000.0).onChange(() => this.updateCameraExposure());
|
||||
camFolder.add(this.params, 'iso', 50.0, 3200.0).onChange(() => this.updateCameraExposure());
|
||||
|
||||
const bloomFolder = camFolder.addFolder('Bloom');
|
||||
@@ -692,13 +698,15 @@ class App {
|
||||
const s = this.sParams;
|
||||
const b = this.bParams;
|
||||
const m = this.mParams;
|
||||
const mw = this.mwParams;
|
||||
const sk = this.skybox;
|
||||
|
||||
return {
|
||||
p: { a: p.aperture, ss: p.shutterSpeed, i: p.iso, st: p.sunTheta, sp: p.sunPhi, fl: p.focalLength, si: p.sunIntensity },
|
||||
c: { v: c.volumetrics, co: c.coverage, d: c.density, h: c.height, s: c.speed, e: c.evolution },
|
||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||
s: { e: s.enabled, d: s.density },
|
||||
s: { e: s.enabled, d: s.density, i: s.intensity },
|
||||
mw: { e: mw.enabled, i: mw.intensity, s: mw.saturation, bp: mw.blackPoint, st: mw.siderealTime, l: mw.latitude },
|
||||
b: { e: b.enabled, lf: b.lensFlare },
|
||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||
@@ -723,6 +731,7 @@ class App {
|
||||
const c = state.c;
|
||||
const w = state.w;
|
||||
const s = state.s;
|
||||
const mw = state.mw;
|
||||
const b = state.b;
|
||||
const m = state.m;
|
||||
const k = state.k;
|
||||
@@ -757,6 +766,18 @@ class App {
|
||||
if (s) {
|
||||
if (s.e !== undefined) this.sParams.enabled = s.e;
|
||||
if (s.d !== undefined) this.sParams.density = s.d;
|
||||
if (s.i !== undefined) this.sParams.intensity = s.i;
|
||||
if (this.updateStars) this.updateStars();
|
||||
}
|
||||
|
||||
if (mw) {
|
||||
if (mw.e !== undefined) this.mwParams.enabled = mw.e;
|
||||
if (mw.i !== undefined) this.mwParams.intensity = mw.i;
|
||||
if (mw.s !== undefined) this.mwParams.saturation = mw.s;
|
||||
if (mw.bp !== undefined) this.mwParams.blackPoint = mw.bp;
|
||||
if (mw.st !== undefined) this.mwParams.siderealTime = mw.st;
|
||||
if (mw.l !== undefined) this.mwParams.latitude = mw.l;
|
||||
if (this.updateMW) this.updateMW();
|
||||
}
|
||||
|
||||
if (b) {
|
||||
|
||||
@@ -96,10 +96,20 @@ material {
|
||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : starIntensity,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonTexture
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : exposure,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : sampler2d,
|
||||
name : moonNormal
|
||||
@@ -162,9 +172,9 @@ fragment {
|
||||
// --- CONFIGURATION ---
|
||||
|
||||
// Stars
|
||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 15.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||
@@ -961,7 +971,11 @@ fragment {
|
||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||
sampler2D moonTex, sampler2D moonNormal) {
|
||||
sampler2D moonTex, sampler2D moonNormal,
|
||||
highp vec3 nightColor,
|
||||
highp mat3 milkyWayRotation,
|
||||
highp vec3 milkyWayControl,
|
||||
sampler2D milkyWayTexture) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
@@ -1057,7 +1071,7 @@ fragment {
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
||||
}
|
||||
|
||||
// Sun Disk Reflection
|
||||
@@ -1088,6 +1102,29 @@ fragment {
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Add Milky Way to Reflection
|
||||
// Calculate Fade based on Sun Elevation
|
||||
highp float sunElvSin = L.y;
|
||||
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
||||
|
||||
if (mwFade > 0.0) {
|
||||
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
||||
|
||||
// Apply Atmosphere Transmittance (approximate)
|
||||
mwColor *= outTransmittance;
|
||||
|
||||
// Apply Fades
|
||||
mwColor *= mwFade;
|
||||
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
||||
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
||||
|
||||
reflection += mwColor;
|
||||
}
|
||||
|
||||
|
||||
// Add Night Color to Reflection
|
||||
reflection += nightColor;
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
@@ -1187,7 +1224,7 @@ fragment {
|
||||
|
||||
// 7. Stars
|
||||
// Add stars before clouds (clouds cover stars)
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl);
|
||||
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
||||
|
||||
// 7b. Milky Way
|
||||
// Add Milky Way behind stars (conceptually) but handled similarly
|
||||
@@ -1232,7 +1269,11 @@ fragment {
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||
materialParams_moonTexture, materialParams_moonNormal);
|
||||
materialParams_moonTexture, materialParams_moonNormal,
|
||||
materialParams.nightColor,
|
||||
materialParams.milkyWayRotation,
|
||||
materialParams.milkyWayControl,
|
||||
materialParams_milkyWayTexture);
|
||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
|
||||
@@ -578,6 +578,7 @@ if (APPLE OR LINUX)
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_Autoresolve.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
|
||||
@@ -1706,6 +1706,8 @@ enum class Workaround : uint16_t {
|
||||
DISABLE_DEPTH_PRECACHE_FOR_DEFAULT_MATERIAL,
|
||||
// Emulate an sRGB swapchain in shader code.
|
||||
EMULATE_SRGB_SWAPCHAIN,
|
||||
// Workaround for AMD Vulkan drivers on Windows crashing when SSR history is detached.
|
||||
BLIT_SSR_HISTORY,
|
||||
};
|
||||
|
||||
using StereoscopicType = Platform::StereoscopicType;
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
@@ -105,8 +110,21 @@ JNIEnv* VirtualMachineEnv::getEnvironmentSlow() {
|
||||
FILAMENT_CHECK_PRECONDITION(mVirtualMachine)
|
||||
<< "JNI_OnLoad() has not been called";
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, nullptr);
|
||||
#ifdef __ANDROID__
|
||||
JavaVMAttachArgs args;
|
||||
args.version = JNI_VERSION_1_6;
|
||||
args.group = nullptr;
|
||||
char threadName[16]; // pthread_getname_np returns at most 16 bytes
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (pthread_getname_np(pthread_self(), threadName, sizeof(threadName)) == 0) {
|
||||
args.name = threadName;
|
||||
} else {
|
||||
args.name = nullptr;
|
||||
}
|
||||
} else {
|
||||
args.name = nullptr;
|
||||
}
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(&mJniEnv, &args);
|
||||
#else
|
||||
jint const result = mVirtualMachine->AttachCurrentThread(reinterpret_cast<void**>(&mJniEnv), nullptr);
|
||||
#endif
|
||||
|
||||
@@ -1092,7 +1092,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
||||
// a multisampled sidecar texture and do a resolve automatically.
|
||||
if (samples > 1 && texture->samples == 1) {
|
||||
auto& sidecar = texture->msaaSidecar;
|
||||
if (!sidecar) {
|
||||
if (!sidecar || sidecar.sampleCount != samples) {
|
||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||
texture->height, samples);
|
||||
}
|
||||
@@ -1123,7 +1123,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
||||
// a multisampled sidecar texture and do a resolve automatically.
|
||||
if (samples > 1 && texture->samples == 1) {
|
||||
auto& sidecar = texture->msaaSidecar;
|
||||
if (!sidecar) {
|
||||
if (!sidecar || sidecar.sampleCount != samples) {
|
||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||
texture->height, samples);
|
||||
}
|
||||
@@ -1155,7 +1155,7 @@ MetalRenderTarget::MetalRenderTarget(MetalContext* context, uint32_t width, uint
|
||||
// a multisampled sidecar texture and do a resolve automatically.
|
||||
if (samples > 1 && texture->samples == 1) {
|
||||
auto& sidecar = texture->msaaSidecar;
|
||||
if (!sidecar) {
|
||||
if (!sidecar || sidecar.sampleCount != samples) {
|
||||
sidecar = createMultisampledTexture(mtlTexture.pixelFormat, texture->width,
|
||||
texture->height, samples);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
GLenum getIndicesType() const noexcept {
|
||||
return indicesType;
|
||||
}
|
||||
} gl;
|
||||
};
|
||||
|
||||
static bool queryOpenGLVersion(GLint* major, GLint* minor) noexcept;
|
||||
|
||||
|
||||
@@ -120,33 +120,27 @@ bool PlatformEGL::isOpenGL() const noexcept {
|
||||
PlatformEGL::ExternalImageEGL::~ExternalImageEGL() = default;
|
||||
|
||||
Driver* PlatformEGL::createDriver(void* sharedContext, const DriverConfig& driverConfig) {
|
||||
static constexpr int kMaxNumEGLDevices = 32;
|
||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||
|
||||
EGLint major, minor;
|
||||
EGLBoolean initialized = false;
|
||||
EGLBoolean initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
|
||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||
PFNEGLGETPLATFORMDISPLAYEXTPROC const getPlatformDisplay =
|
||||
PFNEGLGETPLATFORMDISPLAYEXTPROC(eglGetProcAddress("eglGetPlatformDisplay"));
|
||||
|
||||
if (eglQueryDevicesEXT != nullptr && getPlatformDisplay != nullptr) {
|
||||
EGLint numDevices = 0;
|
||||
EGLDeviceEXT eglDevices[kMaxNumEGLDevices];
|
||||
if (eglQueryDevicesEXT(kMaxNumEGLDevices, eglDevices, &numDevices)) {
|
||||
for (int i = 0; i < numDevices && !initialized; ++i) {
|
||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevices[i], nullptr);
|
||||
if (!initialized) {
|
||||
EGLDeviceEXT eglDevice;
|
||||
EGLint numDevices;
|
||||
PFNEGLQUERYDEVICESEXTPROC const eglQueryDevicesEXT =
|
||||
PFNEGLQUERYDEVICESEXTPROC(eglGetProcAddress("eglQueryDevicesEXT"));
|
||||
if (eglQueryDevicesEXT != nullptr) {
|
||||
eglQueryDevicesEXT(1, &eglDevice, &numDevices);
|
||||
if(auto* getPlatformDisplay = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
|
||||
eglGetProcAddress("eglGetPlatformDisplay"))) {
|
||||
mEGLDisplay = getPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, eglDevice, nullptr);
|
||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert_invariant(mEGLDisplay != EGL_NO_DISPLAY);
|
||||
initialized = eglInitialize(mEGLDisplay, &major, &minor);
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(!initialized)) {
|
||||
LOG(ERROR) << "eglInitialize failed";
|
||||
return nullptr;
|
||||
|
||||
@@ -36,6 +36,7 @@ VK_DEFINE_HANDLE(VmaPool)
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanCommandBuffer;
|
||||
struct VulkanRenderPass;
|
||||
struct VulkanRenderTarget;
|
||||
struct VulkanSwapChain;
|
||||
struct VulkanTexture;
|
||||
@@ -58,11 +59,11 @@ struct VulkanAttachment {
|
||||
VkImageSubresourceRange getSubresourceRange() const;
|
||||
};
|
||||
|
||||
struct VulkanRenderPass {
|
||||
struct VulkanRenderPassContext {
|
||||
// Between the begin and end command render pass we cache the command buffer
|
||||
VulkanCommandBuffer* commandBuffer;
|
||||
fvkmemory::resource_ptr<VulkanRenderTarget> renderTarget;
|
||||
VkRenderPass renderPass;
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> renderPass;
|
||||
RenderPassParams params;
|
||||
int currentSubpass;
|
||||
};
|
||||
@@ -120,6 +121,10 @@ public:
|
||||
return mPhysicalDeviceProperties.properties.vendorID;
|
||||
}
|
||||
|
||||
inline char const* getDriverName() const noexcept {
|
||||
return mDriverProperties.driverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of pre-registered external formats for prewarming the Vulkan
|
||||
* pipeline cache.
|
||||
@@ -216,6 +221,9 @@ private:
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
};
|
||||
VkPhysicalDeviceDriverProperties mDriverProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES,
|
||||
};
|
||||
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -1656,6 +1657,17 @@ bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
|
||||
return false;
|
||||
case Workaround::DISABLE_BLIT_INTO_TEXTURE_ARRAY:
|
||||
return false;
|
||||
case Workaround::BLIT_SSR_HISTORY: {
|
||||
#if defined(WIN32)
|
||||
// AMD GPU on windows crashes when SSR is enabled. It's root-caused to be writing to the
|
||||
// SSR history texture. We blit the history instead of using the linear output of the
|
||||
// color pass. See Issue #9680.
|
||||
std::string_view deviceName{ mContext.getDriverName() };
|
||||
return deviceName.find("AMD proprietary driver") != std::string_view::npos;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -1975,17 +1987,19 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
rpkey.initialDepthLayout = currentDepthLayout;
|
||||
rpkey.subpassMask = uint8_t(params.subpassMask);
|
||||
|
||||
VkRenderPass renderPass = mFramebufferCache.getRenderPass(rpkey);
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> renderPass =
|
||||
mFramebufferCache.getRenderPass(rpkey, &mResourceManager);
|
||||
mPipelineCache.bindRenderPass(renderPass, 0);
|
||||
|
||||
// Create the VkFramebuffer or fetch it from cache.
|
||||
VulkanFboCache::FboKey fbkey = rt->getFboKey();
|
||||
fbkey.renderPass = renderPass;
|
||||
fbkey.renderPass = renderPass->getVkRenderPass();
|
||||
fbkey.layers = 1;
|
||||
|
||||
rt->emitBarriersBeginRenderPass(*commandBuffer);
|
||||
|
||||
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> vkfb =
|
||||
mFramebufferCache.getFramebuffer(fbkey, &mResourceManager);
|
||||
|
||||
// Assign a label to the framebuffer for debugging purposes.
|
||||
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS | FVK_DEBUG_DEBUG_UTILS)
|
||||
@@ -1998,12 +2012,14 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
|
||||
// The current command buffer now has references to the render target and its attachments.
|
||||
commandBuffer->acquire(rt);
|
||||
commandBuffer->acquire(renderPass);
|
||||
commandBuffer->acquire(vkfb);
|
||||
|
||||
// Populate the structures required for vkCmdBeginRenderPass.
|
||||
VkRenderPassBeginInfo renderPassInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
|
||||
.renderPass = renderPass,
|
||||
.framebuffer = vkfb,
|
||||
.renderPass = renderPass->getVkRenderPass(),
|
||||
.framebuffer = vkfb->getVkFramebuffer(),
|
||||
|
||||
// The renderArea field constrains the LoadOp, but scissoring does not.
|
||||
// Therefore, we do not set the scissor rect here, we only need it in draw().
|
||||
@@ -2058,7 +2074,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
mCurrentRenderPass = {
|
||||
.commandBuffer = commandBuffer,
|
||||
.renderTarget = rt,
|
||||
.renderPass = renderPassInfo.renderPass,
|
||||
.renderPass = renderPass,
|
||||
.params = params,
|
||||
.currentSubpass = 0,
|
||||
};
|
||||
@@ -2078,8 +2094,7 @@ void VulkanDriver::endRenderPass(int) {
|
||||
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
||||
|
||||
mCurrentRenderPass.renderTarget = {};
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
|
||||
mCurrentRenderPass.renderPass = {};
|
||||
mCurrentRenderPass.commandBuffer = nullptr;
|
||||
}
|
||||
|
||||
@@ -2244,7 +2259,7 @@ void VulkanDriver::resolve(
|
||||
Handle<HwTexture> src, uint8_t dstLevel, uint8_t dstLayer) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
||||
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||
<< "resolve() cannot be invoked inside a render pass.";
|
||||
|
||||
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
||||
@@ -2287,7 +2302,7 @@ void VulkanDriver::blit(
|
||||
math::uint2 size) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
||||
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||
<< "blit() cannot be invoked inside a render pass.";
|
||||
|
||||
auto srcTexture = resource_ptr<VulkanTexture>::cast(&mResourceManager, src);
|
||||
@@ -2329,7 +2344,7 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
|
||||
// Note: blitDEPRECATED is only used for Renderer::copyFrame()
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mCurrentRenderPass.renderPass == VK_NULL_HANDLE)
|
||||
FILAMENT_CHECK_PRECONDITION(!mCurrentRenderPass.renderPass)
|
||||
<< "blitDEPRECATED() cannot be invoked inside a render pass.";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(buffers == TargetBufferFlags::COLOR0)
|
||||
|
||||
@@ -139,7 +139,7 @@ private:
|
||||
|
||||
resource_ptr<VulkanSwapChain> mCurrentSwapChain;
|
||||
resource_ptr<VulkanRenderTarget> mDefaultRenderTarget;
|
||||
VulkanRenderPass mCurrentRenderPass = {};
|
||||
VulkanRenderPassContext mCurrentRenderPass = {};
|
||||
VmaAllocator mAllocator = VK_NULL_HANDLE;
|
||||
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "VulkanFboCache.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
@@ -69,9 +70,10 @@ VulkanFboCache::~VulkanFboCache() {
|
||||
<< "Please explicitly call terminate() while the VkDevice is still alive.";
|
||||
}
|
||||
|
||||
VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> VulkanFboCache::getFramebuffer(
|
||||
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||
FboMap::iterator iter = mFramebufferCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mFramebufferCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
||||
if (UTILS_LIKELY(iter != mFramebufferCache.end())) {
|
||||
iter.value().timestamp = mCurrentTime;
|
||||
return iter->second.handle;
|
||||
}
|
||||
@@ -117,13 +119,16 @@ VkFramebuffer VulkanFboCache::getFramebuffer(FboKey const& config) noexcept {
|
||||
VkResult error = vkCreateFramebuffer(mDevice, &info, VKALLOC, &framebuffer);
|
||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create framebuffer."
|
||||
<< " error=" << static_cast<int32_t>(error);
|
||||
mFramebufferCache[config] = {framebuffer, mCurrentTime};
|
||||
return framebuffer;
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> fbh =
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer>::construct(resManager, mDevice, framebuffer);
|
||||
mFramebufferCache[config] = {fbh, mCurrentTime};
|
||||
return fbh;
|
||||
}
|
||||
|
||||
VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept {
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> VulkanFboCache::getRenderPass(
|
||||
RenderPassKey const& config, fvkmemory::ResourceManager* resManager) noexcept {
|
||||
auto iter = mRenderPassCache.find(config);
|
||||
if (UTILS_LIKELY(iter != mRenderPassCache.end() && iter->second.handle != VK_NULL_HANDLE)) {
|
||||
if (UTILS_LIKELY(iter != mRenderPassCache.end())) {
|
||||
iter.value().timestamp = mCurrentTime;
|
||||
return iter->second.handle;
|
||||
}
|
||||
@@ -326,7 +331,9 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
||||
VkResult error = vkCreateRenderPass(mDevice, &renderPassInfo, VKALLOC, &renderPass);
|
||||
FILAMENT_CHECK_POSTCONDITION(error == VK_SUCCESS) << "Unable to create render pass."
|
||||
<< " error=" << error;
|
||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> rph =
|
||||
fvkmemory::resource_ptr<VulkanRenderPass>::construct(resManager, mDevice, renderPass);
|
||||
mRenderPassCache[config] = {rph, mCurrentTime};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
FVK_LOGD << "Created render pass " << renderPass << " with ";
|
||||
@@ -343,13 +350,12 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount;
|
||||
#endif
|
||||
|
||||
return renderPass;
|
||||
return rph;
|
||||
}
|
||||
|
||||
void VulkanFboCache::resetFramebuffers() noexcept {
|
||||
for (const auto& pair: mFramebufferCache) {
|
||||
mRenderPassRefCount[pair.first.renderPass]--;
|
||||
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
|
||||
}
|
||||
mFramebufferCache.clear();
|
||||
}
|
||||
@@ -357,9 +363,6 @@ void VulkanFboCache::resetFramebuffers() noexcept {
|
||||
void VulkanFboCache::terminate() noexcept {
|
||||
resetFramebuffers();
|
||||
|
||||
for (const auto& pair: mRenderPassCache) {
|
||||
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
|
||||
}
|
||||
mRenderPassRefCount.clear();
|
||||
mRenderPassCache.clear();
|
||||
}
|
||||
@@ -380,8 +383,6 @@ void VulkanFboCache::gc() noexcept {
|
||||
const FboVal fbo = iter->second;
|
||||
if (fbo.timestamp < evictTime && fbo.handle) {
|
||||
mRenderPassRefCount[iter->first.renderPass]--;
|
||||
vkDestroyFramebuffer(mDevice, fbo.handle, VKALLOC);
|
||||
iter.value().handle = VK_NULL_HANDLE;
|
||||
|
||||
// erase(iterator) returns the iterator to the next element.
|
||||
iter = mFramebufferCache.erase(iter);
|
||||
@@ -391,11 +392,8 @@ void VulkanFboCache::gc() noexcept {
|
||||
}
|
||||
|
||||
for (RenderPassMap::iterator iter = mRenderPassCache.begin(); iter != mRenderPassCache.end(); ) {
|
||||
const VkRenderPass handle = iter->second.handle;
|
||||
const VkRenderPass handle = iter->second.handle->getVkRenderPass();
|
||||
if (iter->second.timestamp < evictTime && handle && mRenderPassRefCount[handle] == 0) {
|
||||
vkDestroyRenderPass(mDevice, handle, VKALLOC);
|
||||
iter.value().handle = VK_NULL_HANDLE;
|
||||
|
||||
// erase(iterator) returns the iterator to the next element.
|
||||
iter = mRenderPassCache.erase(iter);
|
||||
mRenderPassRefCount.erase(handle);
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#define TNT_FILAMENT_BACKEND_VULKANFBOCACHE_H
|
||||
|
||||
#include "VulkanContext.h"
|
||||
#include "vulkan/memory/Resource.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
@@ -27,6 +30,9 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanFramebuffer;
|
||||
struct VulkanRenderPass;
|
||||
|
||||
// Simple manager for VkFramebuffer and VkRenderPass objects.
|
||||
//
|
||||
// Note that a VkFramebuffer is just a binding between a render pass and a set of image views. So,
|
||||
@@ -57,7 +63,7 @@ public:
|
||||
uint8_t padding[2];
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> handle;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
static_assert(0 == MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT % 8);
|
||||
@@ -83,7 +89,7 @@ public:
|
||||
VkImageView depth; // 8 bytes
|
||||
};
|
||||
struct FboVal {
|
||||
VkFramebuffer handle;
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> handle;
|
||||
uint32_t timestamp;
|
||||
};
|
||||
static_assert(sizeof(VkRenderPass) == 8, "VkRenderPass has unexpected size.");
|
||||
@@ -98,10 +104,12 @@ public:
|
||||
~VulkanFboCache();
|
||||
|
||||
// Retrieves or creates a VkFramebuffer handle.
|
||||
VkFramebuffer getFramebuffer(FboKey const& config) noexcept;
|
||||
fvkmemory::resource_ptr<VulkanFramebuffer> getFramebuffer(
|
||||
FboKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
|
||||
|
||||
// Retrieves or creates a VkRenderPass handle.
|
||||
VkRenderPass getRenderPass(RenderPassKey const& config) noexcept;
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> getRenderPass(
|
||||
RenderPassKey const& config, fvkmemory::ResourceManager* resManager) noexcept;
|
||||
|
||||
// Evicts old unused Vulkan objects. Call this once per frame.
|
||||
void gc() noexcept;
|
||||
|
||||
@@ -565,7 +565,7 @@ void VulkanRenderTarget::transformViewportToPlatform(VkViewport* bounds) const {
|
||||
flipVertically(bounds, getExtent().height);
|
||||
}
|
||||
|
||||
uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) const {
|
||||
uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPassContext& pass) const {
|
||||
if (!mOffscreen) {
|
||||
return 1;
|
||||
}
|
||||
@@ -715,4 +715,18 @@ VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
|
||||
vertexBuffer(vb),
|
||||
indexBuffer(ib) {}
|
||||
|
||||
VulkanFramebuffer::VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer)
|
||||
: mDevice(device), mFramebuffer(framebuffer) {}
|
||||
|
||||
VulkanFramebuffer::~VulkanFramebuffer() {
|
||||
vkDestroyFramebuffer(mDevice, mFramebuffer, VKALLOC);
|
||||
}
|
||||
|
||||
VulkanRenderPass::VulkanRenderPass(VkDevice device, VkRenderPass renderPass)
|
||||
: mDevice(device), mRenderPass(renderPass) {}
|
||||
|
||||
VulkanRenderPass::~VulkanRenderPass() {
|
||||
vkDestroyRenderPass(mDevice, mRenderPass, VKALLOC);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -419,7 +419,7 @@ struct VulkanRenderTarget : private HwRenderTarget, fvkmemory::Resource {
|
||||
return mInfo->fbkey.samples;
|
||||
}
|
||||
|
||||
uint8_t getColorTargetCount(VulkanRenderPass const& pass) const;
|
||||
uint8_t getColorTargetCount(VulkanRenderPassContext const& pass) const;
|
||||
|
||||
inline bool hasDepth() const { return mInfo->depthIndex != Auxiliary::UNDEFINED_INDEX; }
|
||||
|
||||
@@ -585,6 +585,32 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
|
||||
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
|
||||
};
|
||||
|
||||
struct VulkanFramebuffer : public fvkmemory::Resource {
|
||||
VulkanFramebuffer(VkDevice device, VkFramebuffer framebuffer);
|
||||
~VulkanFramebuffer();
|
||||
|
||||
inline VkFramebuffer getVkFramebuffer() const noexcept {
|
||||
return mFramebuffer;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VkFramebuffer mFramebuffer;
|
||||
};
|
||||
|
||||
struct VulkanRenderPass : public fvkmemory::Resource {
|
||||
VulkanRenderPass(VkDevice device, VkRenderPass renderPass);
|
||||
~VulkanRenderPass();
|
||||
|
||||
inline VkRenderPass getVkRenderPass() const noexcept {
|
||||
return mRenderPass;
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
VkRenderPass mRenderPass;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANHANDLES_H
|
||||
|
||||
@@ -459,8 +459,10 @@ void VulkanPipelineCache::bindStencilState(StencilState const& stencilState) noe
|
||||
mPipelineRequirements.stencilState = stencilState;
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept {
|
||||
mPipelineRequirements.renderPass = renderPass;
|
||||
void VulkanPipelineCache::bindRenderPass(
|
||||
fvkmemory::resource_ptr<VulkanRenderPass> renderPass,
|
||||
int subpassIndex) noexcept {
|
||||
mPipelineRequirements.renderPass = renderPass->getVkRenderPass();
|
||||
mPipelineRequirements.subpassIndex = subpassIndex;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ public:
|
||||
void bindProgram(fvkmemory::resource_ptr<VulkanProgram> program) noexcept;
|
||||
void bindRasterState(RasterState const& rasterState) noexcept;
|
||||
void bindStencilState(StencilState const& stencilState) noexcept;
|
||||
void bindRenderPass(VkRenderPass renderPass, int subpassIndex) noexcept;
|
||||
void bindRenderPass(fvkmemory::resource_ptr<VulkanRenderPass> renderPass,
|
||||
int subpassIndex) noexcept;
|
||||
void bindPrimitiveTopology(VkPrimitiveTopology topology) noexcept;
|
||||
void bindVertexArray(VkVertexInputAttributeDescription const* attribDesc,
|
||||
VkVertexInputBindingDescription const* bufferDesc, uint8_t count);
|
||||
|
||||
@@ -42,6 +42,8 @@ template ResourceType getTypeEnum<VulkanSync>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanMemoryMappedBuffer>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanSemaphore>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanStream>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanFramebuffer>() noexcept;
|
||||
template ResourceType getTypeEnum<VulkanRenderPass>() noexcept;
|
||||
|
||||
template<typename D>
|
||||
ResourceType getTypeEnum() noexcept {
|
||||
@@ -108,6 +110,12 @@ ResourceType getTypeEnum() noexcept {
|
||||
if constexpr (std::is_same_v<D, VulkanStream>) {
|
||||
return ResourceType::STREAM;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanFramebuffer>) {
|
||||
return ResourceType::FRAMEBUFFER;
|
||||
}
|
||||
if constexpr (std::is_same_v<D, VulkanRenderPass>) {
|
||||
return ResourceType::RENDER_PASS;
|
||||
}
|
||||
return ResourceType::UNDEFINED_TYPE;
|
||||
}
|
||||
|
||||
@@ -155,6 +163,10 @@ std::string_view getTypeStr(ResourceType type) {
|
||||
return "Semaphore";
|
||||
case ResourceType::STREAM:
|
||||
return "VulkanStream";
|
||||
case ResourceType::FRAMEBUFFER:
|
||||
return "Framebuffer";
|
||||
case ResourceType::RENDER_PASS:
|
||||
return "RenderPass";
|
||||
case ResourceType::UNDEFINED_TYPE:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@ enum class ResourceType : uint8_t {
|
||||
MEMORY_MAPPED_BUFFER = 18,
|
||||
SEMAPHORE = 19,
|
||||
STREAM = 20,
|
||||
UNDEFINED_TYPE = 21, // Must be the last enum because we use it for iterating over the enums.
|
||||
FRAMEBUFFER = 21,
|
||||
RENDER_PASS = 22,
|
||||
UNDEFINED_TYPE = 23, // Must be the last enum because we use it for iterating over the enums.
|
||||
};
|
||||
|
||||
template<typename D>
|
||||
|
||||
@@ -126,6 +126,12 @@ void ResourceManager::destroyWithType(ResourceType type, HandleId id) {
|
||||
case ResourceType::STREAM:
|
||||
destruct<VulkanStream>(Handle<VulkanStream>(id));
|
||||
break;
|
||||
case ResourceType::FRAMEBUFFER:
|
||||
destruct<VulkanFramebuffer>(Handle<VulkanFramebuffer>(id));
|
||||
break;
|
||||
case ResourceType::RENDER_PASS:
|
||||
destruct<VulkanRenderPass>(Handle<VulkanRenderPass>(id));
|
||||
break;
|
||||
case ResourceType::UNDEFINED_TYPE:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -952,6 +952,9 @@ void VulkanPlatform::queryAndSetDeviceFeatures(Platform::DriverConfig const& dri
|
||||
chainStruct(&context.mPhysicalDeviceFeatures, &globalPriorityFeatures);
|
||||
}
|
||||
|
||||
// Obtain and store driver info
|
||||
chainStruct(&context.mPhysicalDeviceProperties, &context.mDriverProperties);
|
||||
|
||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||
// physicalDeviceFeatures.
|
||||
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 "BackendTest.h"
|
||||
|
||||
#include "Shader.h"
|
||||
#include "SharedShaders.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
namespace test {
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
using namespace filament::math;
|
||||
|
||||
TEST_F(BackendTest, AutoresolveDifferingSampleCounts) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::OPENGL), "see b/486954356");
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::CI, Backend::VULKAN), "see b/486954356");
|
||||
|
||||
auto& api = getDriverApi();
|
||||
constexpr int kRenderTargetSize = 512;
|
||||
|
||||
auto swapChain = addCleanup(createSwapChain());
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
Shader shader = SharedShaders::makeShader(api, *mCleanup,
|
||||
{
|
||||
.mVertexType = VertexShaderType::Simple,
|
||||
.mFragmentType = FragmentShaderType::SolidColored,
|
||||
.mUniformType = ShaderUniformType::Simple,
|
||||
});
|
||||
|
||||
TrianglePrimitive triangle(api);
|
||||
PipelineState ps = getColorWritePipelineState();
|
||||
shader.addProgramToPipelineState(ps);
|
||||
|
||||
auto ubuffer = addCleanup(api.createBufferObject(sizeof(SimpleMaterialParams),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::STATIC));
|
||||
shader.bindUniform<SimpleMaterialParams>(api, ubuffer);
|
||||
|
||||
// Create a texture with sample count = 1.
|
||||
Handle<HwTexture> texture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1,
|
||||
TextureFormat::RGBA8, 1, kRenderTargetSize, kRenderTargetSize, 1,
|
||||
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
|
||||
|
||||
// First render pass: render a red triangle to a RenderTarget with 8 samples.
|
||||
{
|
||||
Handle<HwRenderTarget> renderTarget =
|
||||
addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, kRenderTargetSize,
|
||||
kRenderTargetSize, 8, 1, { { texture } }, {}, {}));
|
||||
|
||||
shader.uploadUniform(api, ubuffer,
|
||||
SimpleMaterialParams{
|
||||
.color = float4(1, 0, 0, 1),
|
||||
});
|
||||
|
||||
RenderPassParams params = getClearColorRenderPass(float4(0));
|
||||
params.viewport = { 0, 0, kRenderTargetSize, kRenderTargetSize };
|
||||
|
||||
RenderFrame frame(api);
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// Second render pass: render a green triangle to a RenderTarget with 4 samples, attached to
|
||||
// the same texture.
|
||||
{
|
||||
Handle<HwRenderTarget> renderTarget =
|
||||
addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, kRenderTargetSize,
|
||||
kRenderTargetSize, 4, 1, { { texture } }, {}, {}));
|
||||
|
||||
shader.uploadUniform(api, ubuffer,
|
||||
SimpleMaterialParams{
|
||||
.color = float4(0, 1, 0, 1),
|
||||
});
|
||||
|
||||
RenderPassParams params = getClearColorRenderPass(float4(0));
|
||||
params.viewport = { 0, 0, kRenderTargetSize, kRenderTargetSize };
|
||||
|
||||
RenderFrame frame(api);
|
||||
api.beginRenderPass(renderTarget, params);
|
||||
ps.primitiveType = PrimitiveType::TRIANGLES;
|
||||
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
|
||||
api.bindPipeline(ps);
|
||||
api.bindRenderPrimitive(triangle.getRenderPrimitive());
|
||||
api.draw2(0, 3, 1);
|
||||
api.endRenderPass();
|
||||
|
||||
EXPECT_IMAGE(renderTarget, ScreenshotParams(kRenderTargetSize, kRenderTargetSize,
|
||||
"AutoresolveDifferingSampleCounts", 1048576));
|
||||
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -271,8 +271,8 @@ std::unique_ptr<MaterialDefinition> MaterialDefinition::create(FEngine& engine,
|
||||
|
||||
void MaterialDefinition::terminate(FEngine& engine) {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
perViewDescriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
perViewDescriptorSetLayoutVsm.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
perViewDescriptorSetLayoutPcf.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
perViewDescriptorSetLayoutS2d.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
descriptorSetLayout.terminate(engine.getDescriptorSetLayoutFactory(), driver);
|
||||
}
|
||||
|
||||
@@ -607,23 +607,23 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
|
||||
refractionMode == RefractionMode::SCREEN_SPACE;
|
||||
bool const hasFog = !(variantFilterMask & UserVariantFilterMask(UserVariantFilterBit::FOG));
|
||||
|
||||
this->perViewDescriptorSetLayoutDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
this->perViewDescriptorSetLayoutPcfDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
materialDomain, isLit, isSSR, hasFog, false);
|
||||
|
||||
this->perViewDescriptorSetLayoutVsmDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
this->perViewDescriptorSetLayoutS2dDescription = descriptor_sets::getPerViewDescriptorSetLayout(
|
||||
materialDomain, isLit, isSSR, hasFog, true);
|
||||
|
||||
// set the labels
|
||||
this->descriptorSetLayoutDescription.label = CString{ name }.append("_perMat");
|
||||
this->perViewDescriptorSetLayoutDescription.label = CString{ name }.append("_perView");
|
||||
this->perViewDescriptorSetLayoutVsmDescription.label = CString{ name }.append("_perViewVsm");
|
||||
this->perViewDescriptorSetLayoutPcfDescription.label = CString{ name }.append("_perView");
|
||||
this->perViewDescriptorSetLayoutS2dDescription.label = CString{ name }.append("_perViewVsm");
|
||||
|
||||
// get the PER_RENDERABLE and PER_VIEW descriptor binding info
|
||||
for (auto&& [bindingPoint, dsl] : {
|
||||
std::pair{ DescriptorSetBindingPoints::PER_RENDERABLE,
|
||||
descriptor_sets::getPerRenderableLayout() },
|
||||
std::pair{ DescriptorSetBindingPoints::PER_VIEW,
|
||||
this->perViewDescriptorSetLayoutDescription }}) {
|
||||
this->perViewDescriptorSetLayoutPcfDescription }}) {
|
||||
Program::DescriptorBindingsInfo& descriptors = programDescriptorBindings[+bindingPoint];
|
||||
descriptors.reserve(dsl.descriptors.size());
|
||||
for (auto const& entry: dsl.descriptors) {
|
||||
@@ -636,17 +636,17 @@ void MaterialDefinition::processDescriptorSets(FEngine& engine) {
|
||||
descriptorSetLayoutFactory, driver,
|
||||
this->descriptorSetLayoutDescription };
|
||||
|
||||
this->perViewDescriptorSetLayout = {
|
||||
this->perViewDescriptorSetLayoutPcf = {
|
||||
descriptorSetLayoutFactory, driver,
|
||||
this->perViewDescriptorSetLayoutDescription };
|
||||
this->perViewDescriptorSetLayoutPcfDescription };
|
||||
|
||||
this->perViewDescriptorSetLayoutVsm = {
|
||||
this->perViewDescriptorSetLayoutS2d = {
|
||||
descriptorSetLayoutFactory, driver,
|
||||
this->perViewDescriptorSetLayoutVsmDescription };
|
||||
this->perViewDescriptorSetLayoutS2dDescription };
|
||||
}
|
||||
|
||||
backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetLayoutDescription(
|
||||
Variant const variant, bool const useVsmDescriptorSetLayout) const noexcept {
|
||||
Variant const variant, bool const useS2dDescriptorSetLayout) const noexcept {
|
||||
if (materialDomain == MaterialDomain::SURFACE) {
|
||||
if (Variant::isValidDepthVariant(variant)) {
|
||||
// Use the layout description used to create the per view depth variant layout.
|
||||
@@ -657,10 +657,10 @@ backend::DescriptorSetLayout const& MaterialDefinition::getPerViewDescriptorSetL
|
||||
return descriptor_sets::getSsrVariantLayout();
|
||||
}
|
||||
}
|
||||
if (useVsmDescriptorSetLayout) {
|
||||
return perViewDescriptorSetLayoutVsmDescription;
|
||||
if (useS2dDescriptorSetLayout) {
|
||||
return perViewDescriptorSetLayoutS2dDescription;
|
||||
}
|
||||
return perViewDescriptorSetLayoutDescription;
|
||||
return perViewDescriptorSetLayoutPcfDescription;
|
||||
}
|
||||
|
||||
Handle<HwProgram> MaterialDefinition::compileProgram(
|
||||
@@ -689,7 +689,7 @@ Handle<HwProgram> MaterialDefinition::compileProgram(
|
||||
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_VIEW,
|
||||
getPerViewDescriptorSetLayoutDescription(
|
||||
specialization.variant,
|
||||
Variant::isVSMVariant(specialization.variant)));
|
||||
Variant::isShadowSampler2DVariant(specialization.variant)));
|
||||
pb.descriptorLayout(+DescriptorSetBindingPoints::PER_RENDERABLE,
|
||||
descriptor_sets::getPerRenderableLayout());
|
||||
pb.descriptorLayout(
|
||||
|
||||
@@ -94,17 +94,17 @@ struct MaterialDefinition {
|
||||
backend::ShaderModel const sm, bool isStereoSupported) const noexcept;
|
||||
|
||||
backend::DescriptorSetLayout const& getPerViewDescriptorSetLayoutDescription(
|
||||
Variant const variant, bool useVsmDescriptorSetLayout) const noexcept;
|
||||
Variant const variant, bool useS2dDescriptorSetLayout) const noexcept;
|
||||
|
||||
// Keep track of the definitions of the descriptor set layouts, as these
|
||||
// may be used by some backends in parallel compilation of programs.
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutDescription;
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutVsmDescription;
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutPcfDescription;
|
||||
backend::DescriptorSetLayout perViewDescriptorSetLayoutS2dDescription;
|
||||
backend::DescriptorSetLayout descriptorSetLayoutDescription;
|
||||
|
||||
// try to order by frequency of use
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayout;
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutVsm;
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutPcf;
|
||||
filament::DescriptorSetLayout perViewDescriptorSetLayoutS2d;
|
||||
filament::DescriptorSetLayout descriptorSetLayout;
|
||||
backend::Program::DescriptorSetInfo programDescriptorBindings;
|
||||
|
||||
|
||||
@@ -802,7 +802,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
|
||||
|
||||
// use our special SSR variant, it can only be applied to object that have
|
||||
// the SCREEN_SPACE ReflectionMode.
|
||||
passBuilder.variant(Variant{ Variant::SPECIAL_SSR });
|
||||
passBuilder.variant(Variant{ Variant::SPECIAL_SSR_VARIANT });
|
||||
|
||||
// generate all our drawing commands, except blended objects.
|
||||
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::SCREEN_SPACE_REFLECTIONS);
|
||||
|
||||
@@ -568,7 +568,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
||||
if constexpr (isDepthPass) {
|
||||
cmd.info.materialVariant = variant;
|
||||
cmd.info.rasterState = {};
|
||||
cmd.info.rasterState.colorWrite = Variant::isPickingVariant(variant) || Variant::isVSMVariant(variant);
|
||||
cmd.info.rasterState.colorWrite = Variant::isPickingVariant(variant) || Variant::isDepthMomentsVariant(variant);
|
||||
cmd.info.rasterState.depthWrite = true;
|
||||
cmd.info.rasterState.depthFunc = RasterState::DepthFunc::GE;
|
||||
cmd.info.rasterState.alphaToCoverage = false;
|
||||
@@ -616,7 +616,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
||||
bool const hasSkinningOrMorphing = hasSkinning || hasMorphing;
|
||||
|
||||
// if we are already an SSR variant, the SRE bit is already set
|
||||
static_assert(Variant::SPECIAL_SSR & Variant::SRE);
|
||||
static_assert(Variant::SPECIAL_SSR_VARIANT & Variant::SRE);
|
||||
Variant renderableVariant{ variant };
|
||||
|
||||
// we can't have SSR and shadowing together by construction
|
||||
@@ -798,16 +798,19 @@ RenderPass::Command* RenderPass::generateCommandsImpl(CommandTypeFlags extraFlag
|
||||
cmd.info.rasterState.culling = cullingMode;
|
||||
|
||||
// FIXME: should writeDepthForShadowCasters take precedence over mi->getDepthWrite()?
|
||||
cmd.info.rasterState.depthWrite = (1 // only keep bit 0
|
||||
& (mi->isDepthWriteEnabled() | (mode == TransparencyMode::TWO_PASSES_ONE_SIDE)
|
||||
| isPickingVariant)
|
||||
& !(filterTranslucentObjects & translucent)
|
||||
& !(depthFilterAlphaMaskedObjects & rs.alphaToCoverage))
|
||||
| writeDepthForShadowCasters;
|
||||
cmd.info.rasterState.depthWrite =
|
||||
(1 // only keep bit 0
|
||||
& (mi->isDepthWriteEnabled() |
|
||||
(mode == TransparencyMode::TWO_PASSES_ONE_SIDE) |
|
||||
isPickingVariant) &
|
||||
!(depthFilterAlphaMaskedObjects & rs.alphaToCoverage)) |
|
||||
writeDepthForShadowCasters;
|
||||
|
||||
*curr = cmd;
|
||||
// cancel command if both front and back faces are culled
|
||||
curr->key |= select(cullingMode == CullingMode::FRONT_AND_BACK);
|
||||
// cancel command if asked to filter translucent objects
|
||||
curr->key |= select(filterTranslucentObjects & translucent);
|
||||
}
|
||||
|
||||
++curr;
|
||||
|
||||
+58
-75
@@ -213,12 +213,14 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
* Final shadow map transform
|
||||
*/
|
||||
|
||||
// Final shadow transform (focused warped light-space)
|
||||
// Final shadow transform (focused warped light-space), world space to clip space
|
||||
const mat4f S = F * (W * LMpMv);
|
||||
|
||||
// Computes St the transform to use in the shader to access the shadow map texture
|
||||
// i.e. it transforms a world-space vertex to a texture coordinate in the shadowmap
|
||||
const auto [Mt, Mn] = getTextureCoordsMapping(shadowMapInfo, getViewport());
|
||||
|
||||
// world space to texture atlas space
|
||||
const mat4f St = highPrecisionMultiply(Mt, S);
|
||||
|
||||
ShaderParameters shaderParameters;
|
||||
@@ -226,14 +228,12 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
|
||||
|
||||
// note: in texelSizeWorldSpace() below, we can use Mb * Mt * F * W because
|
||||
// L * Mp * Mv is a rigid transform for directional lights, and doesn't matter.
|
||||
// if Wp[3][1] is 0, then LISPSM was cancelled.
|
||||
// if Wp[3][1] is 0, then LiSPSM was canceled.
|
||||
if (useLispsm && Wp[3][1] != 0.0f) {
|
||||
shaderParameters.texelSizeAtOneMeterWs =
|
||||
texelSizeWorldSpace(Wp, mat4f(Mt * F), shadowMapInfo.shadowDimension);
|
||||
shaderParameters.texelSizeAtOneMeterWs = texelSizeWorldSpace(S, shadowMapInfo.shadowDimension);
|
||||
} else {
|
||||
// We know we're using an ortho projection
|
||||
shaderParameters.texelSizeAtOneMeterWs =
|
||||
texelSizeWorldSpace(St.upperLeft(), shadowMapInfo.shadowDimension);
|
||||
shaderParameters.texelSizeAtOneMeterWs = texelSizeWorldSpace(S.upperLeft(), shadowMapInfo.shadowDimension);
|
||||
}
|
||||
if (!shadowMapInfo.vsm) {
|
||||
shaderParameters.lightSpace = St;
|
||||
@@ -1117,84 +1117,67 @@ bool ShadowMap::intersectSegmentWithPlanarQuad(float3& UTILS_RESTRICT p,
|
||||
return hit;
|
||||
}
|
||||
|
||||
float ShadowMap::texelSizeWorldSpace(const mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) noexcept {
|
||||
float2 ShadowMap::texelSizeWorldSpace(const mat3f& clipFromWorld, uint16_t shadowDimension) noexcept {
|
||||
// The Jacobian of the transformation from texture-to-world is the matrix itself for
|
||||
// orthographic projections. We just need to inverse worldToShadowTexture,
|
||||
// orthographic projections. We just need to inverse shadowMapFromWorld,
|
||||
// which is guaranteed to be orthographic.
|
||||
// The two first columns give us how a texel maps in world-space.
|
||||
const float ures = 1.0f / float(shadowDimension);
|
||||
const float vres = 1.0f / float(shadowDimension);
|
||||
const mat3f shadowTextureToWorld(inverse(worldToShadowTexture));
|
||||
const float3 Jx = shadowTextureToWorld[0];
|
||||
const float3 Jy = shadowTextureToWorld[1];
|
||||
const float s = std::max(length(Jx) * ures, length(Jy) * vres);
|
||||
float const oneTexel = 2.0f / float(shadowDimension);
|
||||
mat3f const worldFromClip(inverse(clipFromWorld));
|
||||
float3 const Jx = worldFromClip[0];
|
||||
float3 const Jy = worldFromClip[1];
|
||||
float2 const s = float2{ length(Jx), length(Jy) } * oneTexel;
|
||||
return s;
|
||||
}
|
||||
|
||||
float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
|
||||
uint16_t shadowDimension) noexcept {
|
||||
// Here we compute the Jacobian of inverse(MbMtF * Wp).
|
||||
// The expression below has been computed with Mathematica. However, it's not very hard,
|
||||
// albeit error-prone, to do it by hand because MbMtF is a linear transform.
|
||||
// So we really only need to calculate the Jacobian of inverse(Wp) at inverse(MbMtF).
|
||||
//
|
||||
// Because we're only interested in the length of the columns of the Jacobian, we can use
|
||||
// Mb * Mt * F * Wp instead of the full expression Mb * Mt * F * Wp * Wv * L * Mp * Mv,
|
||||
// because Wv * L * Mp * Mv is a rigid transform, which doesn't affect the length of
|
||||
// the Jacobian's column vectors.
|
||||
/**
|
||||
* Calculates the Jacobian matrix J = ∂(u,v,d)/∂(x,y,z) for a 4x4 perspective projection.
|
||||
*
|
||||
* Given a view-space point P = [x, y, z, 1]^T and a projection matrix M,
|
||||
* the projected homogeneous coordinates are [X, Y, Z, W]^T = M * P.
|
||||
* The resulting NDC coordinates are u = X/W, v = Y/W, and depth d = Z/W.
|
||||
*
|
||||
* To find the Jacobian on the CPU, we apply the quotient rule to each component:
|
||||
* ∂(X/W) / ∂xi = ( (∂X/∂xi) * W - X * (∂W/∂xi) ) / W^2
|
||||
*
|
||||
* In matrix form, this can be expressed as:
|
||||
* J = (1/W) * [ M_sub - (1/W) * (T ⊗ w_grad) ]
|
||||
*
|
||||
* Where:
|
||||
* - W: The homogeneous w-component after projection (usually -z for standard mats).
|
||||
* - M_sub: The top-left 3x3 submatrix of M.
|
||||
* - T: The column vector [X, Y, Z]^T (pre-perspective divide).
|
||||
* - w_grad: The row vector [m30, m31, m32] (the first three elements of M's last row).
|
||||
* - ⊗: The outer product, resulting in a 3x3 matrix.
|
||||
*
|
||||
* This Jacobian describes the local "stretch" of the projection. For LiSPSM or
|
||||
* shadow mapping, the inverse Jacobian J^-1 provides the world-space footprint
|
||||
* of a shadow texel, which is essential for calculating an accurate,
|
||||
* non-constant depth bias to eliminate shadow acne.
|
||||
*
|
||||
* @param M The 4x4 projection matrix.
|
||||
* @param p The 3D point in view-space where the Jacobian is evaluated.
|
||||
* @return A 3x3 matrix representing the partial derivatives of (u,v,d) w.r.t (x,y,z).
|
||||
*/
|
||||
static mat3f jacobian(mat4f const& M, float3 const& p) noexcept {
|
||||
float4 const T = M * p;
|
||||
mat3f const M_sub = M.upperLeft();
|
||||
float3 const w_grad = { M[0].w, M[1].w, M[2].w };
|
||||
mat3f const t_cross_w{
|
||||
w_grad.x * T.xyz,
|
||||
w_grad.y * T.xyz,
|
||||
w_grad.z * T.xyz
|
||||
};
|
||||
return (M_sub - t_cross_w / T.w) / T.w;
|
||||
}
|
||||
|
||||
float2 ShadowMap::texelSizeWorldSpace(mat4f const& S, uint16_t const shadowDimension) noexcept {
|
||||
// The Jacobian is not constant, so we evaluate it in the center of the shadow-map texture.
|
||||
// It might be better to do this computation in the vertex shader.
|
||||
float3 const p = { 0.5f, 0.5f, 0.0f };
|
||||
|
||||
const float ures = 1.0f / float(shadowDimension);
|
||||
const float vres = 1.0f / float(shadowDimension);
|
||||
const float dres = 1.0f / 65536.0f;
|
||||
|
||||
constexpr bool JACOBIAN_ESTIMATE = false;
|
||||
if constexpr (JACOBIAN_ESTIMATE) {
|
||||
// This estimates the Jacobian -- this is a lot heavier. This is mostly for reference
|
||||
// and testing.
|
||||
const mat4f Si(inverse(MbMtF * Wp));
|
||||
const float3 p0 = mat4f::project(Si, p);
|
||||
const float3 p1 = mat4f::project(Si, p + float3{ 1, 0, 0 } * ures);
|
||||
const float3 p2 = mat4f::project(Si, p + float3{ 0, 1, 0 } * vres);
|
||||
const float3 p3 = mat4f::project(Si, p + float3{ 0, 0, 1 } * dres);
|
||||
const float3 Jx = p1 - p0;
|
||||
const float3 Jy = p2 - p0;
|
||||
const float3 UTILS_UNUSED Jz = p3 - p0;
|
||||
const float s = std::max(length(Jx), length(Jy));
|
||||
return s;
|
||||
}
|
||||
|
||||
const float n = Wp[0][0];
|
||||
const float A = Wp[1][1];
|
||||
const float B = Wp[3][1];
|
||||
const float sx = MbMtF[0][0];
|
||||
const float sy = MbMtF[1][1];
|
||||
const float sz = MbMtF[2][2];
|
||||
const float ox = MbMtF[3][0];
|
||||
const float oy = MbMtF[3][1];
|
||||
const float oz = MbMtF[3][2];
|
||||
|
||||
const float X = p.x - ox;
|
||||
const float Y = p.y - oy;
|
||||
const float Z = p.z - oz;
|
||||
|
||||
const float dz = A * sy - Y;
|
||||
const float nsxsz = n * sx * sz;
|
||||
const float j = -(B * sy) / (nsxsz * dz * dz);
|
||||
const mat3f J(mat3f::row_major_init{
|
||||
j * dz * sz, j * X * sz, 0.0f,
|
||||
0.0f, j * nsxsz, 0.0f,
|
||||
0.0f, j * Z * sx, j * dz * sx
|
||||
});
|
||||
|
||||
float3 const Jx = J[0] * ures;
|
||||
float3 const Jy = J[1] * vres;
|
||||
UTILS_UNUSED float3 const Jz = J[2] * dres;
|
||||
const float s = std::max(length(Jx), length(Jy));
|
||||
float3 const p = { 0.0f, 0.0f, 0.0f }; // clip-space
|
||||
float const oneTexel = 2.0f / float(shadowDimension);
|
||||
mat3f const J = jacobian(inverse(S), p);
|
||||
float2 const s = float2{ length(J[0]), length(J[1]) } * oneTexel;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ public:
|
||||
math::mat4f lightSpace{};
|
||||
math::float4 lightFromWorldZ{};
|
||||
math::float4 scissorNormalized{};
|
||||
float texelSizeAtOneMeterWs{};
|
||||
math::float2 texelSizeAtOneMeterWs{};
|
||||
};
|
||||
|
||||
// Call once per frame if the light, scene (or visible layers) or camera changes.
|
||||
@@ -319,11 +319,8 @@ private:
|
||||
|
||||
math::float4 getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const noexcept;
|
||||
|
||||
static float texelSizeWorldSpace(const math::mat3f& worldToShadowTexture,
|
||||
uint16_t shadowDimension) noexcept;
|
||||
|
||||
static float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
|
||||
uint16_t shadowDimension) noexcept;
|
||||
static math::float2 texelSizeWorldSpace(const math::mat3f& clipFromWorld, uint16_t shadowDimension) noexcept;
|
||||
static math::float2 texelSizeWorldSpace(const math::mat4f& clipFromWorld, uint16_t shadowDimension) noexcept;
|
||||
|
||||
static constexpr Segment sBoxSegments[12] = {
|
||||
{ 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 },
|
||||
|
||||
@@ -742,17 +742,16 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
|
||||
// Texel size is constant for directional light (although that's not true when LISPSM
|
||||
// is used, but in that case we're pretending it is).
|
||||
const float wsTexelSize = shaderParameters.texelSizeAtOneMeterWs;
|
||||
float2 const wsTexelSize = shaderParameters.texelSizeAtOneMeterWs;
|
||||
|
||||
auto& s = mShadowUb.edit();
|
||||
s.shadows[shadowIndex].layer = shadowMap.getLayer();
|
||||
s.shadows[shadowIndex].lightFromWorldMatrix = shaderParameters.lightSpace;
|
||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSize;
|
||||
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSize;
|
||||
s.shadows[shadowIndex].normalBias = wsTexelSize * normalBias;
|
||||
s.shadows[shadowIndex].elvsm = options.vsm.elvsm;
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / wsTexelSize;
|
||||
mSoftShadowOptions.penumbraScale * options.shadowBulbRadius / length(wsTexelSize);
|
||||
|
||||
shadowTechnique |= ShadowTechnique::SHADOW_MAP;
|
||||
cascadeHasVisibleShadows |= 0x1u << i;
|
||||
@@ -833,7 +832,7 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
// and if we need to generate it, update all the UBO data
|
||||
if (shadowMap.hasVisibleShadows()) {
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
|
||||
@@ -845,12 +844,11 @@ void ShadowMapManager::prepareSpotShadowMap(ShadowMap& shadowMap, FEngine& engin
|
||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||
/ wsTexelSizeAtOneMeter;
|
||||
/ length(wsTexelSizeAtOneMeter);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -926,7 +924,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
// and if we need to generate it, update all the UBO data
|
||||
if (shadowMap.hasVisibleShadows()) {
|
||||
const size_t shadowIndex = shadowMap.getShadowIndex();
|
||||
const float wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
const float2 wsTexelSizeAtOneMeter = shaderParameters.texelSizeAtOneMeterWs;
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
|
||||
@@ -938,12 +936,11 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
|
||||
s.shadows[shadowIndex].scissorNormalized = shaderParameters.scissorNormalized;
|
||||
s.shadows[shadowIndex].normalBias = normalBias * wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].lightFromWorldZ = shaderParameters.lightFromWorldZ;
|
||||
s.shadows[shadowIndex].texelSizeAtOneMeter = wsTexelSizeAtOneMeter;
|
||||
s.shadows[shadowIndex].nearOverFarMinusNear = float(n / (f - n));
|
||||
s.shadows[shadowIndex].elvsm = options->vsm.elvsm;
|
||||
s.shadows[shadowIndex].bulbRadiusLs =
|
||||
mSoftShadowOptions.penumbraScale * options->shadowBulbRadius
|
||||
/ wsTexelSizeAtOneMeter;
|
||||
/ length(wsTexelSizeAtOneMeter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -257,9 +257,9 @@ filament::DescriptorSetLayout const& FMaterial::getPerViewDescriptorSetLayout(
|
||||
}
|
||||
// mDefinition.perViewDescriptorSetLayout{Vsm} is already resolved for MaterialDomain
|
||||
if (useVsmDescriptorSetLayout) {
|
||||
return mDefinition.perViewDescriptorSetLayoutVsm;
|
||||
return mDefinition.perViewDescriptorSetLayoutS2d;
|
||||
}
|
||||
return mDefinition.perViewDescriptorSetLayout;
|
||||
return mDefinition.perViewDescriptorSetLayoutPcf;
|
||||
}
|
||||
|
||||
void FMaterial::compile(CompilerPriorityQueue const priority,
|
||||
|
||||
@@ -139,7 +139,7 @@ public:
|
||||
// This is mostly intended to be used for post-process materials; but it's also useful for
|
||||
// Surface material that behave like post-process material (i.e. that don't really have variants or for
|
||||
// which VSM is nonsensical, like for unlit materials)
|
||||
return mDefinition.perViewDescriptorSetLayout;
|
||||
return mDefinition.perViewDescriptorSetLayoutPcf;
|
||||
}
|
||||
|
||||
DescriptorSetLayout const& getPerViewDescriptorSetLayout(
|
||||
@@ -378,11 +378,11 @@ private:
|
||||
Variant variant = {}) const noexcept;
|
||||
|
||||
bool isSharedVariant(Variant const variant) const {
|
||||
// HACK: The default material "should" have VSM | DEP, but then we'd have to compile it as a
|
||||
// HACK: The default material "should" have MNT | DEP, but then we'd have to compile it as a
|
||||
// lit material, which would increase binary size. Perhaps we could specially compile it
|
||||
// with this variant, but with the shader program cache in active development, the days of
|
||||
// the default material are numbered anyway.
|
||||
constexpr Variant::type_t vsmAndDep = Variant::VSM | Variant::DEP;
|
||||
constexpr Variant::type_t vsmAndDep = Variant::MNT | Variant::DEP;
|
||||
return mDefinition.materialDomain == MaterialDomain::SURFACE && !mIsDefaultMaterial &&
|
||||
!mDefinition.hasCustomDepthShader && Variant::isValidDepthVariant(variant) &&
|
||||
(variant.key & vsmAndDep) != vsmAndDep;
|
||||
|
||||
@@ -104,6 +104,8 @@ FRenderer::FRenderer(FEngine& engine) :
|
||||
mIsFrameBufferFetchSupported(false),
|
||||
mIsFrameBufferFetchMultiSampleSupported(false),
|
||||
mIsAutoDepthResolveSupported(false),
|
||||
mWorkaroundBlitSsrHistory(
|
||||
engine.getDriverApi().isWorkaroundNeeded(Workaround::BLIT_SSR_HISTORY)),
|
||||
mUserEpoch(engine.getEngineEpoch()),
|
||||
mResourceAllocator(std::make_unique<TextureCache>(
|
||||
engine.getSharedResourceAllocatorDisposer(),
|
||||
@@ -969,10 +971,7 @@ void FRenderer::renderJob(DriverApi& driver, RootArenaScope& rootArenaScope, FVi
|
||||
variant.setDirectionalLighting(view.hasDirectionalLighting());
|
||||
variant.setDynamicLighting(view.hasDynamicLighting());
|
||||
variant.setFog(view.hasFog());
|
||||
// The VSM bit has a different meaning for STANDARD_VARIANT (as opposed to DEPTH_VARIANT),
|
||||
// In the STANDARD_VARIANT case, we are *using* the shadow-map, and the VSM only decides which
|
||||
// type of sampler is used (samplerShadow or sampler2D).
|
||||
variant.setVsm(view.hasShadowing() && view.getShadowType() != ShadowType::PCF);
|
||||
variant.setShadowSampler2D(view.hasShadowing() && view.getShadowType() != ShadowType::PCF);
|
||||
variant.setStereo(view.hasStereo());
|
||||
|
||||
/*
|
||||
@@ -981,10 +980,7 @@ void FRenderer::renderJob(DriverApi& driver, RootArenaScope& rootArenaScope, FVi
|
||||
|
||||
if (view.needsShadowMap()) {
|
||||
Variant shadowVariant(Variant::DEPTH_VARIANT);
|
||||
// The VSM bit has a different meaning for DEPTH_VARIANT (as opposed to STANDARD_VARIANT),
|
||||
// In the DEPTH_VARIANT case, we are *generating* the shadow-map, and some computations
|
||||
// are handled differently. In addition, the color buffer is used.
|
||||
shadowVariant.setVsm(view.getShadowType() == ShadowType::VSM);
|
||||
shadowVariant.setDepthMoments(view.getShadowType() == ShadowType::VSM);
|
||||
|
||||
auto shadows = view.renderShadowMaps(engine, fg, cameraInfo, mShaderUserTime,
|
||||
RenderPassBuilder{ commandArena }
|
||||
@@ -1343,21 +1339,38 @@ void FRenderer::renderJob(DriverApi& driver, RootArenaScope& rootArenaScope, FVi
|
||||
struct ExportSSRHistoryData {
|
||||
FrameGraphId<FrameGraphTexture> history;
|
||||
};
|
||||
// FIXME: should we use the TAA-modified cameraInfo here or not? (we are).
|
||||
mat4 const projection = cameraInfo.projection * cameraInfo.getUserViewMatrix();
|
||||
fg.addPass<ExportSSRHistoryData>("Export SSR history",
|
||||
|
||||
// we can't use colorPassOutput here because it could be tonemapped
|
||||
FrameGraphId<FrameGraphTexture> history = colorPassOutput.linearColor;
|
||||
|
||||
if (mWorkaroundBlitSsrHistory) {
|
||||
history = ppm.blit(fg, false, history,
|
||||
{ 0, 0, colorBufferDesc.width, colorBufferDesc.height },
|
||||
{
|
||||
.width = colorBufferDesc.width,
|
||||
.height = colorBufferDesc.height,
|
||||
.format = config.hdrFormat,
|
||||
},
|
||||
SamplerMagFilter::LINEAR, SamplerMinFilter::LINEAR);
|
||||
}
|
||||
|
||||
fg.addPass<ExportSSRHistoryData>(
|
||||
mWorkaroundBlitSsrHistory ? "Export SSR history (Blit Workaround)"
|
||||
: "Export SSR history",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
// We need to use sideEffect here to ensure this pass won't be culled.
|
||||
// The "output" of this pass is going to be used during the next frame as
|
||||
// an "import".
|
||||
builder.sideEffect();
|
||||
|
||||
// we can't use colorPassOutput here because it could be tonemapped
|
||||
data.history = builder.sample(colorPassOutput.linearColor); // FIXME: an access must be declared for detach(), why?
|
||||
}, [&view, projection](FrameGraphResources const& resources, auto const& data) {
|
||||
data.history = builder.sample(history);
|
||||
},
|
||||
[&view, projection](FrameGraphResources const& resources, auto const& data) {
|
||||
auto& history = view.getFrameHistory();
|
||||
auto& current = history.getCurrent();
|
||||
current.ssr.projection = projection;
|
||||
|
||||
// FIXME: an access must be declared for detach(), why?
|
||||
resources.detach(data.history, ¤t.ssr.color, ¤t.ssr.desc);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -219,6 +219,7 @@ private:
|
||||
bool mIsFrameBufferFetchSupported : 1;
|
||||
bool mIsFrameBufferFetchMultiSampleSupported : 1;
|
||||
bool mIsAutoDepthResolveSupported : 1;
|
||||
bool const mWorkaroundBlitSsrHistory : 1;
|
||||
Epoch mUserEpoch;
|
||||
math::float4 mShaderUserTime{};
|
||||
DisplayInfo mDisplayInfo;
|
||||
|
||||
@@ -143,7 +143,7 @@ FView::FView(FEngine& engine)
|
||||
#ifndef NDEBUG
|
||||
// This can fail if another view has already registered this data source
|
||||
mDebugState->owner = debugRegistry.registerDataSource("d.view.frame_info",
|
||||
[weak = std::weak_ptr(mDebugState)]() -> DebugRegistry::DataSource {
|
||||
[weak = std::weak_ptr<DebugState>(mDebugState)]() -> DebugRegistry::DataSource {
|
||||
// the View could have been destroyed by the time we do this
|
||||
auto const state = weak.lock();
|
||||
if (!state) {
|
||||
|
||||
@@ -36,7 +36,7 @@ backend::DescriptorSetLayout const& getPerRenderableLayout() noexcept;
|
||||
backend::DescriptorSetLayout getPerViewDescriptorSetLayout(
|
||||
MaterialDomain domain,
|
||||
bool isLit, bool isSSR, bool hasFog,
|
||||
bool isVSM) noexcept;
|
||||
bool isShadowSampler2D) noexcept;
|
||||
|
||||
backend::DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant(
|
||||
Variant variant,
|
||||
|
||||
@@ -299,10 +299,9 @@ struct ShadowUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
math::mat4f lightFromWorldMatrix; // 64
|
||||
math::float4 lightFromWorldZ; // 16
|
||||
math::float4 scissorNormalized; // 16
|
||||
float texelSizeAtOneMeter; // 4
|
||||
float bulbRadiusLs; // 4
|
||||
float nearOverFarMinusNear; // 4
|
||||
float normalBias; // 4
|
||||
math::float2 normalBias; // 4
|
||||
bool elvsm; // 4
|
||||
uint32_t layer; // 4
|
||||
uint32_t reserved1; // 4
|
||||
|
||||
@@ -48,20 +48,21 @@ struct Variant {
|
||||
// SRE: Shadow Receiver
|
||||
// SKN: Skinning
|
||||
// DEP: Depth only
|
||||
// FOG: Fog
|
||||
// PCK: Picking (depth variant only)
|
||||
// VSM: Variance shadow maps (depth) / sampler type (standard)
|
||||
// FOG: Fog (standard)
|
||||
// PCK: Picking (depth)
|
||||
// MNT: Output depth moments (depth)
|
||||
// S2D: Sampler type for shadows (0: samplerShadowArray, 1: sampler2DArray) (standard)
|
||||
// STE: Instanced stereo rendering
|
||||
//
|
||||
// X: either 1 or 0
|
||||
// +-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// Variant | STE | VSM | FOG | DEP | SKN | SRE | DYN | DIR | 256
|
||||
// Variant | STE | S2D | FOG | DEP | SKN | SRE | DYN | DIR | 256
|
||||
// +-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// PCK
|
||||
// MNT PCK
|
||||
//
|
||||
// Standard variants:
|
||||
// +-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// | STE | VSM | FOG | 0 | SKN | SRE | DYN | DIR | 128 - 44 = 84
|
||||
// | STE | S2D | FOG | 0 | SKN | SRE | DYN | DIR | 128 - 44 = 84
|
||||
// +-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// Vertex shader X 0 0 0 X X X X
|
||||
// Fragment shader 0 X X 0 0 X X X
|
||||
@@ -72,7 +73,7 @@ struct Variant {
|
||||
//
|
||||
// Depth variants:
|
||||
// +-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// | STE | VSM | PCK | 1 | SKN | 0 | 0 | 0 | 16 - 4 = 12
|
||||
// | STE | MNT | PCK | 1 | SKN | 0 | 0 | 0 | 16 - 4 = 12
|
||||
// +-----+-----+-----+-----+-----+-----+-----+-----+
|
||||
// Vertex depth X X 0 1 X 0 0 0
|
||||
// Fragment depth 0 0 X 1 0 0 0 0
|
||||
@@ -96,13 +97,15 @@ struct Variant {
|
||||
static constexpr type_t DEP = 0x10; // depth only variants
|
||||
static constexpr type_t FOG = 0x20; // fog (standard)
|
||||
static constexpr type_t PCK = 0x20; // picking (depth)
|
||||
static constexpr type_t VSM = 0x40; // variance shadow maps / sampler type
|
||||
static constexpr type_t S2D = 0x40; // sampler type
|
||||
static constexpr type_t MNT = 0x40; // variance shadow maps
|
||||
static constexpr type_t STE = 0x80; // instanced stereo
|
||||
|
||||
static constexpr type_t NO_VARIANT = 0u;
|
||||
|
||||
// special variants (variants that use the reserved space)
|
||||
static constexpr type_t SPECIAL_SSR = VSM | SRE; // screen-space reflections variant
|
||||
static constexpr type_t SPECIAL_SSR_VARIANT= S2D | SRE ;
|
||||
static constexpr type_t SPECIAL_SSR_MASK = STE | S2D | DEP | SRE | DYN | DIR;
|
||||
|
||||
static constexpr type_t STANDARD_MASK = DEP;
|
||||
static constexpr type_t STANDARD_VARIANT = 0u;
|
||||
@@ -127,29 +130,30 @@ struct Variant {
|
||||
void setSkinning(bool v) noexcept { set(v, SKN); }
|
||||
void setFog(bool v) noexcept { set(v, FOG); }
|
||||
void setPicking(bool v) noexcept { set(v, PCK); }
|
||||
void setVsm(bool v) noexcept { set(v, VSM); }
|
||||
void setShadowSampler2D(bool v) noexcept { set(v, S2D); }
|
||||
void setDepthMoments(bool v) noexcept { set(v, MNT); }
|
||||
void setStereo(bool v) noexcept { set(v, STE); }
|
||||
|
||||
static constexpr bool isValidDepthVariant(Variant variant) noexcept {
|
||||
// Can't have VSM and PICKING together with DEPTH variants
|
||||
constexpr type_t RESERVED_MASK = VSM | PCK | DEP | SRE | DYN | DIR;
|
||||
constexpr type_t RESERVED_VALUE = VSM | PCK | DEP;
|
||||
constexpr type_t RESERVED_MASK = MNT | PCK | DEP | SRE | DYN | DIR;
|
||||
constexpr type_t RESERVED_VALUE = MNT | PCK | DEP;
|
||||
return ((variant.key & DEPTH_MASK) == DEPTH_VARIANT) &&
|
||||
((variant.key & RESERVED_MASK) != RESERVED_VALUE);
|
||||
}
|
||||
|
||||
static constexpr bool isValidStandardVariant(Variant variant) noexcept {
|
||||
// can't have shadow receiver if we don't have any lighting
|
||||
constexpr type_t RESERVED0_MASK = VSM | FOG | SRE | DYN | DIR;
|
||||
constexpr type_t RESERVED0_VALUE = VSM | FOG | SRE;
|
||||
constexpr type_t RESERVED0_MASK = S2D | FOG | SRE | DYN | DIR;
|
||||
constexpr type_t RESERVED0_VALUE = S2D | FOG | SRE;
|
||||
|
||||
// can't have shadow receiver if we don't have any lighting
|
||||
constexpr type_t RESERVED1_MASK = VSM | SRE | DYN | DIR;
|
||||
constexpr type_t RESERVED1_MASK = S2D | SRE | DYN | DIR;
|
||||
constexpr type_t RESERVED1_VALUE = SRE;
|
||||
|
||||
// can't have VSM without shadow receiver
|
||||
constexpr type_t RESERVED2_MASK = VSM | SRE;
|
||||
constexpr type_t RESERVED2_VALUE = VSM;
|
||||
constexpr type_t RESERVED2_MASK = S2D | SRE;
|
||||
constexpr type_t RESERVED2_VALUE = S2D;
|
||||
|
||||
return ((variant.key & STANDARD_MASK) == STANDARD_VARIANT) &&
|
||||
((variant.key & RESERVED0_MASK) != RESERVED0_VALUE) &&
|
||||
@@ -174,11 +178,15 @@ struct Variant {
|
||||
}
|
||||
|
||||
static constexpr bool isSSRVariant(Variant variant) noexcept {
|
||||
return (variant.key & (STE | VSM | DEP | SRE | DYN | DIR)) == (VSM | SRE);
|
||||
return (variant.key & SPECIAL_SSR_MASK) == SPECIAL_SSR_VARIANT;
|
||||
}
|
||||
|
||||
static constexpr bool isVSMVariant(Variant variant) noexcept {
|
||||
return !isSSRVariant(variant) && ((variant.key & VSM) == VSM);
|
||||
static constexpr bool isShadowSampler2DVariant(Variant variant) noexcept {
|
||||
return !isSSRVariant(variant) && ((variant.key & (S2D | DEP)) == S2D);
|
||||
}
|
||||
|
||||
static constexpr bool isDepthMomentsVariant(Variant variant) noexcept {
|
||||
return !isSSRVariant(variant) && ((variant.key & (MNT | DEP)) == (MNT | DEP));
|
||||
}
|
||||
|
||||
static constexpr bool isShadowReceiverVariant(Variant variant) noexcept {
|
||||
@@ -202,13 +210,13 @@ struct Variant {
|
||||
// vertex shader.
|
||||
if ((variant.key & STANDARD_MASK) == STANDARD_VARIANT) {
|
||||
if (isSSRVariant(variant)) {
|
||||
variant.key &= ~(VSM | SRE);
|
||||
variant.key &= ~SPECIAL_SSR_VARIANT;
|
||||
}
|
||||
return variant & (STE | SKN | SRE | DYN | DIR);
|
||||
}
|
||||
if ((variant.key & DEPTH_MASK) == DEPTH_VARIANT) {
|
||||
// Only VSM, skinning, and stereo affect the vertex shader's DEPTH variant
|
||||
return variant & (STE | VSM | SKN | DEP);
|
||||
// Only MNT, skinning, and stereo affect the vertex shader's DEPTH variant
|
||||
return variant & (STE | MNT | SKN | DEP);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -217,11 +225,11 @@ struct Variant {
|
||||
// filter out fragment variants that are not needed. For e.g. skinning doesn't
|
||||
// affect the fragment shader.
|
||||
if ((variant.key & STANDARD_MASK) == STANDARD_VARIANT) {
|
||||
return variant & (VSM | FOG | SRE | DYN | DIR);
|
||||
return variant & (S2D | FOG | SRE | DYN | DIR);
|
||||
}
|
||||
if ((variant.key & DEPTH_MASK) == DEPTH_VARIANT) {
|
||||
// Only VSM & PICKING affects the fragment shader's DEPTH variant
|
||||
return variant & (VSM | PCK | DEP);
|
||||
return variant & (MNT | PCK | DEP);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -230,8 +238,8 @@ struct Variant {
|
||||
// special case for depth variant
|
||||
if (isValidDepthVariant(variant)) {
|
||||
if (!isLit) {
|
||||
// if we're unlit, we never need the VSM variant
|
||||
return variant & ~VSM;
|
||||
// if we're unlit, we never need the MNT variant
|
||||
return variant & ~MNT;
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
@@ -242,9 +250,9 @@ struct Variant {
|
||||
// when the shading mode is unlit, remove all the lighting variants
|
||||
return variant & UNLIT_MASK;
|
||||
}
|
||||
// if shadow receiver is disabled, turn off VSM
|
||||
// if shadow receiver is disabled, we pick the shadow sampler
|
||||
if (!(variant.key & SRE)) {
|
||||
return variant & ~VSM;
|
||||
return variant & ~S2D;
|
||||
}
|
||||
return variant;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ utils::CString getDescriptorName(DescriptorSetBindingPoints const set,
|
||||
DescriptorSetLayout getPerViewDescriptorSetLayout(
|
||||
MaterialDomain const domain,
|
||||
bool const isLit, bool const isSSR, bool const hasFog,
|
||||
bool const isVSM) noexcept {
|
||||
bool const isShadowSampler2D) noexcept {
|
||||
|
||||
switch (domain) {
|
||||
case MaterialDomain::SURFACE: {
|
||||
@@ -255,7 +255,7 @@ DescriptorSetLayout getPerViewDescriptorSetLayout(
|
||||
}
|
||||
|
||||
// change the SHADOW_MAP descriptor type for VSM
|
||||
if (isVSM) {
|
||||
if (isShadowSampler2D) {
|
||||
auto const pos = std::find_if(layout.descriptors.begin(), layout.descriptors.end(),
|
||||
[](auto const& v) {
|
||||
return v.binding == PerViewBindingPoints::SHADOW_MAP;
|
||||
@@ -285,8 +285,7 @@ DescriptorSetLayout getPerViewDescriptorSetLayoutWithVariant(
|
||||
return ssrVariantDescriptorSetLayout;
|
||||
}
|
||||
// We need to filter out all the descriptors not included in the "resolved" layout below
|
||||
return getPerViewDescriptorSetLayout(domain, isLit, isSSR, hasFog,
|
||||
Variant::isVSMVariant(variant));
|
||||
return getPerViewDescriptorSetLayout(domain, isLit, isSSR, hasFog, Variant::isShadowSampler2DVariant(variant));
|
||||
}
|
||||
|
||||
DescriptorType getDescriptorType(SamplerType const type, SamplerFormat const format) {
|
||||
|
||||
@@ -32,41 +32,46 @@ namespace filament {
|
||||
Variant Variant::filterUserVariant(
|
||||
Variant variant, UserVariantFilterMask filterMask) noexcept {
|
||||
// these are easy to filter by just removing the corresponding bit
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::DIRECTIONAL_LIGHTING) {
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::DIRECTIONAL_LIGHTING)) {
|
||||
variant.key &= ~DIR;
|
||||
}
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::DYNAMIC_LIGHTING) {
|
||||
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::DYNAMIC_LIGHTING)) {
|
||||
variant.key &= ~DYN;
|
||||
}
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::SKINNING) {
|
||||
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::SKINNING)) {
|
||||
variant.key &= ~SKN;
|
||||
}
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::STE) {
|
||||
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::STE)) {
|
||||
variant.key &= ~(filterMask & STE);
|
||||
}
|
||||
if (!isValidDepthVariant(variant)) {
|
||||
// we can't remove FOG from depth variants, this would, in fact, remove picking
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::FOG) {
|
||||
variant.key &= ~FOG;
|
||||
|
||||
if (isValidDepthVariant(variant)) {
|
||||
// depth variants can have their MNT bit filtered
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::VSM)) {
|
||||
variant.key &= ~MNT;
|
||||
}
|
||||
} else {
|
||||
// depth variants can have their VSM bit filtered
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::VSM) {
|
||||
variant.key &= ~VSM;
|
||||
// we can't remove FOG from depth variants, this would, in fact, remove picking
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::FOG)) {
|
||||
variant.key &= ~FOG;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSSRVariant(variant)) {
|
||||
// SSR variant needs to be handled separately
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::SHADOW_RECEIVER) {
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::SHADOW_RECEIVER)) {
|
||||
variant.key &= ~SRE;
|
||||
}
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::VSM) {
|
||||
variant.key &= ~VSM;
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::VSM)) {
|
||||
variant.key &= ~S2D;
|
||||
}
|
||||
} else {
|
||||
// see if we need to filter out the SSR variants
|
||||
if (filterMask & (uint32_t)UserVariantFilterBit::SSR) {
|
||||
variant.key &= ~SPECIAL_SSR;
|
||||
if (filterMask & uint32_t(UserVariantFilterBit::SSR)) {
|
||||
variant.key &= ~SPECIAL_SSR_VARIANT;
|
||||
}
|
||||
}
|
||||
return variant;
|
||||
|
||||
@@ -60,7 +60,8 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(io::sstream& out,
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_SHADOWING",
|
||||
litVariants && filament::Variant::isShadowReceiverVariant(variant));
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_VSM",
|
||||
filament::Variant::isVSMVariant(variant));
|
||||
filament::Variant::isShadowSampler2DVariant(variant) ||
|
||||
filament::Variant::isDepthMomentsVariant(variant));
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_STEREO",
|
||||
hasStereo(variant, featureLevel));
|
||||
CodeGenerator::generateDefine(out, "VARIANT_DEPTH",
|
||||
|
||||
@@ -49,7 +49,7 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce
|
||||
// reason we name them "unused*" to ensure we're not using them by mistake (type/format don't
|
||||
// matter).
|
||||
|
||||
static SamplerInterfaceBlock const sibPcf{ SamplerInterfaceBlock::Builder()
|
||||
static SamplerInterfaceBlock const sibShadowSamplerPcf{ SamplerInterfaceBlock::Builder()
|
||||
.name("sampler0")
|
||||
.stageFlags(backend::ShaderStageFlags::FRAGMENT)
|
||||
.add( {{ "shadowMap", +PerViewBindingPoints::SHADOW_MAP, Type::SAMPLER_2D_ARRAY, Format::SHADOW, Precision::MEDIUM, FILTERABLE, !MULTISAMPLE, ALL_STAGES },
|
||||
@@ -62,7 +62,7 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce
|
||||
)
|
||||
.build() };
|
||||
|
||||
static SamplerInterfaceBlock const sibVsm{ SamplerInterfaceBlock::Builder()
|
||||
static SamplerInterfaceBlock const sibShadowSampler2D{ SamplerInterfaceBlock::Builder()
|
||||
.name("sampler0")
|
||||
.stageFlags(backend::ShaderStageFlags::FRAGMENT)
|
||||
.add( {{ "shadowMap", +PerViewBindingPoints::SHADOW_MAP, Type::SAMPLER_2D_ARRAY, Format::FLOAT, Precision::HIGH, FILTERABLE, !MULTISAMPLE, ALL_STAGES },
|
||||
@@ -85,10 +85,10 @@ SamplerInterfaceBlock const& SibGenerator::getPerViewSib(Variant variant) noexce
|
||||
|
||||
if (Variant::isSSRVariant(variant)) {
|
||||
return sibSsr;
|
||||
} else if (Variant::isVSMVariant(variant)) {
|
||||
return sibVsm;
|
||||
} else if (Variant::isShadowSampler2DVariant(variant)) {
|
||||
return sibShadowSampler2D;
|
||||
} else {
|
||||
return sibPcf;
|
||||
return sibShadowSamplerPcf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,13 @@
|
||||
#define GLTFIO_WARN(msg) slog.w << msg << io::endl
|
||||
#endif
|
||||
|
||||
#ifndef GLTFIO_USE_FILESYSTEM
|
||||
#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) || defined(FILAMENT_IOS)
|
||||
#define GLTFIO_USE_FILESYSTEM 0
|
||||
#else
|
||||
#define GLTFIO_USE_FILESYSTEM 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace utils {
|
||||
class NameComponentManager;
|
||||
|
||||
@@ -48,10 +48,11 @@ std::string formatVariantString(Variant variant, MaterialDomain domain) noexcept
|
||||
if (variant.key & Variant::DEP) variantString += "DEP|";
|
||||
if (variant.key & Variant::DEP) {
|
||||
if (variant.key & Variant::PCK) variantString += "PCK|";
|
||||
if (variant.key & Variant::MNT) variantString += "MNT|";
|
||||
} else {
|
||||
if (variant.key & Variant::FOG) variantString += "FOG|";
|
||||
if (variant.key & Variant::S2D) variantString += "S2D|";
|
||||
}
|
||||
if (variant.key & Variant::VSM) variantString += "VSM|";
|
||||
if (variant.key & Variant::STE) variantString += "STE|";
|
||||
variantString = variantString.substr(0, variantString.length() - 1);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
// TODO: Clean-up. We shouldn't need this #ifndef here, but a client has requested that perfetto be
|
||||
// disabled due to size increase. In their case, this flag would be defined across targets. Hence
|
||||
// we guard below with an #ifndef.
|
||||
#include <cstring>
|
||||
#ifndef FILAMENT_TRACING_ENABLED
|
||||
// Note: The overhead of TRACING is not negligible especially with parallel_for().
|
||||
#define FILAMENT_TRACING_ENABLED false
|
||||
@@ -98,7 +99,11 @@ namespace utils {
|
||||
|
||||
void JobSystem::setThreadName(const char* name) noexcept {
|
||||
#if defined(__linux__)
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
constexpr size_t MAX_PTHREAD_NAME_LEN = 16;
|
||||
char buf[MAX_PTHREAD_NAME_LEN];
|
||||
strncpy(buf, name, MAX_PTHREAD_NAME_LEN - 1);
|
||||
buf[MAX_PTHREAD_NAME_LEN - 1] = '\0';
|
||||
pthread_setname_np(pthread_self(), buf);
|
||||
#elif defined(__APPLE__)
|
||||
pthread_setname_np(name);
|
||||
#elif defined(WIN32)
|
||||
|
||||
@@ -111,7 +111,7 @@ highp vec4 getSpotLightSpacePosition(int index, highp vec3 dir, highp float zLig
|
||||
highp mat4 lightFromWorldMatrix = shadowUniforms.shadows[index].lightFromWorldMatrix;
|
||||
|
||||
// for spotlights, the bias depends on z
|
||||
float bias = shadowUniforms.shadows[index].normalBias * zLight;
|
||||
highp vec2 bias = shadowUniforms.shadows[index].normalBias * zLight;
|
||||
|
||||
return computeLightSpacePosition(getWorldPosition(), getWorldGeometricNormalVector(),
|
||||
dir, bias, lightFromWorldMatrix);
|
||||
|
||||
@@ -12,12 +12,51 @@
|
||||
*/
|
||||
|
||||
highp vec4 computeLightSpacePosition(highp vec3 p, const highp vec3 n,
|
||||
const highp vec3 dir, const float b, highp_mat4 lightFromWorldMatrix) {
|
||||
const highp vec3 dir, const highp vec2 b, highp_mat4 lightFromWorldMatrix) {
|
||||
|
||||
#if !defined(VARIANT_HAS_VSM)
|
||||
highp float cosTheta = saturate(dot(n, dir));
|
||||
highp float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
|
||||
p += n * (sinTheta * b);
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Anisotropic Normal Bias for Shadow Mapping
|
||||
// --------------------------------------------------------------------------------------
|
||||
// To prevent shadow acne, we must push the geometry along its normal to clear the
|
||||
// quantization steps of the shadow map's discrete depth grid. The exact physical depth
|
||||
// error we must clear is proportional to the shadow texel's world-space dimensions.
|
||||
//
|
||||
// This implementation computes the exact geometric projection of the rectangular
|
||||
// shadow map texel onto the surface normal.
|
||||
//
|
||||
// 1. Coordinate Space Transition:
|
||||
// We project the world-space normal onto the light's X and Y basis vectors (L_right,
|
||||
// L_up). This gives us the lateral components of the normal in Light Space (n_Lx, n_Ly).
|
||||
//
|
||||
// 2. The Implicit sin(theta) Slope Scale:
|
||||
// Because the normal is a unit vector, the magnitude of its lateral components in
|
||||
// light space inherently equals sin(theta), where theta is the angle of incidence.
|
||||
// This perfectly and automatically scales the bias from 0.0 (top-down, flat surface)
|
||||
// to maximum (grazing angle).
|
||||
//
|
||||
// 3. Exact Anisotropic Footprint (The L1 Norm):
|
||||
// Shadow texels are rarely perfectly square due to Cascaded Shadow Maps (CSM) or
|
||||
// Light Space Perspective Shadow Maps (LiSPSM). Jx and Jy are the physical world-space
|
||||
// dimensions of the texel.
|
||||
// By evaluating `abs(n_Lx * Jx) + abs(n_Ly * Jy)`, we compute the exact scalar
|
||||
// projection of the rectangular texel footprint.
|
||||
// - It is superior to `max(Jx, Jy)` which assumes a massive square and causes Peter Panning.
|
||||
// - It is superior to `length()` which assumes an ellipse and under-biases the corners.
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
// Extract the first row (Light's Right vector in World Space)
|
||||
highp vec3 L_right = vec3(lightFromWorldMatrix[0][0], lightFromWorldMatrix[1][0], lightFromWorldMatrix[2][0]);
|
||||
|
||||
// Extract the second row (Light's Up vector in World Space)
|
||||
highp vec3 L_up = vec3(lightFromWorldMatrix[0][1], lightFromWorldMatrix[1][1], lightFromWorldMatrix[2][1]);
|
||||
|
||||
// Project the world normal onto the shadow map's 2D grid
|
||||
highp float n_Lx = dot(n, L_right);
|
||||
highp float n_Ly = dot(n, L_up);
|
||||
|
||||
// Apply the anisotropic normal bias
|
||||
p += n * (abs(n_Lx * b.x) + abs(n_Ly * b.y));
|
||||
#endif
|
||||
|
||||
return mulMat4x4Float3(lightFromWorldMatrix, p);
|
||||
|
||||
@@ -5,10 +5,9 @@ struct ShadowData {
|
||||
highp mat4 lightFromWorldMatrix;
|
||||
highp vec4 lightFromWorldZ;
|
||||
highp vec4 scissorNormalized;
|
||||
mediump float texelSizeAtOneMeter;
|
||||
mediump float bulbRadiusLs;
|
||||
mediump float nearOverFarMinusNear;
|
||||
mediump float normalBias;
|
||||
highp vec2 normalBias;
|
||||
bool elvsm;
|
||||
mediump uint layer;
|
||||
mediump uint reserved1;
|
||||
|
||||
@@ -76,10 +76,11 @@ into **branch** of `filament-assets`. This branch is paired with a PR or commit
|
||||
|
||||
As an example, imagine I am working on a PR, and I've uploaded my change, which is in a
|
||||
branch called `my-pr-branch`, to `filament`. This PR requires updating the golden. We would do
|
||||
it in the following fashion
|
||||
it in the following fashion on a macOS machine:
|
||||
|
||||
### Using a script to update the golden repo
|
||||
|
||||
- Make sure you've completed the steps in 'Setting up python'
|
||||
- Run interactive mode in the `update_golden.py` script.
|
||||
```
|
||||
python3 test/renderdiff/src/update_golden.py
|
||||
@@ -114,6 +115,24 @@ Doing the above has multiple effects:
|
||||
- If the PR is merged, then there is another workflow that will merge `my-pr-branch-golden`
|
||||
to the `main` branch of the golden repo.
|
||||
|
||||
### Automated update via commit message
|
||||
|
||||
Alternatively, if you are confident in your changes and want the CI to handle the update
|
||||
for you, you can use the following tag in your commit message:
|
||||
|
||||
- In the commit message of your working branch on `filament`, add the following line:
|
||||
```
|
||||
RDIFF_ACCEPT_NEW_GOLDENS
|
||||
```
|
||||
|
||||
This has the following effects:
|
||||
- The presubmit test `test-renderdiff` will be bypassed (it will not perform rendering or
|
||||
comparison).
|
||||
- When the PR is merged, the postsubmit CI will automatically:
|
||||
1. Build Filament and generate the new images.
|
||||
2. Upload them to a temporary branch in `filament-assets`.
|
||||
3. Merge that branch into `main`.
|
||||
|
||||
## Viewing test results
|
||||
|
||||
We provide a viewer for looking at the result of a test run. The viewer is a webapp that can
|
||||
|
||||
@@ -18,6 +18,7 @@ import re
|
||||
from utils import execute, ArgParseImpl
|
||||
|
||||
RDIFF_UPDATE_GOLDEN_STR = 'RDIFF_BRANCH'
|
||||
RDIFF_ACCEPT_NEW_GOLDENS_STR = 'RDIFF_ACCEPT_NEW_GOLDENS'
|
||||
|
||||
def _parse_commit(commit_str):
|
||||
lines = commit_str.split('\n')
|
||||
@@ -42,9 +43,11 @@ def _parse_commit(commit_str):
|
||||
|
||||
if __name__ == "__main__":
|
||||
RE_STR = rf"{RDIFF_UPDATE_GOLDEN_STR}(?:\s)?\=(?:\s)?([a-zA-Z0-9\s\-\/]+)"
|
||||
RE_ACCEPT = rf"^{RDIFF_ACCEPT_NEW_GOLDENS_STR}$"
|
||||
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--file', help='A file containing the commit message')
|
||||
parser.add_argument('--mode', choices=['branch', 'accept_new_goldens'], default='branch', help='Mode of operation')
|
||||
args, _ = parser.parse_known_args(sys.argv[1:])
|
||||
|
||||
if not args.file:
|
||||
@@ -53,14 +56,21 @@ if __name__ == "__main__":
|
||||
with open(args.file, 'r') as f:
|
||||
msg = f.read()
|
||||
|
||||
to_update = []
|
||||
commit, title, description = _parse_commit(msg)
|
||||
|
||||
if args.mode == 'accept_new_goldens':
|
||||
for line in description:
|
||||
if re.match(RE_ACCEPT, line):
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
|
||||
# args.mode == 'branch'
|
||||
for line in description:
|
||||
m = re.match(RE_STR, line)
|
||||
if not m:
|
||||
continue
|
||||
print(m.group(1))
|
||||
exit(0)
|
||||
sys.exit(0)
|
||||
|
||||
# Always default to the main branch
|
||||
print('main')
|
||||
|
||||
@@ -16,6 +16,7 @@ import os
|
||||
import shutil
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from utils import execute, ArgParseImpl, mkdir_p
|
||||
|
||||
@@ -99,10 +100,11 @@ class GoldenManager:
|
||||
code, old_commit = execute(f'git log --format=%B -n 1', cwd=assets_dir)
|
||||
if tag and len(tag) > 0:
|
||||
old_commit += f'\nFILAMENT={tag}'
|
||||
COMMIT_FILE = '/tmp/golden_commit.txt'
|
||||
with open(COMMIT_FILE, 'w') as f:
|
||||
with tempfile.NamedTemporaryFile('w', delete=False) as f:
|
||||
f.write(old_commit)
|
||||
self._git_exec(f'commit --amend -F {COMMIT_FILE}')
|
||||
commit_file = f.name
|
||||
self._git_exec(f'commit --amend -F {commit_file}')
|
||||
os.remove(commit_file)
|
||||
|
||||
# Do the actual merge
|
||||
self._git_exec(f'checkout main')
|
||||
@@ -139,11 +141,11 @@ class GoldenManager:
|
||||
os.path.join(rdiff_dir, f))
|
||||
self._git_exec(f'add {os.path.join(GOLDENS_DIR, f)}')
|
||||
|
||||
TMP_GOLDEN_COMMIT_FILE = '/tmp/golden_commit.txt'
|
||||
|
||||
with open(TMP_GOLDEN_COMMIT_FILE, 'w') as f:
|
||||
with tempfile.NamedTemporaryFile('w', delete=False) as f:
|
||||
f.write(commit_msg)
|
||||
self._git_exec(f'commit -a -F {TMP_GOLDEN_COMMIT_FILE}')
|
||||
tmp_golden_commit_file = f.name
|
||||
self._git_exec(f'commit -a -F {tmp_golden_commit_file}')
|
||||
os.remove(tmp_golden_commit_file)
|
||||
if push_to_remote and \
|
||||
(self.access_token_ or self.access_type_ == ACCESS_TYPE_SSH):
|
||||
self._git_exec(f'push -f origin {branch}')
|
||||
|
||||
@@ -64,19 +64,19 @@ def _do_update(golden_manager, config):
|
||||
|
||||
def _same_image_diffimg(diffimg_path, img1, img2):
|
||||
cmd = [diffimg_path, img1, img2]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
output = result.stdout.strip()
|
||||
if not output:
|
||||
return False
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"diffimg failed with return code {result.returncode}:\n{result.stderr}")
|
||||
|
||||
try:
|
||||
res_json = json.loads(output)
|
||||
return res_json.get('passed', False)
|
||||
except json.JSONDecodeError:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
output = result.stdout.strip()
|
||||
if not output:
|
||||
raise RuntimeError("diffimg produced no output")
|
||||
|
||||
try:
|
||||
res_json = json.loads(output)
|
||||
return res_json.get('passed', False)
|
||||
except json.JSONDecodeError as e:
|
||||
raise RuntimeError(f"Failed to parse diffimg output: {e}\nOutput: {output}")
|
||||
|
||||
def _get_deletes_updates(update_dir, golden_dir, diffimg_path):
|
||||
ret_delete = []
|
||||
@@ -93,13 +93,13 @@ def _get_deletes_updates(update_dir, golden_dir, diffimg_path):
|
||||
# However, update_golden typically only updates/adds based on the new render set.
|
||||
# But strict sync might imply deleting missing ones.
|
||||
# The original logic was: delete = list(base - new).
|
||||
delete = list(base - new)
|
||||
delete_files = list(base_files - new_files)
|
||||
|
||||
# Files in new but not in base are definitely updates (additions)
|
||||
update = list(new - base)
|
||||
update_files = list(new_files - base_files)
|
||||
|
||||
# Files in both need comparison
|
||||
for fpath in base.intersection(new_files):
|
||||
for fpath in base_files.intersection(new_files):
|
||||
base_fpath = os.path.join(golden_dir, fpath)
|
||||
new_fpath = os.path.join(update_dir, fpath)
|
||||
|
||||
@@ -110,10 +110,10 @@ def _get_deletes_updates(update_dir, golden_dir, diffimg_path):
|
||||
is_different = _file_as_str(new_fpath) != _file_as_str(base_fpath)
|
||||
|
||||
if is_different:
|
||||
update.append(fpath)
|
||||
ret_update.append(fpath)
|
||||
|
||||
ret_update += update
|
||||
ret_delete += delete
|
||||
ret_update += update_files
|
||||
ret_delete += delete_files
|
||||
|
||||
return ret_delete, ret_update
|
||||
|
||||
@@ -179,7 +179,7 @@ if __name__ == "__main__":
|
||||
parser = ArgParseImpl()
|
||||
parser.add_argument('--branch', help='Branch of the golden repo to write to')
|
||||
parser.add_argument('--golden-repo-token', help='Access token for the golden repo')
|
||||
parser.add_argument('--push-to-remote', action="store_true", help='Access token for the golden repo')
|
||||
parser.add_argument('--push-to-remote', action="store_true", help='Push the golden repo changes to remote')
|
||||
parser.add_argument('--diffimg', help='Path to the diffimg tool',
|
||||
default='./out/cmake-release/tools/diffimg/diffimg')
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import shutil
|
||||
import argparse
|
||||
import sys
|
||||
import pathlib
|
||||
import shlex
|
||||
|
||||
def execute(cmd,
|
||||
cwd=None,
|
||||
@@ -25,7 +26,7 @@ def execute(cmd,
|
||||
stdin=None,
|
||||
env=None,
|
||||
raise_errors=False):
|
||||
in_env = os.environ
|
||||
in_env = os.environ.copy()
|
||||
in_env.update(env if env else {})
|
||||
home = os.environ['HOME']
|
||||
if f'{home}/bin' not in in_env['PATH']:
|
||||
@@ -45,11 +46,11 @@ def execute(cmd,
|
||||
'universal_newlines': True
|
||||
}
|
||||
if capture_output:
|
||||
process = subprocess.Popen(cmd.split(' '), **kwargs)
|
||||
process = subprocess.Popen(shlex.split(cmd), **kwargs)
|
||||
output, err_output = process.communicate()
|
||||
return_code = process.returncode
|
||||
else:
|
||||
return_code = subprocess.call(cmd.split(' '), **kwargs)
|
||||
return_code = subprocess.call(shlex.split(cmd), **kwargs)
|
||||
|
||||
if return_code:
|
||||
# Error
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
{
|
||||
"name": "Transimssion",
|
||||
"description": "transmission",
|
||||
// Disable transmission for webgpu because it seems flaky (b/488070152)
|
||||
"backends": ["opengl", "vulkan"],
|
||||
"apply_presets": ["base", "transmission_models"],
|
||||
"rendering": {
|
||||
"camera.focalLength": 52.0
|
||||
|
||||
Reference in New Issue
Block a user