Compare commits

...

5 Commits

Author SHA1 Message Date
Eliza Velasquez
ea9fdbeb5c wip: shader compilation benchmark 2024-01-16 17:03:41 -08:00
Ben Doherty
5fd7a4e153 Don't render in background (#7486) 2024-01-12 10:17:00 -08:00
Sungun Park
20ff230b92 Fix a json parsing bug (#7490)
When parsing a lexeme, we use one less byte than it's intended to be for
comparing the current string.

This results in a success in cases like:
- true and truX
- false and falsX
- null and nulX
where X means an arbitrary character.

Fix this by the full intended length.
2024-01-11 12:47:11 -08:00
Ben Doherty
44ff79ad34 Metal: disable fast math (#7485) 2024-01-10 15:31:23 -08:00
Mathias Agopian
102d2db008 Bokeh aspect ratio (#7482)
* Bokeh aspect ratio

new DoF option to set the bokeh aspect ratio, this can be used to
simulate anamorphic lenses

* Update android/filament-android/src/main/java/com/google/android/filament/View.java

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

* Update web/filament-js/filament.d.ts

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

---------

Co-authored-by: Powei Feng <powei@google.com>
2024-01-10 15:25:28 -08:00
17 changed files with 229 additions and 14 deletions

View File

@@ -7,3 +7,5 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- Metal: fix some shader artifacts by disabling fast math optimizations.

View File

@@ -1637,6 +1637,10 @@ public class View {
* circle of confusion scale factor (amount of blur)
*/
public float cocScale = 1.0f;
/**
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
*/
public float cocAspectRatio = 1.0f;
/**
* maximum aperture diameter in meters (zero to disable rotation)
*/
@@ -1878,7 +1882,7 @@ public class View {
* Options for Temporal Anti-aliasing (TAA)
* Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process
* shaders to be recompiled. These options should be changed or set during initialization.
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
* `filterWidth`, `feedback` and `jitterPattern`, however, could be changed at any time.
*
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.

View File

@@ -103,10 +103,19 @@ void MetalShaderCompiler::terminate() noexcept {
NSString* objcSource = [[NSString alloc] initWithBytes:source.data()
length:source.size() - 1
encoding:NSUTF8StringEncoding];
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point arithmetic,
// which is crucial for half precision floats in scenarios where fast math optimizations
// lead to inaccuracies, such as in handling special values like NaN or Infinity.
options.fastMathEnabled = NO;
NSError* error = nil;
// When options is nil, Metal uses the most recent language version available.
id<MTLLibrary> library = [device newLibraryWithSource:objcSource
options:nil
options:options
error:&error];
if (library == nil) {
if (error) {

View File

@@ -293,6 +293,7 @@ struct DepthOfFieldOptions {
MEDIAN
};
float cocScale = 1.0f; //!< circle of confusion scale factor (amount of blur)
float cocAspectRatio = 1.0f; //!< width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
float maxApertureDiameter = 0.01f; //!< maximum aperture diameter in meters (zero to disable rotation)
bool enabled = false; //!< enable or disable depth of field effect
Filter filter = Filter::MEDIAN; //!< filter to use for filling gaps in the kernel

View File

@@ -1428,7 +1428,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
FrameGraphId<FrameGraphTexture> depth,
const CameraInfo& cameraInfo,
bool translucent,
float bokehAspectRatio,
float2 bokehScale,
const DepthOfFieldOptions& dofOptions) noexcept {
assert_invariant(depth);
@@ -1818,8 +1818,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
mi->setParameter("tiles", tilesCocMinMax,
{ .filterMin = SamplerMinFilter::NEAREST });
mi->setParameter("cocToTexelScale", float2{
bokehAspectRatio / (inputDesc.width * dofResolution),
1.0 / (inputDesc.height * dofResolution)
bokehScale.x / (inputDesc.width * dofResolution),
bokehScale.y / (inputDesc.height * dofResolution)
});
mi->setParameter("cocToPixelScale", (1.0f / float(dofResolution)));
mi->setParameter("ringCounts", float4{

View File

@@ -162,7 +162,7 @@ public:
FrameGraphId<FrameGraphTexture> depth,
const CameraInfo& cameraInfo,
bool translucent,
float bokehAspectRatio,
math::float2 bokehScale,
const DepthOfFieldOptions& dofOptions) noexcept;
// Bloom

View File

@@ -1043,9 +1043,13 @@ void FRenderer::renderJob(ArenaScope& arena, FView& view) {
// The bokeh height is always correct regardless of the dynamic resolution scaling.
// (because the CoC is calculated w.r.t. the height), so we only need to adjust
// the width.
float const bokehAspectRatio = scale.x / scale.y;
float const aspect = (scale.x / scale.y) * dofOptions.cocAspectRatio;
float2 const bokehScale{
aspect < 1.0f ? aspect : 1.0f,
aspect > 1.0f ? 1.0f / aspect : 1.0f
};
input = ppm.dof(fg, input, depth, cameraInfo, needsAlphaChannel,
bokehAspectRatio, dofOptions);
bokehScale, dofOptions);
}
FrameGraphId<FrameGraphTexture> bloom, flare;

View File

@@ -47,6 +47,9 @@ const float kToastDelayDuration = 2.0f;
- (void)createRenderables;
- (void)createLights;
- (void)appWillResignActive:(NSNotification*)notification;
- (void)appDidBecomeActive:(NSNotification*)notification;
@end
@implementation FILViewController {
@@ -73,6 +76,16 @@ const float kToastDelayDuration = 2.0f;
self.title = @"https://google.github.io/filament/remote";
// Observe lifecycle notifications to prevent us from rendering in the background.
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(appWillResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(appDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
// Arguments:
// --model <path>
// path to glb or gltf file to load from documents directory
@@ -125,6 +138,14 @@ const float kToastDelayDuration = 2.0f;
[self.view addSubview:_toastLabel];
}
- (void)appWillResignActive:(NSNotification*)notification {
[self stopDisplayLink];
}
- (void)appDidBecomeActive:(NSNotification*)notification {
[self startDisplayLink];
}
- (void)viewWillAppear:(BOOL)animated {
[self startDisplayLink];
}
@@ -330,6 +351,7 @@ const float kToastDelayDuration = 2.0f;
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
delete _server;
delete _automation;
self.modelView.engine->destroy(_indirectLight);

View File

@@ -391,6 +391,8 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOpt
CHECK_KEY(tok);
if (compare(tok, jsonChunk, "cocScale") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cocScale);
} else if (compare(tok, jsonChunk, "cocAspectRatio") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->cocAspectRatio);
} else if (compare(tok, jsonChunk, "maxApertureDiameter") == 0) {
i = parse(tokens, i + 1, jsonChunk, &out->maxApertureDiameter);
} else if (compare(tok, jsonChunk, "enabled") == 0) {
@@ -424,6 +426,7 @@ int parse(jsmntok_t const* tokens, int i, const char* jsonChunk, DepthOfFieldOpt
std::ostream& operator<<(std::ostream& out, const DepthOfFieldOptions& in) {
return out << "{\n"
<< "\"cocScale\": " << (in.cocScale) << ",\n"
<< "\"cocAspectRatio\": " << (in.cocAspectRatio) << ",\n"
<< "\"maxApertureDiameter\": " << (in.maxApertureDiameter) << ",\n"
<< "\"enabled\": " << to_string(in.enabled) << ",\n"
<< "\"filter\": " << (in.filter) << ",\n"

View File

@@ -1036,6 +1036,7 @@ void ViewerGui::updateUserInterface() {
ImGui::Checkbox("Enabled##dofEnabled", &mSettings.view.dof.enabled);
ImGui::SliderFloat("Focus distance", &mSettings.viewer.cameraFocusDistance, 0.0f, 30.0f);
ImGui::SliderFloat("Blur scale", &mSettings.view.dof.cocScale, 0.1f, 10.0f);
ImGui::SliderFloat("CoC aspect-ratio", &mSettings.view.dof.cocAspectRatio, 0.25f, 4.0f);
ImGui::SliderInt("Ring count", &dofRingCount, 1, 17);
ImGui::SliderInt("Max CoC", &dofMaxCoC, 1, 32);
ImGui::Checkbox("Native Resolution", &mSettings.view.dof.nativeResolution);

View File

@@ -27,9 +27,7 @@ JsonType JsonishLexer::readIdentifier() noexcept {
consume();
}
const char* lexemeEnd = mCursor - 1;
size_t lexemeSize = lexemeEnd - lexemeStart;
size_t lexemeSize = mCursor - lexemeStart;
// Check what kind of keyword we got here.
if (strncmp("true", lexemeStart, lexemeSize) == 0) {

View File

@@ -60,6 +60,7 @@ Filament.loadGeneratedExtensions = function() {
Filament.View.prototype.setDepthOfFieldOptionsDefaults = function(overrides) {
const options = {
cocScale: 1.0,
cocAspectRatio: 1.0,
maxApertureDiameter: 0.01,
enabled: false,
filter: Filament.View$DepthOfFieldOptions$Filter.MEDIAN,

View File

@@ -1409,6 +1409,10 @@ export interface View$DepthOfFieldOptions {
* circle of confusion scale factor (amount of blur)
*/
cocScale?: number;
/**
* width/height aspect ratio of the circle of confusion (simulate anamorphic lenses)
*/
cocAspectRatio?: number;
/**
* maximum aperture diameter in meters (zero to disable rotation)
*/
@@ -1659,7 +1663,7 @@ export enum View$TemporalAntiAliasingOptions$JitterPattern {
* Options for Temporal Anti-aliasing (TAA)
* Most TAA parameters are extremely costly to change, as they will trigger the TAA post-process
* shaders to be recompiled. These options should be changed or set during initialization.
* `filterWidth`, `feedback` and `jitterPattern`, however, can be changed at any time.
* `filterWidth`, `feedback` and `jitterPattern`, however, could be changed at any time.
*
* `feedback` of 0.1 effectively accumulates a maximum of 19 samples in steady state.
* see "A Survey of Temporal Antialiasing Techniques" by Lei Yang and all for more information.

View File

@@ -58,6 +58,7 @@ value_object<View::FogOptions>("View$FogOptions")
value_object<View::DepthOfFieldOptions>("View$DepthOfFieldOptions")
.field("cocScale", &View::DepthOfFieldOptions::cocScale)
.field("cocAspectRatio", &View::DepthOfFieldOptions::cocAspectRatio)
.field("maxApertureDiameter", &View::DepthOfFieldOptions::maxApertureDiameter)
.field("enabled", &View::DepthOfFieldOptions::enabled)
.field("filter", &View::DepthOfFieldOptions::filter)

View File

@@ -203,7 +203,9 @@ set(HTML_FILES
skinning.html
suzanne.html
test-filament-viewer.html
triangle.html)
triangle.html
benchmark-shader-compilation.html
benchmark-shader-compilation.js)
set(ASSET_FILES
assets/favicon.png)

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Shader Compilation Benchmark</title>
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
<style>
body { margin: 0; overflow: hidden; }
canvas { touch-action: none; width: 100%; height: 100%; }
.frame-time { position: absolute; color: #ffffff; }
</style>
</head>
<body>
<div class="frame-time">
Average Frame Time: <span id="frame-time-counter">Calculating...</span>
</div>
<canvas></canvas>
<script src="filament.js"></script>
<script src="gl-matrix-min.js"></script>
<script src="benchmark-shader-compilation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,141 @@
Filament.init(['nonlit.filamat'], () => {
window.VertexAttribute = Filament.VertexAttribute;
window.AttributeType = Filament.VertexBuffer$AttributeType;
window.Projection = Filament.Camera$Projection;
window.app = new App(
document.getElementsByTagName('canvas')[0],
document.getElementById('frame-time-counter'));
});
const NUMBER_OF_TRIANGLES = 100;
// probably 60 fps; record 1 seconds ish worth of frames.
const NUMBER_OF_FRAMES_TO_RECORD_FPS = 1 * 60;
class App {
constructor(canvas, frameTimeCounter) {
this.canvas = canvas;
this.frameTimeCounter = frameTimeCounter;
this.lastFrameTime = null;
this.frameDeltas = [];
for (let i = 0; i < NUMBER_OF_FRAMES_TO_RECORD_FPS; ++i) {
this.frameDeltas.push(0);
}
this.frameDeltasIndex = 0;
this.frameDeltasSum = 0;
this.frameTimeIsValid = false;
const engine = this.engine = Filament.Engine.create(this.canvas);
this.scene = engine.createScene();
this.triangles = [];
for (let i = 0; i < NUMBER_OF_TRIANGLES; ++i) {
const entity = Filament.EntityManager.get().create();
this.triangles.push({entity: entity});
this.scene.addEntity(entity);
}
const TRIANGLE_POSITIONS = new Float32Array([
1,
0,
Math.cos(Math.PI * 2 / 3),
Math.sin(Math.PI * 2 / 3),
Math.cos(Math.PI * 4 / 3),
Math.sin(Math.PI * 4 / 3),
]);
const TRIANGLE_COLORS =
new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff]);
this.vb =
Filament.VertexBuffer.Builder()
.vertexCount(3)
.bufferCount(2)
.attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT2, 0, 8)
.attribute(VertexAttribute.COLOR, 1, AttributeType.UBYTE4, 0, 4)
.normalized(VertexAttribute.COLOR)
.build(engine);
this.vb.setBufferAt(engine, 0, TRIANGLE_POSITIONS);
this.vb.setBufferAt(engine, 1, TRIANGLE_COLORS);
this.ib = Filament.IndexBuffer.Builder()
.indexCount(3)
.bufferType(Filament.IndexBuffer$IndexType.USHORT)
.build(engine);
this.ib.setBuffer(engine, new Uint16Array([0, 1, 2]));
this.swapChain = engine.createSwapChain();
this.renderer = engine.createRenderer();
this.camera = engine.createCamera(Filament.EntityManager.get().create());
this.view = engine.createView();
this.view.setSampleCount(4);
this.view.setCamera(this.camera);
this.view.setScene(this.scene);
this.renderer.setClearOptions(
{clearColor: [0.0, 0.1, 0.2, 1.0], clear: true});
this.resize();
this.render = this.render.bind(this);
this.resize = this.resize.bind(this);
window.addEventListener('resize', this.resize);
window.requestAnimationFrame(this.render);
}
render() {
const frameTime = Date.now();
if (this.lastFrameTime) {
const delta = frameTime - this.lastFrameTime;
this.frameDeltasSum =
this.frameDeltasSum - this.frameDeltas[this.frameDeltasIndex] + delta;
this.frameDeltas[this.frameDeltasIndex] = delta;
this.frameDeltasIndex =
(this.frameDeltasIndex + 1) % NUMBER_OF_FRAMES_TO_RECORD_FPS;
if (this.frameDeltasIndex == 0) {
this.frameTimeIsValid = true;
}
}
this.lastFrameTime = frameTime;
if (this.frameTimeIsValid) {
const averageFrameDelta =
this.frameDeltasSum * 1.0 / NUMBER_OF_FRAMES_TO_RECORD_FPS;
this.frameTimeCounter.innerHTML = averageFrameDelta.toFixed(2) + ' ms';
}
this.triangles.forEach(triangle => {
if (triangle.mat) {
this.engine.destroyMaterial(triangle.mat);
}
triangle.mat = this.engine.createMaterial('nonlit.filamat');
const matinst = triangle.mat.getDefaultInstance();
Filament.RenderableManager.Builder(1)
.boundingBox({center: [-1, -1, -1], halfExtent: [1, 1, 1]})
.material(0, matinst)
.geometry(
0, Filament.RenderableManager$PrimitiveType.TRIANGLES, this.vb,
this.ib)
.build(this.engine, triangle.entity);
});
const radians = Date.now() / 1000;
const transform = mat4.fromRotation(mat4.create(), radians, [0, 0, 1]);
const tcm = this.engine.getTransformManager();
const inst = tcm.getInstance(this.triangles[0].entity);
tcm.setTransform(inst, transform);
inst.delete();
this.renderer.render(this.swapChain, this.view);
window.requestAnimationFrame(this.render);
}
resize() {
const dpr = window.devicePixelRatio;
const width = this.canvas.width = window.innerWidth * dpr;
const height = this.canvas.height = window.innerHeight * dpr;
this.view.setViewport([0, 0, width, height]);
const aspect = width / height;
this.camera.setProjection(Projection.ORTHO, -aspect, aspect, -1, 1, 0, 1);
}
}