Next, invoke matc
as follows.
matc -a opengl -p mobile -o plastic.filamat plastic.mat
+matc -a opengl -p mobile -o plastic.filamat plastic.mat
You should now have a material archive in your working directory, which we'll use later in the
@@ -53,7 +53,7 @@
Bake environment map
Next we'll use Filament's cmgen
tool to consume a HDR environment map in latlong format, and
produce two cubemap files: a mipmapped IBL and a blurry skybox.
Download pillars_2k.hdr, then invoke the following command in your terminal.
-cmgen -x pillars_2k --format=ktx --size=256 --extract-blur=0.1 pillars_2k.hdr
+cmgen -x pillars_2k --format=ktx --size=256 --extract-blur=0.1 pillars_2k.hdr
You should now have a pillars_2k
folder containing a couple KTX files for the IBL and skybox, as
@@ -61,81 +61,81 @@
Bake environment map
IBL KTX contains these coefficients in its metadata.
Create JavaScript
Next, create redball.js
with the following content.
-const environ = 'pillars_2k';
-const ibl_url = `${environ}/${environ}_ibl.ktx`;
-const sky_url = `${environ}/${environ}_skybox.ktx`;
-const filamat_url = 'plastic.filamat'
-
-Filament.init([ filamat_url, ibl_url, sky_url ], () => {
- // Create some global aliases to enums for convenience.
- window.VertexAttribute = Filament.VertexAttribute;
- window.AttributeType = Filament.VertexBuffer$AttributeType;
- window.PrimitiveType = Filament.RenderableManager$PrimitiveType;
- window.IndexType = Filament.IndexBuffer$IndexType;
- window.Fov = Filament.Camera$Fov;
- window.LightType = Filament.LightManager$Type;
-
- // Obtain the canvas DOM object and pass it to the App.
- const canvas = document.getElementsByTagName('canvas')[0];
- window.app = new App(canvas);
-} );
-
-class App {
- constructor(canvas) {
- this.canvas = canvas;
- const engine = this.engine = Filament.Engine.create(canvas);
- const scene = engine.createScene();
-
- // TODO: create material
- // TODO: create sphere
- // TODO: create lights
- // TODO: create IBL
- // TODO: create skybox
-
- this.swapChain = engine.createSwapChain();
- this.renderer = engine.createRenderer();
- this.camera = engine.createCamera(Filament.EntityManager.get().create());
- this.view = engine.createView();
- this.view.setCamera(this.camera);
- this.view.setScene(scene);
- 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 eye = [0, 0, 4], center = [0, 0, 0], up = [0, 1, 0];
- const radians = Date.now() / 10000;
- vec3.rotateY(eye, eye, center, radians);
- this.camera.lookAt(eye, center, up);
- 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]);
- this.camera.setProjectionFov(45, width / height, 1.0, 10.0, Fov.VERTICAL);
- }
-}
+const environ = 'pillars_2k';
+const ibl_url = `${environ}/${environ}_ibl.ktx`;
+const sky_url = `${environ}/${environ}_skybox.ktx`;
+const filamat_url = 'plastic.filamat'
+
+Filament.init([ filamat_url, ibl_url, sky_url ], () => {
+ // Create some global aliases to enums for convenience.
+ window.VertexAttribute = Filament.VertexAttribute;
+ window.AttributeType = Filament.VertexBuffer$AttributeType;
+ window.PrimitiveType = Filament.RenderableManager$PrimitiveType;
+ window.IndexType = Filament.IndexBuffer$IndexType;
+ window.Fov = Filament.Camera$Fov;
+ window.LightType = Filament.LightManager$Type;
+
+ // Obtain the canvas DOM object and pass it to the App.
+ const canvas = document.getElementsByTagName('canvas')[0];
+ window.app = new App(canvas);
+} );
+
+class App {
+ constructor(canvas) {
+ this.canvas = canvas;
+ const engine = this.engine = Filament.Engine.create(canvas);
+ const scene = engine.createScene();
+
+ // TODO: create material
+ // TODO: create sphere
+ // TODO: create lights
+ // TODO: create IBL
+ // TODO: create skybox
+
+ this.swapChain = engine.createSwapChain();
+ this.renderer = engine.createRenderer();
+ this.camera = engine.createCamera(Filament.EntityManager.get().create());
+ this.view = engine.createView();
+ this.view.setCamera(this.camera);
+ this.view.setScene(scene);
+ 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 eye = [0, 0, 4], center = [0, 0, 0], up = [0, 1, 0];
+ const radians = Date.now() / 10000;
+ vec3.rotateY(eye, eye, center, radians);
+ this.camera.lookAt(eye, center, up);
+ 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]);
+ this.camera.setProjectionFov(45, width / height, 1.0, 10.0, Fov.VERTICAL);
+ }
+}
The above boilerplate should be familiar to you from the previous tutorial, although it loads in a
new set of assets. We also added some animation to the camera.
Next let's create a material instance from the package that we built at the beginning the tutorial.
Replace the create material comment with the following snippet.
-const material = engine.createMaterial(filamat_url);
-const matinstance = material.createInstance();
-
-const red = [0.8, 0.0, 0.0];
-matinstance.setColor3Parameter('baseColor', Filament.RgbType.sRGB, red);
-matinstance.setFloatParameter('roughness', 0.5);
-matinstance.setFloatParameter('clearCoat', 1.0);
-matinstance.setFloatParameter('clearCoatRoughness', 0.3);
+const material = engine.createMaterial(filamat_url);
+const matinstance = material.createInstance();
+
+const red = [0.8, 0.0, 0.0];
+matinstance.setColor3Parameter('baseColor', Filament.RgbType.sRGB, red);
+matinstance.setFloatParameter('roughness', 0.5);
+matinstance.setFloatParameter('clearCoat', 1.0);
+matinstance.setFloatParameter('clearCoatRoughness', 0.3);
The next step is to create a renderable for the sphere. To help with this, we'll use the IcoSphere
@@ -149,33 +149,33 @@
Create JavaScript
Let's go ahead use these arrays to build the vertex buffer and index buffer. Replace create
sphere with the following snippet.
-const renderable = Filament.EntityManager.get().create();
-scene.addEntity(renderable);
-
-const icosphere = new Filament.IcoSphere(5);
-
-const vb = Filament.VertexBuffer.Builder()
- .vertexCount(icosphere.vertices.length / 3)
- .bufferCount(2)
- .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, 0)
- .attribute(VertexAttribute.TANGENTS, 1, AttributeType.SHORT4, 0, 0)
- .normalized(VertexAttribute.TANGENTS)
- .build(engine);
-
-const ib = Filament.IndexBuffer.Builder()
- .indexCount(icosphere.triangles.length)
- .bufferType(IndexType.USHORT)
- .build(engine);
-
-vb.setBufferAt(engine, 0, icosphere.vertices);
-vb.setBufferAt(engine, 1, icosphere.tangents);
-ib.setBuffer(engine, icosphere.triangles);
-
-Filament.RenderableManager.Builder(1)
- .boundingBox({ center: [-1, -1, -1], halfExtent: [1, 1, 1] })
- .material(0, matinstance)
- .geometry(0, PrimitiveType.TRIANGLES, vb, ib)
- .build(engine, renderable);
+const renderable = Filament.EntityManager.get().create();
+scene.addEntity(renderable);
+
+const icosphere = new Filament.IcoSphere(5);
+
+const vb = Filament.VertexBuffer.Builder()
+ .vertexCount(icosphere.vertices.length / 3)
+ .bufferCount(2)
+ .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, 0)
+ .attribute(VertexAttribute.TANGENTS, 1, AttributeType.SHORT4, 0, 0)
+ .normalized(VertexAttribute.TANGENTS)
+ .build(engine);
+
+const ib = Filament.IndexBuffer.Builder()
+ .indexCount(icosphere.triangles.length)
+ .bufferType(IndexType.USHORT)
+ .build(engine);
+
+vb.setBufferAt(engine, 0, icosphere.vertices);
+vb.setBufferAt(engine, 1, icosphere.tangents);
+ib.setBuffer(engine, icosphere.triangles);
+
+Filament.RenderableManager.Builder(1)
+ .boundingBox({ center: [-1, -1, -1], halfExtent: [1, 1, 1] })
+ .material(0, matinstance)
+ .geometry(0, PrimitiveType.TRIANGLES, vb, ib)
+ .build(engine, renderable);
At this point, the app is rendering a sphere, but it is black so it doesn't show up. To prove that
@@ -185,94 +185,94 @@
Add lighting
In this section we will create some directional light sources, as well as an image-based light (IBL)
defined by one of the KTX files we built at the start of the demo. First, replace the create
lights comment with the following snippet.
-const sunlight = Filament.EntityManager.get().create();
-scene.addEntity(sunlight);
-Filament.LightManager.Builder(LightType.SUN)
- .color([0.98, 0.92, 0.89])
- .intensity(110000.0)
- .direction([0.6, -1.0, -0.8])
- .sunAngularRadius(1.9)
- .sunHaloSize(10.0)
- .sunHaloFalloff(80.0)
- .build(engine, sunlight);
-
-const backlight = Filament.EntityManager.get().create();
-scene.addEntity(backlight);
-Filament.LightManager.Builder(LightType.DIRECTIONAL)
- .direction([-1, 0, 1])
- .intensity(50000.0)
- .build(engine, backlight);
+const sunlight = Filament.EntityManager.get().create();
+scene.addEntity(sunlight);
+Filament.LightManager.Builder(LightType.SUN)
+ .color([0.98, 0.92, 0.89])
+ .intensity(110000.0)
+ .direction([0.6, -1.0, -0.8])
+ .sunAngularRadius(1.9)
+ .sunHaloSize(10.0)
+ .sunHaloFalloff(80.0)
+ .build(engine, sunlight);
+
+const backlight = Filament.EntityManager.get().create();
+scene.addEntity(backlight);
+Filament.LightManager.Builder(LightType.DIRECTIONAL)
+ .direction([-1, 0, 1])
+ .intensity(50000.0)
+ .build(engine, backlight);
The SUN
light source is similar to the DIRECTIONAL
light source, but has some extra
parameters because Filament will automatically draw a disk into the skybox.
Next we need to create an IndirectLight
object from the KTX IBL. One way of doing this is the
following (don't type this out, there's an easier way).
-const format = Filament.PixelDataFormat.RGB;
-const datatype = Filament.PixelDataType.UINT_10F_11F_11F_REV;
-
-// Create a Texture object for the mipmapped cubemap.
-const ibl_package = Filament.Buffer(Filament.assets[ibl_url]);
-const iblktx = new Filament.Ktx1Bundle(ibl_package);
-
-const ibltex = Filament.Texture.Builder()
- .width(iblktx.info().pixelWidth)
- .height(iblktx.info().pixelHeight)
- .levels(iblktx.getNumMipLevels())
- .sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
- .format(Filament.Texture$InternalFormat.RGBA8)
- .build(engine);
-
-for (let level = 0; level < iblktx.getNumMipLevels(); ++level) {
- const uint8array = iblktx.getCubeBlob(level).getBytes();
- const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
- ibltex.setImageCube(engine, level, pixelbuffer);
-}
-
-// Parse the spherical harmonics metadata.
-const shstring = iblktx.getMetadata('sh');
-const shfloats = shstring.split(/\s/, 9 * 3).map(parseFloat);
-
-// Build the IBL object and insert it into the scene.
-const indirectLight = Filament.IndirectLight.Builder()
- .reflections(ibltex)
- .irradianceSh(3, shfloats)
- .intensity(50000.0)
- .build(engine);
-
-scene.setIndirectLight(indirectLight);
+const format = Filament.PixelDataFormat.RGB;
+const datatype = Filament.PixelDataType.UINT_10F_11F_11F_REV;
+
+// Create a Texture object for the mipmapped cubemap.
+const ibl_package = Filament.Buffer(Filament.assets[ibl_url]);
+const iblktx = new Filament.Ktx1Bundle(ibl_package);
+
+const ibltex = Filament.Texture.Builder()
+ .width(iblktx.info().pixelWidth)
+ .height(iblktx.info().pixelHeight)
+ .levels(iblktx.getNumMipLevels())
+ .sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
+ .format(Filament.Texture$InternalFormat.RGBA8)
+ .build(engine);
+
+for (let level = 0; level < iblktx.getNumMipLevels(); ++level) {
+ const uint8array = iblktx.getCubeBlob(level).getBytes();
+ const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
+ ibltex.setImageCube(engine, level, pixelbuffer);
+}
+
+// Parse the spherical harmonics metadata.
+const shstring = iblktx.getMetadata('sh');
+const shfloats = shstring.split(/\s/, 9 * 3).map(parseFloat);
+
+// Build the IBL object and insert it into the scene.
+const indirectLight = Filament.IndirectLight.Builder()
+ .reflections(ibltex)
+ .irradianceSh(3, shfloats)
+ .intensity(50000.0)
+ .build(engine);
+
+scene.setIndirectLight(indirectLight);
Filament provides a JavaScript utility to make this simpler,
simply replace the create IBL comment with the following snippet.
-const indirectLight = engine.createIblFromKtx1(ibl_url);
-indirectLight.setIntensity(50000);
-scene.setIndirectLight(indirectLight);
+const indirectLight = engine.createIblFromKtx1(ibl_url);
+indirectLight.setIntensity(50000);
+scene.setIndirectLight(indirectLight);
Add background
At this point you can run the demo and you should see a red plastic ball against a black background.
Without a skybox, the reflections on the ball are not representative of its surroundings.
Here's one way to create a texture for the skybox:
-const sky_package = Filament.Buffer(Filament.assets[sky_url]);
-const skyktx = new Filament.Ktx1Bundle(sky_package);
-const skytex = Filament.Texture.Builder()
- .width(skyktx.info().pixelWidth)
- .height(skyktx.info().pixelHeight)
- .levels(1)
- .sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
- .format(Filament.Texture$InternalFormat.RGBA8)
- .build(engine);
-
-const uint8array = skyktx.getCubeBlob(0).getBytes();
-const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
-skytex.setImageCube(engine, 0, pixelbuffer);
+const sky_package = Filament.Buffer(Filament.assets[sky_url]);
+const skyktx = new Filament.Ktx1Bundle(sky_package);
+const skytex = Filament.Texture.Builder()
+ .width(skyktx.info().pixelWidth)
+ .height(skyktx.info().pixelHeight)
+ .levels(1)
+ .sampler(Filament.Texture$Sampler.SAMPLER_CUBEMAP)
+ .format(Filament.Texture$InternalFormat.RGBA8)
+ .build(engine);
+
+const uint8array = skyktx.getCubeBlob(0).getBytes();
+const pixelbuffer = Filament.PixelBuffer(uint8array, format, datatype);
+skytex.setImageCube(engine, 0, pixelbuffer);
Filament provides a Javascript utility to make this easier.
Replace create skybox with the following.
-const skybox = engine.createSkyFromKtx1(sky_url);
-scene.setSkybox(skybox);
+const skybox = engine.createSkyFromKtx1(sky_url);
+scene.setSkybox(skybox);
That's it, we now have a shiny red ball floating in an environment! The complete JavaScript file is
diff --git a/docs/webgl/tutorial_suzanne.html b/docs/webgl/tutorial_suzanne.html
index bfaf97e97cd..c0edb5ed0af 100644
--- a/docs/webgl/tutorial_suzanne.html
+++ b/docs/webgl/tutorial_suzanne.html
@@ -15,7 +15,7 @@
Create filamesh file
Filament does not have an asset loading system, but it does provide a binary mesh format
called filamesh
for simple use cases. Let's create a compressed filamesh file for suzanne by
converting this OBJ file:
-filamesh --compress monkey.obj suzanne.filamesh
+filamesh --compress monkey.obj suzanne.filamesh
Create mipmapped textures
@@ -23,26 +23,26 @@ Create mipmapped textures
non-compressed variants for each texture, since not all platforms support the same compression
formats. First copy over the PNG files from the monkey folder, then do:
# Create mipmaps for base color and two compressed variants.
-mipgen albedo.png albedo.ktx
-mipgen --compression=astc_fast_ldr_4x4 albedo.png albedo_astc.ktx
-mipgen --compression=s3tc_rgb_dxt1 albedo.png albedo_s3tc_srgb.ktx
+mipgen albedo.png albedo.ktx
+mipgen --compression=astc_fast_ldr_4x4 albedo.png albedo_astc.ktx
+mipgen --compression=s3tc_rgb_dxt1 albedo.png albedo_s3tc_srgb.ktx
# Create mipmaps for the normal map and a compressed variant.
-mipgen --strip-alpha --kernel=NORMALS --linear normal.png normal.ktx
-mipgen --strip-alpha --kernel=NORMALS --linear --compression=etc_rgb8_normalxyz_40 \
- normal.png normal_etc.ktx
+mipgen --strip-alpha --kernel=NORMALS --linear normal.png normal.ktx
+mipgen --strip-alpha --kernel=NORMALS --linear --compression=etc_rgb8_normalxyz_40 \
+ normal.png normal_etc.ktx
# Create mipmaps for the single-component roughness map and a compressed variant.
-mipgen --grayscale roughness.png roughness.ktx
-mipgen --grayscale --compression=etc_r11_numeric_40 roughness.png roughness_etc.ktx
+mipgen --grayscale roughness.png roughness.ktx
+mipgen --grayscale --compression=etc_r11_numeric_40 roughness.png roughness_etc.ktx
# Create mipmaps for the single-component metallic map and a compressed variant.
-mipgen --grayscale metallic.png metallic.ktx
-mipgen --grayscale --compression=etc_r11_numeric_40 metallic.png metallic_etc.ktx
+mipgen --grayscale metallic.png metallic.ktx
+mipgen --grayscale --compression=etc_r11_numeric_40 metallic.png metallic_etc.ktx
# Create mipmaps for the single-component occlusion map and a compressed variant.
-mipgen --grayscale ao.png ao.ktx
-mipgen --grayscale --compression=etc_r11_numeric_40 ao.png ao_etc.ktx
+mipgen --grayscale ao.png ao.ktx
+mipgen --grayscale --compression=etc_r11_numeric_40 ao.png ao_etc.ktx
For more information on mipgen's arguments and supported formats, do mipgen --help
.
@@ -50,12 +50,12 @@ Create mipmapped textures
Bake environment map
Much like the previous tutorial we need to use Filament's cmgen
tool to produce cubemap files.
Download venetian_crossroads_2k.hdr, then invoke the following commands in your terminal.
-cmgen -x . --format=ktx --size=64 --extract-blur=0.1 venetian_crossroads_2k.hdr
-cd venetian* ; mv venetian*_ibl.ktx venetian_crossroads_2k_skybox_tiny.ktx ; cd -
+cmgen -x . --format=ktx --size=64 --extract-blur=0.1 venetian_crossroads_2k.hdr
+cd venetian* ; mv venetian*_ibl.ktx venetian_crossroads_2k_skybox_tiny.ktx ; cd -
-cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
-cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
-cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
+cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
+cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
+cmgen -x . --format=ktx --size=256 --extract-blur=0.1 venetian_crossroads_2k.hdr
Define textured material
@@ -91,7 +91,7 @@ Define textured material
Next, invoke matc
as follows.
-matc -a opengl -p mobile -o textured.filamat textured.mat
+matc -a opengl -p mobile -o textured.filamat textured.mat
You should now have a material archive in your working directory. For the suzanne asset, the normal
@@ -101,62 +101,62 @@
Create app skeleton
Create a text file called suzanne.html
and copy over the HTML that we used in the previous
tutorial. Change the last script tag from redball.js
to suzanne.js
. Next, create suzanne.js
with the following content.
-// TODO: declare asset URLs
-
-Filament.init([ filamat_url, filamesh_url, sky_small_url, ibl_url ], () => {
- window.app = new App(document.getElementsByTagName('canvas')[0]);
-});
-
-class App {
- constructor(canvas) {
- this.canvas = canvas;
- this.engine = Filament.Engine.create(canvas);
- this.scene = this.engine.createScene();
-
- const material = this.engine.createMaterial(filamat_url);
- this.matinstance = material.createInstance();
-
- const filamesh = this.engine.loadFilamesh(filamesh_url, this.matinstance);
- this.suzanne = filamesh.renderable;
-
- // TODO: create sky box and IBL
- // TODO: initialize gltumble
- // TODO: fetch larger assets
-
- this.swapChain = this.engine.createSwapChain();
- this.renderer = this.engine.createRenderer();
- this.camera = this.engine.createCamera(Filament.EntityManager.get().create());
- this.view = this.engine.createView();
- this.view.setCamera(this.camera);
- this.view.setScene(this.scene);
- this.render = this.render.bind(this);
- this.resize = this.resize.bind(this);
- window.addEventListener('resize', this.resize);
-
- const eye = [0, 0, 4], center = [0, 0, 0], up = [0, 1, 0];
- this.camera.lookAt(eye, center, up);
-
- this.resize();
- window.requestAnimationFrame(this.render);
- }
-
- render() {
- // TODO: apply gltumble matrix
- 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;
- const Fov = Filament.Camera$Fov, fov = aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL;
- this.camera.setProjectionFov(45, aspect, 1.0, 10.0, fov);
- }
-}
+// TODO: declare asset URLs
+
+Filament.init([ filamat_url, filamesh_url, sky_small_url, ibl_url ], () => {
+ window.app = new App(document.getElementsByTagName('canvas')[0]);
+});
+
+class App {
+ constructor(canvas) {
+ this.canvas = canvas;
+ this.engine = Filament.Engine.create(canvas);
+ this.scene = this.engine.createScene();
+
+ const material = this.engine.createMaterial(filamat_url);
+ this.matinstance = material.createInstance();
+
+ const filamesh = this.engine.loadFilamesh(filamesh_url, this.matinstance);
+ this.suzanne = filamesh.renderable;
+
+ // TODO: create sky box and IBL
+ // TODO: initialize gltumble
+ // TODO: fetch larger assets
+
+ this.swapChain = this.engine.createSwapChain();
+ this.renderer = this.engine.createRenderer();
+ this.camera = this.engine.createCamera(Filament.EntityManager.get().create());
+ this.view = this.engine.createView();
+ this.view.setCamera(this.camera);
+ this.view.setScene(this.scene);
+ this.render = this.render.bind(this);
+ this.resize = this.resize.bind(this);
+ window.addEventListener('resize', this.resize);
+
+ const eye = [0, 0, 4], center = [0, 0, 0], up = [0, 1, 0];
+ this.camera.lookAt(eye, center, up);
+
+ this.resize();
+ window.requestAnimationFrame(this.render);
+ }
+
+ render() {
+ // TODO: apply gltumble matrix
+ 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;
+ const Fov = Filament.Camera$Fov, fov = aspect < 1 ? Fov.HORIZONTAL : Fov.VERTICAL;
+ this.camera.setProjectionFov(45, aspect, 1.0, 10.0, fov);
+ }
+}
Our app will only require a subset of assets to be present for App
construction. We'll download
@@ -172,29 +172,29 @@
Create app skeleton
In our case, we know that our web server will have astc
and s3tc
variants for albedo, and etc
variants for the other textures. The uncompressed variants (empty string) are always available as a
last resort. Go ahead and replace the declare asset URLs comment with the following snippet.
-const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc_srgb');
-const texture_suffix = Filament.getSupportedFormatSuffix('etc');
-
-const environ = 'venetian_crossroads_2k'
-const ibl_url = `${environ}/${environ}_ibl.ktx`;
-const sky_small_url = `${environ}/${environ}_skybox_tiny.ktx`;
-const sky_large_url = `${environ}/${environ}_skybox.ktx`;
-const albedo_url = `albedo${albedo_suffix}.ktx`;
-const ao_url = `ao${texture_suffix}.ktx`;
-const metallic_url = `metallic${texture_suffix}.ktx`;
-const normal_url = `normal${texture_suffix}.ktx`;
-const roughness_url = `roughness${texture_suffix}.ktx`;
-const filamat_url = 'textured.filamat';
-const filamesh_url = 'suzanne.filamesh';
+const albedo_suffix = Filament.getSupportedFormatSuffix('astc s3tc_srgb');
+const texture_suffix = Filament.getSupportedFormatSuffix('etc');
+
+const environ = 'venetian_crossroads_2k'
+const ibl_url = `${environ}/${environ}_ibl.ktx`;
+const sky_small_url = `${environ}/${environ}_skybox_tiny.ktx`;
+const sky_large_url = `${environ}/${environ}_skybox.ktx`;
+const albedo_url = `albedo${albedo_suffix}.ktx`;
+const ao_url = `ao${texture_suffix}.ktx`;
+const metallic_url = `metallic${texture_suffix}.ktx`;
+const normal_url = `normal${texture_suffix}.ktx`;
+const roughness_url = `roughness${texture_suffix}.ktx`;
+const filamat_url = 'textured.filamat';
+const filamesh_url = 'suzanne.filamesh';
Create skybox and IBL
Next, let's create the low-resolution skybox and IBL in the App
constructor.
-this.skybox = this.engine.createSkyFromKtx1(sky_small_url);
-this.scene.setSkybox(this.skybox);
-this.indirectLight = this.engine.createIblFromKtx1(ibl_url);
-this.indirectLight.setIntensity(100000);
-this.scene.setIndirectLight(this.indirectLight);
+this.skybox = this.engine.createSkyFromKtx1(sky_small_url);
+this.scene.setSkybox(this.skybox);
+this.indirectLight = this.engine.createIblFromKtx1(ibl_url);
+this.indirectLight.setIntensity(100000);
+this.scene.setIndirectLight(this.indirectLight);
This allows users to see a reasonable background fairly quickly, before larger assets have finished
@@ -206,31 +206,31 @@
Fetch assets asychronously
In our callback, we'll make several setTextureParameter
calls on the material instance, then we'll
recreate the skybox using a higher-resolution texture. As a last step we unhide the renderable that
was created in the app constructor.
-Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url], () => {
- const albedo = this.engine.createTextureFromKtx1(albedo_url, {srgb: true});
- const roughness = this.engine.createTextureFromKtx1(roughness_url);
- const metallic = this.engine.createTextureFromKtx1(metallic_url);
- const normal = this.engine.createTextureFromKtx1(normal_url);
- const ao = this.engine.createTextureFromKtx1(ao_url);
-
- const sampler = new Filament.TextureSampler(
- Filament.MinFilter.LINEAR_MIPMAP_LINEAR,
- Filament.MagFilter.LINEAR,
- Filament.WrapMode.CLAMP_TO_EDGE);
-
- this.matinstance.setTextureParameter('albedo', albedo, sampler);
- this.matinstance.setTextureParameter('roughness', roughness, sampler);
- this.matinstance.setTextureParameter('metallic', metallic, sampler);
- this.matinstance.setTextureParameter('normal', normal, sampler);
- this.matinstance.setTextureParameter('ao', ao, sampler);
-
- // Replace low-res skybox with high-res skybox.
- this.engine.destroySkybox(this.skybox);
- this.skybox = this.engine.createSkyFromKtx1(sky_large_url);
- this.scene.setSkybox(this.skybox);
-
- this.scene.addEntity(this.suzanne);
-});
+Filament.fetch([sky_large_url, albedo_url, roughness_url, metallic_url, normal_url, ao_url], () => {
+ const albedo = this.engine.createTextureFromKtx1(albedo_url, {srgb: true});
+ const roughness = this.engine.createTextureFromKtx1(roughness_url);
+ const metallic = this.engine.createTextureFromKtx1(metallic_url);
+ const normal = this.engine.createTextureFromKtx1(normal_url);
+ const ao = this.engine.createTextureFromKtx1(ao_url);
+
+ const sampler = new Filament.TextureSampler(
+ Filament.MinFilter.LINEAR_MIPMAP_LINEAR,
+ Filament.MagFilter.LINEAR,
+ Filament.WrapMode.CLAMP_TO_EDGE);
+
+ this.matinstance.setTextureParameter('albedo', albedo, sampler);
+ this.matinstance.setTextureParameter('roughness', roughness, sampler);
+ this.matinstance.setTextureParameter('metallic', metallic, sampler);
+ this.matinstance.setTextureParameter('normal', normal, sampler);
+ this.matinstance.setTextureParameter('ao', ao, sampler);
+
+ // Replace low-res skybox with high-res skybox.
+ this.engine.destroySkybox(this.skybox);
+ this.skybox = this.engine.createSkyFromKtx1(sky_large_url);
+ this.scene.setSkybox(this.skybox);
+
+ this.scene.addEntity(this.suzanne);
+});
Introduce trackball rotation
@@ -241,13 +241,13 @@ Introduce trackball rotation
Next, replace the initialize gltumble and apply gltumble matrix comments with the following
two code snippets.
-this.trackball = new Trackball(canvas, {startSpin: 0.035});
+this.trackball = new Trackball(canvas, {startSpin: 0.035});
-const tcm = this.engine.getTransformManager();
-const inst = tcm.getInstance(this.suzanne);
-tcm.setTransform(inst, this.trackball.getMatrix());
-inst.delete();
+const tcm = this.engine.getTransformManager();
+const inst = tcm.getInstance(this.suzanne);
+tcm.setTransform(inst, this.trackball.getMatrix());
+inst.delete();
That's it, we now have a fast-loading interactive demo. The complete JavaScript file is available
diff --git a/docs/webgl/tutorial_triangle.html b/docs/webgl/tutorial_triangle.html
index ab2df5a7feb..9f87d040549 100644
--- a/docs/webgl/tutorial_triangle.html
+++ b/docs/webgl/tutorial_triangle.html
@@ -22,9 +22,9 @@
Start your project
<title>Filament Tutorial</title>
<meta charset="utf-8">
<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%; }
+ <style>
+ body { margin: 0; overflow: hidden; }
+ canvas { touch-action: none; width: 100%; height: 100%; }
</style>
</head>
<body>
@@ -48,24 +48,24 @@ Start your project
triangle.js
will contain your application code.
Go ahead and create triangle.js
with the following content.
-class App {
- constructor() {
- // TODO: create entities
- this.render = this.render.bind(this);
- this.resize = this.resize.bind(this);
- window.addEventListener('resize', this.resize);
- window.requestAnimationFrame(this.render);
- }
- render() {
- // TODO: render scene
- window.requestAnimationFrame(this.render);
- }
- resize() {
- // TODO: adjust viewport and canvas
- }
-}
-
-Filament.init(['triangle.filamat'], () => { window.app = new App() } );
+class App {
+ constructor() {
+ // TODO: create entities
+ this.render = this.render.bind(this);
+ this.resize = this.resize.bind(this);
+ window.addEventListener('resize', this.resize);
+ window.requestAnimationFrame(this.render);
+ }
+ render() {
+ // TODO: render scene
+ window.requestAnimationFrame(this.render);
+ }
+ resize() {
+ // TODO: adjust viewport and canvas
+ }
+}
+
+Filament.init(['triangle.filamat'], () => { window.app = new App() } );
The two calls to bind()
allow us to pass instance methods as callbacks for animation and resize
@@ -81,9 +81,9 @@
Start your project
Spawn a local server
Because of CORS restrictions, your web app cannot fetch the material package directly from the
file system. One way around this is to create a temporary server using Python or node:
-python3 -m http.server # Python 3
-python -m SimpleHTTPServer # Python 2.7
-npx http-server -p 8000 # nodejs
+python3 -m http.server # Python 3
+python -m SimpleHTTPServer # Python 2.7
+npx http-server -p 8000 # nodejs
To see if this works, navigate to http://localhost:8000 and check if you
@@ -93,8 +93,8 @@
Spawn a local server
Create the Engine and Scene
We now have a basic skeleton that can respond to paint and resize events. Let's start adding
Filament objects to the app. Insert the following code into the top of the app constructor.
-this.canvas = document.getElementsByTagName('canvas')[0];
-const engine = this.engine = Filament.Engine.create(this.canvas);
+this.canvas = document.getElementsByTagName('canvas')[0];
+const engine = this.engine = Filament.Engine.create(this.canvas);
The above snippet creates the Engine
by passing it a canvas DOM object. The engine needs the
@@ -102,9 +102,9 @@
Create the Engine and Scene
The engine is a factory for many Filament entities, including Scene
, which is a flat container of
entities. Let's go ahead and create a scene, then add a blank entity called triangle
into the
scene.
-this.scene = engine.createScene();
-this.triangle = Filament.EntityManager.get().create();
-this.scene.addEntity(this.triangle);
+this.scene = engine.createScene();
+this.triangle = Filament.EntityManager.get().create();
+this.scene.addEntity(this.triangle);
Filament uses an Entity-Component System.
@@ -114,28 +114,28 @@
Create the Engine and Scene
Construct typed arrays
Next we'll create two typed arrays: a positions array with XY coordinates for each vertex, and a
colors array with a 32-bit word for each vertex.
-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_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]);
+const TRIANGLE_COLORS = new Uint32Array([0xffff0000, 0xff00ff00, 0xff0000ff]);
Next we'll use the positions and colors buffers to create a single VertexBuffer
object.
-const VertexAttribute = Filament.VertexAttribute;
-const AttributeType = Filament.VertexBuffer$AttributeType;
-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);
+const VertexAttribute = Filament.VertexAttribute;
+const AttributeType = Filament.VertexBuffer$AttributeType;
+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);
The above snippet first creates aliases for two enum types, then constructs the vertex buffer using
@@ -149,12 +149,12 @@
Construct typed arrays
buffer slot.
Next we'll construct an index buffer. The index buffer for our triangle is trivial: it simply holds
the integers 0,1,2.
-this.ib = Filament.IndexBuffer.Builder()
- .indexCount(3)
- .bufferType(Filament.IndexBuffer$IndexType.USHORT)
- .build(engine);
+this.ib = Filament.IndexBuffer.Builder()
+ .indexCount(3)
+ .bufferType(Filament.IndexBuffer$IndexType.USHORT)
+ .build(engine);
-this.ib.setBuffer(engine, new Uint16Array([0, 1, 2]));
+this.ib.setBuffer(engine, new Uint16Array([0, 1, 2]));
Note that constructing an index buffer is similar to constructing a vertex buffer, but it only has
@@ -167,29 +167,29 @@
Finish up initialization
next tutorial.
After extracting the material instance, we can finally create a renderable component for the
triangle by setting up a bounding box and passing in the vertex and index buffers.
-const mat = engine.createMaterial('triangle.filamat');
-const matinst = 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(engine, this.triangle);
+const mat = engine.createMaterial('triangle.filamat');
+const matinst = 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(engine, this.triangle);
Next let's wrap up the initialization routine by creating the swap chain, renderer, camera, and
view.
-this.swapChain = engine.createSwapChain();
-this.renderer = engine.createRenderer();
-this.camera = engine.createCamera(Filament.EntityManager.get().create());
-this.view = engine.createView();
-this.view.setCamera(this.camera);
-this.view.setScene(this.scene);
-
-// Set up a blue-green background:
-this.renderer.setClearOptions({clearColor: [0.0, 0.1, 0.2, 1.0], clear: true});
-
-// Adjust the initial viewport:
-this.resize();
+this.swapChain = engine.createSwapChain();
+this.renderer = engine.createRenderer();
+this.camera = engine.createCamera(Filament.EntityManager.get().create());
+this.view = engine.createView();
+this.view.setCamera(this.camera);
+this.view.setScene(this.scene);
+
+// Set up a blue-green background:
+this.renderer.setClearOptions({clearColor: [0.0, 0.1, 0.2, 1.0], clear: true});
+
+// Adjust the initial viewport:
+this.resize();
At this point, we're done creating all Filament entities, and the code should run without errors.
@@ -197,24 +197,24 @@
Finish up initialization
Render and resize handlers
Recall that our App class has a skeletal render method, which the browser calls every time it needs
to repaint. Often this is 60 times a second.
-render() {
- // TODO: render scene
- window.requestAnimationFrame(this.render);
-}
+render() {
+ // TODO: render scene
+ window.requestAnimationFrame(this.render);
+}
Let's flesh this out by rotating the triangle and invoking the Filament renderer. Add the following
code to the top of the render method.
-// Rotate the triangle.
-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.triangle);
-tcm.setTransform(inst, transform);
-inst.delete();
-
-// Render the frame.
-this.renderer.render(this.swapChain, this.view);
+// Rotate the triangle.
+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.triangle);
+tcm.setTransform(inst, transform);
+inst.delete();
+
+// Render the frame.
+this.renderer.render(this.swapChain, this.view);
The first half of our render method obtains the transform component of the triangle entity and uses
@@ -225,14 +225,14 @@
Render and resize handlers
One last step. Add the following code to the resize method. This adjusts the resolution of the
rendering surface when the window size changes, taking devicePixelRatio
into account for high-DPI
displays. It also adjusts the camera frustum accordingly.
-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;
-const Projection = Filament.Camera$Projection;
-this.camera.setProjection(Projection.ORTHO, -aspect, aspect, -1, 1, 0, 1);
+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;
+const Projection = Filament.Camera$Projection;
+this.camera.setProjection(Projection.ORTHO, -aspect, aspect, -1, 1, 0, 1);
You should now have a spinning triangle! The completed JavaScript is available
diff --git a/filament/backend/CMakeLists.txt b/filament/backend/CMakeLists.txt
index 946b55d1c7b..6f68d2802af 100644
--- a/filament/backend/CMakeLists.txt
+++ b/filament/backend/CMakeLists.txt
@@ -179,12 +179,15 @@ if (FILAMENT_SUPPORTS_VULKAN)
src/vulkan/VulkanPipelineCache.cpp
src/vulkan/VulkanPipelineCache.h
src/vulkan/VulkanPlatform.cpp
+ src/vulkan/VulkanReadPixels.cpp
+ src/vulkan/VulkanReadPixels.h
src/vulkan/VulkanSamplerCache.cpp
src/vulkan/VulkanSamplerCache.h
src/vulkan/VulkanStagePool.cpp
src/vulkan/VulkanStagePool.h
src/vulkan/VulkanSwapChain.cpp
src/vulkan/VulkanSwapChain.h
+ src/vulkan/VulkanTaskHandler.h
src/vulkan/VulkanTexture.cpp
src/vulkan/VulkanTexture.h
src/vulkan/VulkanUtility.cpp
diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm
index 27c57e32f9e..6b3c872efe1 100644
--- a/filament/backend/src/metal/MetalDriver.mm
+++ b/filament/backend/src/metal/MetalDriver.mm
@@ -1287,6 +1287,7 @@
fromRegion:srcRegion
mipmapLevel:0];
scheduleDestroy(std::move(*p));
+ delete p;
}];
}
diff --git a/filament/backend/src/opengl/GLUtils.h b/filament/backend/src/opengl/GLUtils.h
index bdb1327a285..d37da813044 100644
--- a/filament/backend/src/opengl/GLUtils.h
+++ b/filament/backend/src/opengl/GLUtils.h
@@ -120,7 +120,7 @@ constexpr inline GLenum getBufferBindingType(BufferObjectBinding bindingType) no
case BufferObjectBinding::UNIFORM:
return GL_UNIFORM_BUFFER;
case BufferObjectBinding::SHADER_STORAGE:
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
return GL_SHADER_STORAGE_BUFFER;
#else
utils::panic(__func__, __FILE__, __LINE__, "SHADER_STORAGE not supported");
@@ -423,7 +423,7 @@ constexpr /* inline */ GLenum getInternalFormat(TextureFormat format) noexcept {
case TextureFormat::RGBA32I: return GL_RGBA32I;
// compressed formats
-#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_4_3) || defined(GL_ARB_ES3_compatibility)
+#if defined(GL_ES_VERSION_3_0) || defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_ARB_ES3_compatibility)
case TextureFormat::EAC_R11: return GL_COMPRESSED_R11_EAC;
case TextureFormat::EAC_R11_SIGNED: return GL_COMPRESSED_SIGNED_R11_EAC;
case TextureFormat::EAC_RG11: return GL_COMPRESSED_RG11_EAC;
diff --git a/filament/backend/src/opengl/OpenGLContext.cpp b/filament/backend/src/opengl/OpenGLContext.cpp
index 48772dcdf99..d65b3b4043d 100644
--- a/filament/backend/src/opengl/OpenGLContext.cpp
+++ b/filament/backend/src/opengl/OpenGLContext.cpp
@@ -28,17 +28,17 @@ using namespace utils;
namespace filament::backend {
bool OpenGLContext::queryOpenGLVersion(GLint* major, GLint* minor) noexcept {
- if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
- char const* version = (char const*)glGetString(GL_VERSION);
- // This works on all versions of GLES
- int const n = version ? sscanf(version, "OpenGL ES %d.%d", major, minor) : 0;
- return n == 2;
- } else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
- // OpenGL version
- glGetIntegerv(GL_MAJOR_VERSION, major);
- glGetIntegerv(GL_MINOR_VERSION, minor);
- return (glGetError() == GL_NO_ERROR);
- }
+#ifdef BACKEND_OPENGL_VERSION_GLES
+ char const* version = (char const*)glGetString(GL_VERSION);
+ // This works on all versions of GLES
+ int const n = version ? sscanf(version, "OpenGL ES %d.%d", major, minor) : 0;
+ return n == 2;
+#else
+ // OpenGL version
+ glGetIntegerv(GL_MAJOR_VERSION, major);
+ glGetIntegerv(GL_MINOR_VERSION, minor);
+ return (glGetError() == GL_NO_ERROR);
+#endif
}
OpenGLContext::OpenGLContext() noexcept {
@@ -73,50 +73,48 @@ OpenGLContext::OpenGLContext() noexcept {
constexpr GLint MAX_VERTEX_SAMPLER_COUNT = caps3.MAX_VERTEX_SAMPLER_COUNT;
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
- if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
-#if defined(GL_ES_VERSION_2_0)
- initExtensionsGLES();
-#endif
- if (state.major == 3) {
- assert_invariant(gets.max_texture_image_units >= 16);
- assert_invariant(gets.max_combined_texture_image_units >= 32);
- if (state.minor >= 1) {
- features.multisample_texture = true;
- // figure out our feature level
- if (ext.EXT_texture_cube_map_array) {
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
- if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
- gets.max_combined_texture_image_units >=
- (MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
- mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
- }
- }
- }
- }
- } else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
-#if defined(GL_VERSION_4_1)
- initExtensionsGL();
-#endif
- if (state.major == 4) {
- assert_invariant(state.minor >= 1);
- mShaderModel = ShaderModel::DESKTOP;
- if (state.minor >= 3) {
- // cubemap arrays are available as of OpenGL 4.0
+#ifdef BACKEND_OPENGL_VERSION_GLES
+ initExtensionsGLES();
+ if (state.major == 3) {
+ // Runtime OpenGL version is ES 3.x
+ assert_invariant(gets.max_texture_image_units >= 16);
+ assert_invariant(gets.max_combined_texture_image_units >= 32);
+ if (state.minor >= 1) {
+ features.multisample_texture = true;
+ // figure out our feature level
+ if (ext.EXT_texture_cube_map_array) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
- // figure out our feature level
if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
gets.max_combined_texture_image_units >=
(MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
}
}
- features.multisample_texture = true;
}
- // feedback loops are allowed on GL desktop as long as writes are disabled
- bugs.allow_read_only_ancillary_feedback_loop = true;
- assert_invariant(gets.max_texture_image_units >= 16);
- assert_invariant(gets.max_combined_texture_image_units >= 32);
}
+#else
+ initExtensionsGL();
+ if (state.major == 4) {
+ assert_invariant(state.minor >= 1);
+ mShaderModel = ShaderModel::DESKTOP;
+ if (state.minor >= 3) {
+ // cubemap arrays are available as of OpenGL 4.0
+ mFeatureLevel = FeatureLevel::FEATURE_LEVEL_2;
+ // figure out our feature level
+ if (gets.max_texture_image_units >= MAX_FRAGMENT_SAMPLER_COUNT &&
+ gets.max_combined_texture_image_units >=
+ (MAX_FRAGMENT_SAMPLER_COUNT + MAX_VERTEX_SAMPLER_COUNT)) {
+ mFeatureLevel = FeatureLevel::FEATURE_LEVEL_3;
+ }
+ }
+ features.multisample_texture = true;
+ }
+ // feedback loops are allowed on GL desktop as long as writes are disabled
+ bugs.allow_read_only_ancillary_feedback_loop = true;
+ assert_invariant(gets.max_texture_image_units >= 16);
+ assert_invariant(gets.max_combined_texture_image_units >= 32);
+#endif
+
#ifdef GL_EXT_texture_filter_anisotropic
if (ext.EXT_texture_filter_anisotropic) {
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gets.max_anisotropy);
@@ -337,7 +335,7 @@ void OpenGLContext::setDefaultState() noexcept {
// Point sprite size and seamless cubemap filtering are disabled by default in desktop GL.
// In OpenGL ES, these flags do not exist because they are always on.
-#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
+#ifdef BACKEND_OPENGL_VERSION_GL
glEnable(GL_PROGRAM_POINT_SIZE);
enable(GL_PROGRAM_POINT_SIZE);
#endif
@@ -351,14 +349,16 @@ void OpenGLContext::setDefaultState() noexcept {
glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
#endif
-#if defined(GL_EXT_clip_control) || defined(GL_ARB_clip_control) || defined(GL_VERSION_4_5)
if (ext.EXT_clip_control) {
+#if defined(BACKEND_OPENGL_VERSION_GL)
glClipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE);
- }
+#elif defined(GL_EXT_clip_control)
+ glClipControlEXT(GL_LOWER_LEFT_EXT, GL_ZERO_TO_ONE_EXT);
#endif
+ }
}
-#if defined(GL_ES_VERSION_2_0)
+#ifdef BACKEND_OPENGL_VERSION_GLES
void OpenGLContext::initExtensionsGLES() noexcept {
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
@@ -394,7 +394,6 @@ void OpenGLContext::initExtensionsGLES() noexcept {
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
- ext.QCOM_tiled_rendering = exts.has("GL_QCOM_tiled_rendering"sv);
ext.WEBGL_compressed_texture_etc = exts.has("WEBGL_compressed_texture_etc"sv);
ext.WEBGL_compressed_texture_s3tc = exts.has("WEBGL_compressed_texture_s3tc"sv);
ext.WEBGL_compressed_texture_s3tc_srgb = exts.has("WEBGL_compressed_texture_s3tc_srgb"sv);
@@ -404,9 +403,9 @@ void OpenGLContext::initExtensionsGLES() noexcept {
}
}
-#endif // defined(GL_ES_VERSION_2_0)
+#endif // BACKEND_OPENGL_VERSION_GLES
-#if defined(GL_VERSION_4_1)
+#ifdef BACKEND_OPENGL_VERSION_GL
void OpenGLContext::initExtensionsGL() noexcept {
GLUtils::unordered_string_set exts;
@@ -427,7 +426,7 @@ void OpenGLContext::initExtensionsGL() noexcept {
auto minor = state.minor;
ext.APPLE_color_buffer_packed_float = true; // Assumes core profile.
ext.ARB_shading_language_packing = exts.has("GL_ARB_shading_language_packing"sv) || (major == 4 && minor >= 2);
- ext.EXT_clip_control = exts.has("GL_ARB_clip_control"sv) || (major == 4 && minor >= 5);
+ ext.EXT_clip_control = (major == 4 && minor >= 5);
ext.EXT_color_buffer_float = true; // Assumes core profile.
ext.EXT_color_buffer_half_float = true; // Assumes core profile.
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
@@ -448,13 +447,12 @@ void OpenGLContext::initExtensionsGL() noexcept {
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_EGL_image_external_essl3 = false;
- ext.QCOM_tiled_rendering = false;
ext.WEBGL_compressed_texture_etc = false;
ext.WEBGL_compressed_texture_s3tc = false;
ext.WEBGL_compressed_texture_s3tc_srgb = false;
}
-#endif // defined(GL_VERSION_4_1)
+#endif // BACKEND_OPENGL_VERSION_GL
void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept {
if (target == GL_ELEMENT_ARRAY_BUFFER) {
@@ -636,7 +634,7 @@ void OpenGLContext::resetState() noexcept {
GLenum const bufferTargets[] = {
GL_UNIFORM_BUFFER,
GL_TRANSFORM_FEEDBACK_BUFFER,
-#if !defined(__EMSCRIPTEN__) && (defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1))
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
GL_SHADER_STORAGE_BUFFER,
#endif
GL_ARRAY_BUFFER,
@@ -667,14 +665,14 @@ void OpenGLContext::resetState() noexcept {
{ GL_TEXTURE_2D_ARRAY, true },
{ GL_TEXTURE_CUBE_MAP, true },
{ GL_TEXTURE_3D, true },
-#if !defined(__EMSCRIPTEN__)
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
{ GL_TEXTURE_2D_MULTISAMPLE, true },
#endif
+#if !defined(__EMSCRIPTEN__)
#if defined(GL_OES_EGL_image_external)
{ GL_TEXTURE_EXTERNAL_OES, ext.OES_EGL_image_external_essl3 },
#endif
-#if defined(GL_VERSION_4_1) || defined(GL_EXT_texture_cube_map_array)
+#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_texture_cube_map_array)
{ GL_TEXTURE_CUBE_MAP_ARRAY, ext.EXT_texture_cube_map_array },
#endif
#endif
diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h
index ded404f83aa..4b54cc1cf29 100644
--- a/filament/backend/src/opengl/OpenGLContext.h
+++ b/filament/backend/src/opengl/OpenGLContext.h
@@ -69,6 +69,24 @@ class OpenGLContext {
OpenGLContext() noexcept;
+ constexpr bool isAtLeastGL(int major, int minor) const noexcept {
+#ifdef BACKEND_OPENGL_VERSION_GL
+ return state.major > major || (state.major == major && state.minor >= minor);
+#else
+ (void)major, (void)minor;
+ return false;
+#endif
+ }
+
+ constexpr bool isAtLeastGLES(int major, int minor) const noexcept {
+#ifdef BACKEND_OPENGL_VERSION_GLES
+ return state.major > major || (state.major == major && state.minor >= minor);
+#else
+ (void)major, (void)minor;
+ return false;
+#endif
+ }
+
constexpr static inline size_t getIndexForTextureTarget(GLuint target) noexcept;
constexpr inline size_t getIndexForCap(GLenum cap) noexcept;
constexpr static inline size_t getIndexForBufferTarget(GLenum target) noexcept;
@@ -184,7 +202,6 @@ class OpenGLContext {
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool OES_EGL_image_external_essl3;
- bool QCOM_tiled_rendering;
bool WEBGL_compressed_texture_etc;
bool WEBGL_compressed_texture_s3tc;
bool WEBGL_compressed_texture_s3tc_srgb;
@@ -413,10 +430,10 @@ class OpenGLContext {
RenderPrimitive mDefaultVAO;
// this is chosen to minimize code size
-#if defined(GL_ES_VERSION_2_0)
+#if defined(BACKEND_OPENGL_VERSION_GLES)
void initExtensionsGLES() noexcept;
#endif
-#if defined(GL_VERSION_4_1)
+#if defined(BACKEND_OPENGL_VERSION_GL)
void initExtensionsGL() noexcept;
#endif
@@ -443,7 +460,7 @@ constexpr size_t OpenGLContext::getIndexForTextureTarget(GLuint target) noexcept
case GL_TEXTURE_2D: return 0;
case GL_TEXTURE_2D_ARRAY: return 1;
case GL_TEXTURE_CUBE_MAP: return 2;
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_TEXTURE_2D_MULTISAMPLE: return 3;
#endif
case GL_TEXTURE_EXTERNAL_OES: return 4;
@@ -469,7 +486,7 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
#ifdef GL_ARB_seamless_cube_map
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break;
#endif
-#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
+#ifdef BACKEND_OPENGL_VERSION_GL
case GL_PROGRAM_POINT_SIZE: index = 10; break;
#endif
default: break;
@@ -484,7 +501,7 @@ constexpr size_t OpenGLContext::getIndexForBufferTarget(GLenum target) noexcept
// The indexed buffers MUST be first in this list (those usable with bindBufferRange)
case GL_UNIFORM_BUFFER: index = 0; break;
case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break;
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_SHADER_STORAGE_BUFFER: index = 2; break;
#endif
case GL_ARRAY_BUFFER: index = 3; break;
diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp
index 0a3ab8ac9a2..c7f5c2d832c 100644
--- a/filament/backend/src/opengl/OpenGLDriver.cpp
+++ b/filament/backend/src/opengl/OpenGLDriver.cpp
@@ -131,18 +131,18 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
return {};
}
- if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
- if (UTILS_UNLIKELY(!(major >= 3 && minor >= 0))) {
- PANIC_LOG("OpenGL ES 3.0 minimum needed (current %d.%d)", major, minor);
- goto cleanup;
- }
- } else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
- // we require GL 4.1 headers and minimum version
- if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
- PANIC_LOG("OpenGL 4.1 minimum needed (current %d.%d)", major, minor);
- goto cleanup;
- }
+#if defined(BACKEND_OPENGL_VERSION_GLES)
+ if (UTILS_UNLIKELY(!(major >= 3 && minor >= 0))) {
+ PANIC_LOG("OpenGL ES 3.0 minimum needed (current %d.%d)", major, minor);
+ goto cleanup;
+ }
+#else
+ // we require GL 4.1 headers and minimum version
+ if (UTILS_UNLIKELY(!((major == 4 && minor >= 1) || major > 4))) {
+ PANIC_LOG("OpenGL 4.1 minimum needed (current %d.%d)", major, minor);
+ goto cleanup;
}
+#endif
size_t const defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
Platform::DriverConfig const validConfig {
@@ -182,9 +182,13 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
// Timer queries are core in GL 3.3, otherwise we need EXT_disjoint_timer_query
// iOS headers don't define GL_EXT_disjoint_timer_query, so make absolutely sure
// we won't use it.
-#if defined(GL_VERSION_3_3) || defined(GL_EXT_disjoint_timer_query)
- if (mContext.ext.EXT_disjoint_timer_query ||
- BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
+
+#if defined(BACKEND_OPENGL_VERSION_GL)
+ assert_invariant(mContext.ext.EXT_disjoint_timer_query);
+#endif
+
+#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
+ if (mContext.ext.EXT_disjoint_timer_query) {
// timer queries are available
if (mContext.bugs.dont_use_timer_query && mPlatform.canCreateFence()) {
// however, they don't work well, revert to using fences if we can.
@@ -545,25 +549,29 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
GLsizei(width), GLsizei(height), GLsizei(depth) * 6);
break;
}
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#ifdef BACKEND_OPENGL_LEVEL_GLES31
case GL_TEXTURE_2D_MULTISAMPLE:
if constexpr (TEXTURE_2D_MULTISAMPLE_SUPPORTED) {
// NOTE: if there is a mix of texture and renderbuffers, "fixed_sample_locations" must be true
// NOTE: what's the benefit of setting "fixed_sample_locations" to false?
-#if BACKEND_OPENGL_LEVEL >= BACKEND_OPENGL_LEVEL_GLES31
- // only supported from GL 4.3 and GLES 3.1 headers
- glTexStorage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
- GLsizei(width), GLsizei(height), GL_TRUE);
-#elif defined(GL_VERSION_4_1)
- // only supported in GL (GL4.1 doesn't support glTexStorage2DMultisample)
- glTexImage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
- GLsizei(width), GLsizei(height), GL_TRUE);
+
+ if (mContext.isAtLeastGL(4, 3) || mContext.isAtLeastGLES(3, 1)) {
+ // only supported from GL 4.3 and GLES 3.1 headers
+ glTexStorage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
+ GLsizei(width), GLsizei(height), GL_TRUE);
+ }
+#ifdef BACKEND_OPENGL_VERSION_GL
+ else {
+ // only supported in GL (GL4.1 doesn't support glTexStorage2DMultisample)
+ glTexImage2DMultisample(t->gl.target, t->samples, t->gl.internalFormat,
+ GLsizei(width), GLsizei(height), GL_TRUE);
+ }
#endif
} else {
PANIC_LOG("GL_TEXTURE_2D_MULTISAMPLE is not supported");
}
break;
-#endif
+#endif // BACKEND_OPENGL_LEVEL_GLES31
default: // cannot happen
break;
}
@@ -631,7 +639,7 @@ void OpenGLDriver::createTextureR(Handle th, SamplerType target, uint
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
@@ -733,7 +741,7 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t id,
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
@@ -922,7 +930,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
case GL_TEXTURE_2D:
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
case GL_TEXTURE_2D_MULTISAMPLE:
#endif
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
@@ -1649,7 +1657,7 @@ bool OpenGLDriver::isRenderTargetFormatSupported(TextureFormat format) {
// Three-component SRGB is a color-renderable texture format in core OpenGL on desktop.
case TextureFormat::SRGB8:
- return BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL;
+ return mContext.isAtLeastGL(4, 5);
// Half-float formats, requires extension.
case TextureFormat::R16F:
@@ -1979,7 +1987,7 @@ void OpenGLDriver::generateMipmaps(Handle th) {
auto& gl = mContext;
GLTexture* t = handle_cast(th);
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
assert_invariant(t->gl.target != GL_TEXTURE_2D_MULTISAMPLE);
#endif
// Note: glGenerateMimap can also fail if the internal format is not both
@@ -2385,21 +2393,20 @@ void OpenGLDriver::beginRenderPass(Handle rth,
gl.bindFramebuffer(GL_FRAMEBUFFER, rt->gl.fbo);
CHECK_GL_FRAMEBUFFER_STATUS(utils::slog.e, GL_FRAMEBUFFER)
- // glInvalidateFramebuffer appeared on GLES 3.0 and GL4.3, for simplicity we just
- // ignore it on GL (rather than having to do a runtime check).
- if (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES &&
- BACKEND_OPENGL_LEVEL >= BACKEND_OPENGL_LEVEL_GLES30) {
- if (!gl.bugs.disable_invalidate_framebuffer) {
- AttachmentArray attachments; // NOLINT
- GLsizei const attachmentCount = getAttachments(attachments, rt, discardFlags);
- if (attachmentCount) {
- glInvalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
- }
- CHECK_GL_ERROR(utils::slog.e)
+ // glInvalidateFramebuffer appeared on GLES 3.0 and GL4.3
+#if defined(BACKEND_OPENGL_LEVEL_GLES30) || defined(BACKEND_OPENGL_VERSION_GL)
+ if ((mContext.isAtLeastGLES(3, 0) || mContext.isAtLeastGL(4, 3))
+ && !gl.bugs.disable_invalidate_framebuffer) {
+ AttachmentArray attachments; // NOLINT
+ GLsizei const attachmentCount = getAttachments(attachments, rt, discardFlags);
+ if (attachmentCount) {
+ glInvalidateFramebuffer(GL_FRAMEBUFFER, attachmentCount, attachments.data());
}
- } else {
- // on GL desktop we assume we don't have glInvalidateFramebuffer, but even if the GPU is
- // not a tiler, it's important to clear the framebuffer before drawing, as it resets
+ CHECK_GL_ERROR(utils::slog.e)
+ } else
+#endif
+ {
+ // It's important to clear the framebuffer before drawing, as it resets
// the fb to a known state (resets fb compression and possibly other things).
// So we use glClear instead of glInvalidateFramebuffer
gl.disable(GL_SCISSOR_TEST);
@@ -2471,8 +2478,8 @@ void OpenGLDriver::endRenderPass(int) {
// glInvalidateFramebuffer appeared on GLES 3.0 and GL4.3, for simplicity we just
// ignore it on GL (rather than having to do a runtime check).
- if (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES &&
- BACKEND_OPENGL_LEVEL >= BACKEND_OPENGL_LEVEL_GLES30) {
+#if defined(BACKEND_OPENGL_LEVEL_GLES30) || defined(BACKEND_OPENGL_VERSION_GL)
+ if (mContext.isAtLeastGLES(3, 0) || mContext.isAtLeastGL(4, 3)) {
auto effectiveDiscardFlags = discardFlags;
if (gl.bugs.invalidate_end_only_if_invalidate_start) {
effectiveDiscardFlags &= mRenderPassParams.flags.discardStart;
@@ -2488,6 +2495,7 @@ void OpenGLDriver::endRenderPass(int) {
CHECK_GL_ERROR(utils::slog.e)
}
}
+#endif
#ifndef NDEBUG
// clear the discarded buffers in debug builds
@@ -3232,7 +3240,7 @@ void OpenGLDriver::dispatchCompute(Handle program, math::uint3 workGr
useProgram(p);
-#if defined(GL_ES_VERSION_3_1) || defined(GL_VERSION_4_3)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
#if defined(__ANDROID__)
// on Android, GLES3.1 and above entry-points are defined in glext
@@ -3241,7 +3249,7 @@ void OpenGLDriver::dispatchCompute(Handle program, math::uint3 workGr
#endif
glDispatchCompute(workGroupCount.x, workGroupCount.y, workGroupCount.z);
-#endif
+#endif // BACKEND_OPENGL_LEVEL_GLES31
#ifdef FILAMENT_ENABLE_MATDBG
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
diff --git a/filament/backend/src/opengl/OpenGLProgram.cpp b/filament/backend/src/opengl/OpenGLProgram.cpp
index 2605306dac1..e0ec9dad0f9 100644
--- a/filament/backend/src/opengl/OpenGLProgram.cpp
+++ b/filament/backend/src/opengl/OpenGLProgram.cpp
@@ -130,7 +130,7 @@ void OpenGLProgram::compileShaders(OpenGLContext& context,
glShaderType = GL_FRAGMENT_SHADER;
break;
case ShaderStage::COMPUTE:
-#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
+#if defined(BACKEND_OPENGL_LEVEL_GLES31)
glShaderType = GL_COMPUTE_SHADER;
#else
continue;
@@ -192,13 +192,12 @@ std::string_view OpenGLProgram::process_GOOGLE_cpp_style_line_directive(OpenGLCo
return { source, len };
}
-// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 and
+// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
std::string_view OpenGLProgram::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
using namespace std::literals;
- if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
- if (context.state.major == 4 && context.state.minor == 1 &&
- !context.ext.ARB_shading_language_packing) {
+#ifdef BACKEND_OPENGL_VERSION_GL
+ if (!context.isAtLeastGL(4, 2) && !context.ext.ARB_shading_language_packing) {
return R"(
// these don't handle denormals, NaNs or inf
@@ -236,7 +235,7 @@ highp uint packHalf2x16(vec2 v) {
}
)"sv;
}
- }
+#endif // BACKEND_OPENGL_VERSION_GL
return ""sv;
}
diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.cpp b/filament/backend/src/opengl/OpenGLTimerQuery.cpp
index dd07ac30f89..20e6f1aa52e 100644
--- a/filament/backend/src/opengl/OpenGLTimerQuery.cpp
+++ b/filament/backend/src/opengl/OpenGLTimerQuery.cpp
@@ -34,7 +34,7 @@ OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default;
// ------------------------------------------------------------------------------------------------
-#if defined(GL_VERSION_3_3) || defined(GL_EXT_disjoint_timer_query)
+#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
TimerQueryNative::TimerQueryNative(OpenGLContext&) {
}
diff --git a/filament/backend/src/opengl/OpenGLTimerQuery.h b/filament/backend/src/opengl/OpenGLTimerQuery.h
index e6fc88e2c2f..e11235042b8 100644
--- a/filament/backend/src/opengl/OpenGLTimerQuery.h
+++ b/filament/backend/src/opengl/OpenGLTimerQuery.h
@@ -48,7 +48,7 @@ class OpenGLTimerQueryInterface {
virtual uint64_t queryResult(GLTimerQuery* query) = 0;
};
-#if defined(GL_VERSION_3_3) || defined(GL_EXT_disjoint_timer_query)
+#if defined(BACKEND_OPENGL_VERSION_GL) || defined(GL_EXT_disjoint_timer_query)
class TimerQueryNative : public OpenGLTimerQueryInterface {
public:
diff --git a/filament/backend/src/opengl/gl_headers.cpp b/filament/backend/src/opengl/gl_headers.cpp
index 2c29aae796f..5253d3dcb65 100644
--- a/filament/backend/src/opengl/gl_headers.cpp
+++ b/filament/backend/src/opengl/gl_headers.cpp
@@ -28,10 +28,6 @@ static void getProcAddress(T& pfn, const char* name) noexcept {
}
namespace glext {
-#ifdef GL_QCOM_tiled_rendering
-PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
-PFNGLENDTILINGQCOMPROC glEndTilingQCOM;
-#endif
#ifdef GL_OES_EGL_image
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
@@ -52,7 +48,7 @@ PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#endif
#ifdef GL_EXT_clip_control
-PFNGLCLIPCONTROLEXTPROC glClipControl;
+PFNGLCLIPCONTROLEXTPROC glClipControlEXT;
#endif
#if defined(__ANDROID__)
@@ -65,31 +61,27 @@ static std::once_flag sGlExtInitialized;
void importGLESExtensionsEntryPoints() {
std::call_once(sGlExtInitialized, +[]() {
-#ifdef GL_QCOM_tiled_rendering
- getProcAddress(glStartTilingQCOM, "glStartTilingQCOM");
- getProcAddress(glEndTilingQCOM, "glEndTilingQCOM");
-#endif
#ifdef GL_OES_EGL_image
- getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
+ getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
#endif
#if GL_EXT_debug_marker
- getProcAddress(glInsertEventMarkerEXT, "glInsertEventMarkerEXT");
- getProcAddress(glPushGroupMarkerEXT, "glPushGroupMarkerEXT");
- getProcAddress(glPopGroupMarkerEXT, "glPopGroupMarkerEXT");
+ getProcAddress(glInsertEventMarkerEXT, "glInsertEventMarkerEXT");
+ getProcAddress(glPushGroupMarkerEXT, "glPushGroupMarkerEXT");
+ getProcAddress(glPopGroupMarkerEXT, "glPopGroupMarkerEXT");
#endif
#if GL_EXT_multisampled_render_to_texture
- getProcAddress(glFramebufferTexture2DMultisampleEXT, "glFramebufferTexture2DMultisampleEXT");
- getProcAddress(glRenderbufferStorageMultisampleEXT, "glRenderbufferStorageMultisampleEXT");
+ getProcAddress(glFramebufferTexture2DMultisampleEXT, "glFramebufferTexture2DMultisampleEXT");
+ getProcAddress(glRenderbufferStorageMultisampleEXT, "glRenderbufferStorageMultisampleEXT");
#endif
#ifdef GL_KHR_debug
- getProcAddress(glDebugMessageCallbackKHR, "glDebugMessageCallbackKHR");
- getProcAddress(glGetDebugMessageLogKHR, "glGetDebugMessageLogKHR");
+ getProcAddress(glDebugMessageCallbackKHR, "glDebugMessageCallbackKHR");
+ getProcAddress(glGetDebugMessageLogKHR, "glGetDebugMessageLogKHR");
#endif
#ifdef GL_EXT_disjoint_timer_query
- getProcAddress(glGetQueryObjectui64v, "glGetQueryObjectui64vEXT");
+ getProcAddress(glGetQueryObjectui64v, "glGetQueryObjectui64vEXT");
#endif
#ifdef GL_EXT_clip_control
- getProcAddress(glClipControl, "glClipControlEXT");
+ getProcAddress(glClipControlEXT, "glClipControlEXT");
#endif
#if defined(__ANDROID__)
getProcAddress(glDispatchCompute, "glDispatchCompute");
diff --git a/filament/backend/src/opengl/gl_headers.h b/filament/backend/src/opengl/gl_headers.h
index e59d341cc29..46c0e8362f0 100644
--- a/filament/backend/src/opengl/gl_headers.h
+++ b/filament/backend/src/opengl/gl_headers.h
@@ -17,6 +17,26 @@
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GL_HEADERS_H
#define TNT_FILAMENT_BACKEND_OPENGL_GL_HEADERS_H
+/*
+ * Configuration we aim to support:
+ *
+ * GL 4.5 headers
+ * - GL 4.1 runtime (for macOS)
+ * - GL 4.5 runtime
+ *
+ * GLES 2.0 headers
+ * - GLES 2.0 runtime Android only
+ *
+ * GLES 3.0 headers
+ * - GLES 3.0 runtime iOS and WebGL2 only
+ *
+ * GLES 3.1 headers
+ * - GLES 2.0 runtime
+ * - GLES 3.0 runtime
+ * - GLES 3.1 runtime
+ */
+
+
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
#if defined(__EMSCRIPTEN__)
@@ -46,12 +66,22 @@
#endif
+/* Validate the header configurations we aim to support */
-#if (!defined(GL_ES_VERSION_2_0) && !defined(GL_VERSION_4_1))
-#error "Minimum header version must be OpenGL ES 2.0 or OpenGL 4.1"
+#if defined(GL_VERSION_4_5)
+#elif defined(GL_ES_VERSION_3_1)
+#elif defined(GL_ES_VERSION_3_0)
+# if !defined(IOS) && !defined(__EMSCRIPTEN__)
+# error "GLES 3.0 headers only supported on iOS and WebGL2"
+# endif
+#elif defined(GL_ES_VERSION_2_0)
+# if !defined(__ANDROID__)
+# error "GLES 2.0 headers only supported on Android"
+# endif
+#else
+# error "Minimum header version must be OpenGL ES 2.0 or OpenGL 4.5"
#endif
-
/*
* GLES extensions
*/
@@ -72,10 +102,6 @@ namespace glext {
// it is currently called from PlatformEGL.
void importGLESExtensionsEntryPoints();
-#ifdef GL_QCOM_tiled_rendering
-extern PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
-extern PFNGLENDTILINGQCOMPROC glEndTilingQCOM;
-#endif
#ifdef GL_OES_EGL_image
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
@@ -93,7 +119,7 @@ extern PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
extern PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
#endif
#ifdef GL_EXT_clip_control
-extern PFNGLCLIPCONTROLEXTPROC glClipControl;
+extern PFNGLCLIPCONTROLEXTPROC glClipControlEXT;
#endif
#ifdef GL_EXT_disjoint_timer_query
extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
@@ -157,29 +183,25 @@ void glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void *d
}
#endif
-
-#define BACKEND_OPENGL_VERSION_GLES 0
-#define BACKEND_OPENGL_VERSION_GL 1
#if defined(GL_ES_VERSION_2_0)
-# define BACKEND_OPENGL_VERSION BACKEND_OPENGL_VERSION_GLES
-#elif defined(GL_VERSION_4_1)
-# define BACKEND_OPENGL_VERSION BACKEND_OPENGL_VERSION_GL
+# define BACKEND_OPENGL_VERSION_GLES
+#elif defined(GL_VERSION_4_5)
+# define BACKEND_OPENGL_VERSION_GL
+#else
+# error "Unsupported header version"
#endif
-#define BACKEND_OPENGL_LEVEL_GLES20 0
-#define BACKEND_OPENGL_LEVEL_GLES30 1
-#define BACKEND_OPENGL_LEVEL_GLES31 2
-
-#if defined(GL_VERSION_4_1)
-# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES30
+#if defined(GL_VERSION_4_5) || defined(GL_ES_VERSION_3_1)
+# define BACKEND_OPENGL_LEVEL_GLES31
+# ifdef __EMSCRIPTEN__
+# error "__EMSCRIPTEN__ shouldn't be defined with GLES 3.1 headers"
+# endif
#endif
-
-#if defined(GL_ES_VERSION_3_1)
-# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES31
-#elif defined(GL_ES_VERSION_3_0)
-# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES30
-#elif defined(GL_ES_VERSION_2_0)
-# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES20
+#if defined(GL_VERSION_4_5) || defined(GL_ES_VERSION_3_0)
+# define BACKEND_OPENGL_LEVEL_GLES30
+#endif
+#if defined(GL_VERSION_4_5) || defined(GL_ES_VERSION_2_0)
+# define BACKEND_OPENGL_LEVEL_GLES20
#endif
#include "NullGLES.h"
diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp
index d6ada095b3e..01493659750 100644
--- a/filament/backend/src/vulkan/VulkanDriver.cpp
+++ b/filament/backend/src/vulkan/VulkanDriver.cpp
@@ -75,6 +75,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform,
mContext.commands->setObserver(&mPipelineCache);
mPipelineCache.setDevice(mContext.device, mContext.allocator);
mPipelineCache.setDummyTexture(mContext.emptyTexture->getPrimaryImageView());
+
+ mReadPixels.initialize(mContext.device);
}
VulkanDriver::~VulkanDriver() noexcept = default;
@@ -134,6 +136,9 @@ void VulkanDriver::terminate() {
void VulkanDriver::tick(int) {
mContext.commands->updateFences();
+
+ // Handle any posted tasks
+ runTaskHandler();
}
// Garbage collection should not occur too frequently, only about once per frame. Internally, the
@@ -1011,31 +1016,35 @@ void VulkanDriver::beginRenderPass(Handle rth, const RenderPassP
rt->transformClientRectToPlatform(&renderPassInfo.renderArea);
- VkClearValue clearValues[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + 1] = {};
-
- // NOTE: clearValues must be populated in the same order as the attachments array in
- // VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
- // actually clearing that particular target.
- for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
- if (fbkey.color[i]) {
- VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
- clearValue.color.float32[0] = params.clearColor.r;
- clearValue.color.float32[1] = params.clearColor.g;
- clearValue.color.float32[2] = params.clearColor.b;
- clearValue.color.float32[3] = params.clearColor.a;
+ VkClearValue clearValues[
+ MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT +
+ 1] = {};
+ if (params.flags.clear != TargetBufferFlags::NONE) {
+
+ // NOTE: clearValues must be populated in the same order as the attachments array in
+ // VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
+ // actually clearing that particular target.
+ for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
+ if (fbkey.color[i]) {
+ VkClearValue &clearValue = clearValues[renderPassInfo.clearValueCount++];
+ clearValue.color.float32[0] = params.clearColor.r;
+ clearValue.color.float32[1] = params.clearColor.g;
+ clearValue.color.float32[2] = params.clearColor.b;
+ clearValue.color.float32[3] = params.clearColor.a;
+ }
}
- }
- // Resolve attachments are not cleared but still have entries in the list, so skip over them.
- for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
- if (rpkey.needsResolveMask & (1u << i)) {
- renderPassInfo.clearValueCount++;
+ // Resolve attachments are not cleared but still have entries in the list, so skip over them.
+ for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
+ if (rpkey.needsResolveMask & (1u << i)) {
+ renderPassInfo.clearValueCount++;
+ }
}
+ if (fbkey.depth) {
+ VkClearValue &clearValue = clearValues[renderPassInfo.clearValueCount++];
+ clearValue.depthStencil = {(float) params.clearDepth, 0};
+ }
+ renderPassInfo.pClearValues = &clearValues[0];
}
- if (fbkey.depth) {
- VkClearValue& clearValue = clearValues[renderPassInfo.clearValueCount++];
- clearValue.depthStencil = {(float) params.clearDepth, 0};
- }
- renderPassInfo.pClearValues = &clearValues[0];
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
@@ -1321,152 +1330,18 @@ void VulkanDriver::stopCapture(int) {
}
-void VulkanDriver::readPixels(Handle src, uint32_t x, uint32_t y,
- uint32_t width, uint32_t height, PixelBufferDescriptor&& pbd) {
- const VkDevice device = mContext.device;
+void VulkanDriver::readPixels(Handle src, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height, PixelBufferDescriptor&& pbd) {
VulkanRenderTarget* srcTarget = handle_cast(src);
- VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
- assert_invariant(srcTexture);
- const VkFormat srcFormat = srcTexture->getVkFormat();
- const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM;
-
- // Create a host visible, linearly tiled image as a staging area.
-
- VkImageCreateInfo imageInfo {
- .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
- .imageType = VK_IMAGE_TYPE_2D,
- .format = srcFormat,
- .extent = { width, height, 1 },
- .mipLevels = 1,
- .arrayLayers = 1,
- .samples = VK_SAMPLE_COUNT_1_BIT,
- .tiling = VK_IMAGE_TILING_LINEAR,
- .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
- .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- };
-
- VkImage stagingImage;
- vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
-
- VkMemoryRequirements memReqs;
- VkDeviceMemory stagingMemory;
- vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
- VkMemoryAllocateInfo allocInfo = {
- .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
- .allocationSize = memReqs.size,
- .memoryTypeIndex = mContext.selectMemoryType(memReqs.memoryTypeBits,
- VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
- VK_MEMORY_PROPERTY_HOST_CACHED_BIT)
- };
-
- vkAllocateMemory(device, &allocInfo, nullptr, &stagingMemory);
- vkBindImageMemory(device, stagingImage, stagingMemory, 0);
-
- // TODO: don't flush/wait here, this should be asynchronous
-
- mContext.commands->flush();
- mContext.commands->wait();
-
- // Transition the staging image layout.
-
- const VkCommandBuffer cmdbuffer = mContext.commands->get().cmdbuffer;
-
- transitionImageLayout(cmdbuffer, {
- .image = stagingImage,
- .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- .newLayout = VK_IMAGE_LAYOUT_GENERAL,
- .subresources = {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .baseMipLevel = 0,
- .levelCount = 1,
- .baseArrayLayer = 0,
- .layerCount = 1,
- },
- .srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
- .srcAccessMask = 0,
- .dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
- .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
- });
-
- const VulkanAttachment srcAttachment = srcTarget->getColor(0);
-
- VkImageCopy imageCopyRegion = {
- .srcSubresource = {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .mipLevel = srcAttachment.level,
- .baseArrayLayer = srcAttachment.layer,
- .layerCount = 1,
- },
- .srcOffset = {
- .x = (int32_t) x,
- .y = (int32_t) (srcTarget->getExtent().height - (height + y)),
- },
- .dstSubresource = {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .layerCount = 1,
- },
- .extent = {
- .width = width,
- .height = height,
- .depth = 1,
- },
- };
-
- // Transition the source image layout (which might be the swap chain)
-
- const VkImageSubresourceRange srcRange = {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .baseMipLevel = srcAttachment.level,
- .levelCount = 1,
- .baseArrayLayer = srcAttachment.layer,
- .layerCount = 1,
- };
-
- srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
-
- // Perform the copy into the staging area. At this point we know that the src layout is
- // TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
-
- UTILS_UNUSED_IN_RELEASE VkExtent2D srcExtent = srcAttachment.getExtent2D();
- assert_invariant(imageCopyRegion.srcOffset.x + imageCopyRegion.extent.width <= srcExtent.width);
- assert_invariant(imageCopyRegion.srcOffset.y + imageCopyRegion.extent.height <= srcExtent.height);
-
- vkCmdCopyImage(cmdbuffer, srcAttachment.getImage(),
- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, stagingImage, VK_IMAGE_LAYOUT_GENERAL,
- 1, &imageCopyRegion);
-
- // Restore the source image layout. Between driver API calls, color images are always kept in
- // UNDEFINED layout or in their "usage default" layout (see comment for getDefaultImageLayout).
-
- srcTexture->transitionLayout(cmdbuffer, srcRange,
- getDefaultImageLayout(TextureUsage::COLOR_ATTACHMENT));
-
- // TODO: don't flush/wait here -- we should do this asynchronously
-
- // Flush and wait.
- mContext.commands->flush();
- mContext.commands->wait();
-
- VkImageSubresource subResource { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT };
- VkSubresourceLayout subResourceLayout;
- vkGetImageSubresourceLayout(device, stagingImage, &subResource, &subResourceLayout);
-
- // Map image memory so we can start copying from it.
-
- const uint8_t* srcPixels;
- vkMapMemory(device, stagingMemory, 0, VK_WHOLE_SIZE, 0, (void**) &srcPixels);
- srcPixels += subResourceLayout.offset;
-
- if (!DataReshaper::reshapeImage(&pbd, getComponentType(srcFormat), getComponentCount(srcFormat),
- srcPixels, subResourceLayout.rowPitch, width, height, swizzle)) {
- utils::slog.e << "Unsupported PixelDataFormat or PixelDataType" << utils::io::endl;
- }
-
- vkUnmapMemory(device, stagingMemory);
- vkDestroyImage(device, stagingImage, nullptr);
- vkFreeMemory(device, stagingMemory, nullptr);
-
- scheduleDestroy(std::move(pbd));
+ mReadPixels.run(
+ srcTarget, x, y, width, height, mContext.graphicsQueueFamilyIndex, std::move(pbd),
+ getTaskHandler(),
+ [&context = mContext](uint32_t reqs, VkFlags flags) {
+ return context.selectMemoryType(reqs, flags);
+ },
+ [this](PixelBufferDescriptor&& pbd) {
+ this->scheduleDestroy(std::move(pbd));
+ });
}
void VulkanDriver::readBufferSubData(backend::BufferObjectHandle boh,
diff --git a/filament/backend/src/vulkan/VulkanDriver.h b/filament/backend/src/vulkan/VulkanDriver.h
index e403d0926fe..793f08bd9f3 100644
--- a/filament/backend/src/vulkan/VulkanDriver.h
+++ b/filament/backend/src/vulkan/VulkanDriver.h
@@ -23,8 +23,10 @@
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanFboCache.h"
+#include "VulkanReadPixels.h"
#include "VulkanSamplerCache.h"
#include "VulkanStagePool.h"
+#include "VulkanTaskHandler.h"
#include "VulkanUtility.h"
#include "private/backend/Driver.h"
@@ -39,11 +41,14 @@ namespace filament::backend {
class VulkanPlatform;
struct VulkanSamplerGroup;
-class VulkanDriver final : public DriverBase {
+class VulkanDriver final : public DriverBase, private VulkanTaskHandler::Host {
public:
static Driver* create(VulkanPlatform* platform,
const char* const* ppEnabledExtensions, uint32_t enabledExtensionCount, const Platform::DriverConfig& driverConfig) noexcept;
+ VulkanDriver(VulkanDriver const&) = delete;
+ VulkanDriver& operator = (VulkanDriver const&) = delete;
+
private:
void debugCommandBegin(CommandStream* cmds, bool synchronous, const char* methodName) noexcept override;
@@ -73,9 +78,6 @@ class VulkanDriver final : public DriverBase {
#include "private/backend/DriverAPI.inc"
- VulkanDriver(VulkanDriver const&) = delete;
- VulkanDriver& operator = (VulkanDriver const&) = delete;
-
private:
HandleAllocatorVK mHandleAllocator;
@@ -151,6 +153,7 @@ class VulkanDriver final : public DriverBase {
VulkanSamplerCache mSamplerCache;
VulkanBlitter mBlitter;
VulkanSamplerGroup* mSamplerBindings[VulkanPipelineCache::SAMPLER_BINDING_COUNT] = {};
+ VulkanReadPixels mReadPixels;
};
} // namespace filament::backend
diff --git a/filament/backend/src/vulkan/VulkanReadPixels.cpp b/filament/backend/src/vulkan/VulkanReadPixels.cpp
new file mode 100644
index 00000000000..fb1388a8f6e
--- /dev/null
+++ b/filament/backend/src/vulkan/VulkanReadPixels.cpp
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2023 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 "VulkanReadPixels.h"
+
+#include "DataReshaper.h"
+#include "VulkanHandles.h"
+#include "VulkanTaskHandler.h"
+#include "VulkanTexture.h"
+
+#include
+
+using namespace bluevk;
+
+namespace filament::backend {
+
+VulkanReadPixels::~VulkanReadPixels() noexcept {
+ assert_invariant(mDevice != VK_NULL_HANDLE);
+ if (mCommandPool == VK_NULL_HANDLE) {
+ return;
+ }
+ vkDestroyCommandPool(mDevice, mCommandPool, VKALLOC);
+}
+
+void VulkanReadPixels::initialize(VkDevice device) {
+ mDevice = device;
+}
+
+void VulkanReadPixels::run(VulkanRenderTarget const* srcTarget, uint32_t const x, uint32_t const y,
+ uint32_t const width, uint32_t const height, uint32_t const graphicsQueueFamilyIndex,
+ PixelBufferDescriptor&& pbd, VulkanTaskHandler& taskHandler,
+ SelecteMemoryFunction const& selectMemoryFunc,
+ OnReadCompleteFunction const& readCompleteFunc) {
+ assert_invariant(mDevice != VK_NULL_HANDLE);
+
+ VkDevice device = mDevice;
+
+ if (mCommandPool == VK_NULL_HANDLE) {
+ // Create a command pool if one has not been created.
+ VkCommandPoolCreateInfo createInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
+ | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
+ .queueFamilyIndex = graphicsQueueFamilyIndex,
+ };
+ vkCreateCommandPool(device, &createInfo, VKALLOC, &mCommandPool);
+ }
+ VkCommandPool cmdpool = mCommandPool;
+
+ VulkanTexture* srcTexture = srcTarget->getColor(0).texture;
+ assert_invariant(srcTexture);
+ VkFormat const srcFormat = srcTexture->getVkFormat();
+ bool const swizzle
+ = srcFormat == VK_FORMAT_B8G8R8A8_UNORM || srcFormat == VK_FORMAT_B8G8R8A8_SRGB;
+
+ // Create a host visible, linearly tiled image as a staging area.
+ VkImageCreateInfo const imageInfo{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = srcFormat,
+ .extent = {width, height, 1},
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_LINEAR,
+ .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ };
+
+ VkImage stagingImage;
+ vkCreateImage(device, &imageInfo, VKALLOC, &stagingImage);
+
+ VkMemoryRequirements memReqs;
+ VkDeviceMemory stagingMemory;
+ vkGetImageMemoryRequirements(device, stagingImage, &memReqs);
+ VkMemoryAllocateInfo const allocInfo = {.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = memReqs.size,
+ .memoryTypeIndex = selectMemoryFunc(memReqs.memoryTypeBits,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
+ | VK_MEMORY_PROPERTY_HOST_CACHED_BIT)};
+
+ vkAllocateMemory(device, &allocInfo, VKALLOC, &stagingMemory);
+ vkBindImageMemory(device, stagingImage, stagingMemory, 0);
+
+ VkCommandBuffer cmdbuffer;
+ VkCommandBufferAllocateInfo const allocateInfo{
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
+ .commandPool = cmdpool,
+ .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
+ .commandBufferCount = 1,
+ };
+ vkAllocateCommandBuffers(device, &allocateInfo, &cmdbuffer);
+
+ VkCommandBufferBeginInfo const binfo{
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+ vkBeginCommandBuffer(cmdbuffer, &binfo);
+
+ transitionImageLayout(cmdbuffer, {
+ .image = stagingImage,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .subresources = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .srcStage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
+ .srcAccessMask = 0,
+ .dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ });
+
+ VulkanAttachment const srcAttachment = srcTarget->getColor(0);
+
+ VkImageCopy const imageCopyRegion = {
+ .srcSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = srcAttachment.level,
+ .baseArrayLayer = srcAttachment.layer,
+ .layerCount = 1,
+ },
+ .srcOffset = {
+ .x = (int32_t) x,
+ .y = (int32_t) (srcTarget->getExtent().height - (height + y)),
+ },
+ .dstSubresource = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .layerCount = 1,
+ },
+ .extent = {
+ .width = width,
+ .height = height,
+ .depth = 1,
+ },
+ };
+
+ // Transition the source image layout (which might be the swap chain)
+ VkImageSubresourceRange const srcRange = {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = srcAttachment.level,
+ .levelCount = 1,
+ .baseArrayLayer = srcAttachment.layer,
+ .layerCount = 1,
+ };
+
+ srcTexture->transitionLayout(cmdbuffer, srcRange, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
+
+ // Perform the copy into the staging area. At this point we know that the src layout is
+ // TRANSFER_SRC_OPTIMAL and the staging area is GENERAL.
+ UTILS_UNUSED_IN_RELEASE VkExtent2D srcExtent = srcAttachment.getExtent2D();
+ assert_invariant(imageCopyRegion.srcOffset.x + imageCopyRegion.extent.width <= srcExtent.width);
+ assert_invariant(
+ imageCopyRegion.srcOffset.y + imageCopyRegion.extent.height <= srcExtent.height);
+
+ vkCmdCopyImage(cmdbuffer, srcAttachment.getImage(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ stagingImage, VK_IMAGE_LAYOUT_GENERAL, 1, &imageCopyRegion);
+
+ // Restore the source image layout. Between driver API calls, color images are always kept in
+ // UNDEFINED layout or in their "usage default" layout (see comment for getDefaultImageLayout).
+ srcTexture->transitionLayout(cmdbuffer, srcRange,
+ getDefaultImageLayout(TextureUsage::COLOR_ATTACHMENT));
+
+ vkEndCommandBuffer(cmdbuffer);
+
+ VkQueue queue;
+ vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &queue);
+
+ VkSubmitInfo const submitInfo{
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .waitSemaphoreCount = 0,
+ .pWaitSemaphores = VK_NULL_HANDLE,
+ .pWaitDstStageMask = VK_NULL_HANDLE,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &cmdbuffer,
+ .signalSemaphoreCount = 0,
+ .pSignalSemaphores = VK_NULL_HANDLE,
+ };
+ VkFence fence;
+ VkFenceCreateInfo const fenceCreateInfo{
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ };
+ vkCreateFence(device, &fenceCreateInfo, VKALLOC, &fence);
+ vkQueueSubmit(queue, 1, &submitInfo, fence);
+
+ auto* const pUserBuffer = new PixelBufferDescriptor(std::move(pbd));
+ auto const waitTaskId = taskHandler.createTask(
+ [device, width, height, swizzle, srcFormat, fence, stagingImage, stagingMemory, cmdpool,
+ cmdbuffer, pUserBuffer, readCompleteFunc,
+ &taskHandler](VulkanTaskHandler::TaskId taskId, void* data) mutable {
+ PixelBufferDescriptor& p = *pUserBuffer;
+ vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
+ VkResult status = vkGetFenceStatus(device, fence);
+
+ // Fence hasn't been reached. Try waiting again.
+ if (status == VK_NOT_READY) {
+ taskHandler.post(taskId);
+ return;
+ }
+
+ // Need to abort the readPixels if the device is lost.
+ if (status == VK_ERROR_DEVICE_LOST) {
+ utils::slog.e << "Device lost while in VulkanReadPixels::run"
+ << utils::io::endl;
+ taskHandler.completed(taskId);
+
+ // Try to free the pbd anyway
+ readCompleteFunc(std::move(p));
+ delete pUserBuffer;
+ return;
+ }
+
+ VkImageSubresource subResource{.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT};
+ VkSubresourceLayout subResourceLayout;
+ vkGetImageSubresourceLayout(device, stagingImage, &subResource, &subResourceLayout);
+
+ // Map image memory so that we can start copying from it.
+ uint8_t const* srcPixels;
+ vkMapMemory(device, stagingMemory, 0, VK_WHOLE_SIZE, 0, (void**) &srcPixels);
+ srcPixels += subResourceLayout.offset;
+
+ if (!DataReshaper::reshapeImage(&p, getComponentType(srcFormat),
+ getComponentCount(srcFormat), srcPixels,
+ static_cast(subResourceLayout.rowPitch), static_cast(width),
+ static_cast(height), swizzle)) {
+ utils::slog.e << "Unsupported PixelDataFormat or PixelDataType"
+ << utils::io::endl;
+ }
+
+ vkUnmapMemory(device, stagingMemory);
+ vkDestroyImage(device, stagingImage, VKALLOC);
+ vkFreeMemory(device, stagingMemory, VKALLOC);
+ vkDestroyFence(device, fence, VKALLOC);
+ vkFreeCommandBuffers(device, cmdpool, 1, &cmdbuffer);
+ readCompleteFunc(std::move(p));
+ delete pUserBuffer;
+
+ taskHandler.completed(taskId);
+ },
+ nullptr);
+
+ taskHandler.post(waitTaskId);
+}
+
+}// namespace filament::backend
diff --git a/filament/backend/src/vulkan/VulkanReadPixels.h b/filament/backend/src/vulkan/VulkanReadPixels.h
new file mode 100644
index 00000000000..a402e7eab04
--- /dev/null
+++ b/filament/backend/src/vulkan/VulkanReadPixels.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
+#define TNT_FILAMENT_BACKEND_VULKANREADPIXELS_H
+
+#include "VulkanTaskHandler.h"
+#include "private/backend/Driver.h"
+
+#include
+#include