Compare commits
5 Commits
v1.50.0
...
exv/webgl-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9fdbeb5c | ||
|
|
5fd7a4e153 | ||
|
|
20ff230b92 | ||
|
|
44ff79ad34 | ||
|
|
102d2db008 |
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -162,7 +162,7 @@ public:
|
||||
FrameGraphId<FrameGraphTexture> depth,
|
||||
const CameraInfo& cameraInfo,
|
||||
bool translucent,
|
||||
float bokehAspectRatio,
|
||||
math::float2 bokehScale,
|
||||
const DepthOfFieldOptions& dofOptions) noexcept;
|
||||
|
||||
// Bloom
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
6
web/filament-js/filament.d.ts
vendored
6
web/filament-js/filament.d.ts
vendored
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
22
web/samples/benchmark-shader-compilation.html
Normal file
22
web/samples/benchmark-shader-compilation.html
Normal 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>
|
||||
141
web/samples/benchmark-shader-compilation.js
Normal file
141
web/samples/benchmark-shader-compilation.js
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user