Skip to content

Cache environments and skyboxes#667

Merged
cdata merged 7 commits intoremove_pmrem_flagfrom
cache_env
Jul 12, 2019
Merged

Cache environments and skyboxes#667
cdata merged 7 commits intoremove_pmrem_flagfrom
cache_env

Conversation

@elalish
Copy link
Contributor

@elalish elalish commented Jun 28, 2019

Fixes #336

Really #336 was already fixed, but not in a complete way as only the generated environment was cached, but not any loaded environments, skyboxes, or PMREM results. Now all of the above are cached in a single system. The near-term goal of this PR is to get our automated tests to pass on Safari with PMREM, as we're guessing the current problem is they use too much memory from duplicate environments.

The next thing to consider as a follow-on to this work is probably #491.

@elalish elalish requested a review from cdata June 28, 2019 17:14
@elalish elalish self-assigned this Jun 28, 2019
@elalish elalish changed the title Cache env Cache environments and skyboxes Jun 28, 2019

export class EquirectangularToCubeGenerator {
public renderTarget: WebGLRenderTarget;
public renderTarget: WebGLRenderTargetCube;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

options: EnvironmentGenerationConfig = {}): Promise<{
environmentMap: WebGLRenderTarget,
skybox: WebGLRenderTargetCube|null
}> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a type for this struct.

Something like:

interface EnvironmentMapAndSkybox {
  environmentMap: WebGLRenderTarget,
  skybox?: WebGLRenderTargetCube
}

environmentMap = environmentMap.texture
} else {
if (!!skyboxUrl) {
if (targetCache.has(skyboxUrl)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like these cache checks ought to be hoisted above the conditionals where we load so that we only do the check once, and otherwise load + populate the cache.

One pattern I like a lot is to populate caches with a promise for the value (rather than the value itself). This allows us to streamline the async flow of the method a bit. For example:

let skyboxLoads: Promise<WebGLRenderTargetCube|null> = Promise.resolve(null);
let skybox: WebGLRenderTargetCube|null = null;

// ...

if (!!skyboxUrl) {
  if (!skyboxCache.has(skyboxUrl) {
    skyboxCache.set(skyboxUrl, this.loadEquirectAsCubeMap(
        skyboxUrl,
        progressTracker ? progressTracker.beginActivity() : () => {}));
  }
  skyboxLoads = targetCache.get(skyboxUrl);
}

// ...

skybox = await skyboxLoads;

This approach also helps a lot if another element wants to load the same map but only starts trying to in the async time while it has already started loading but hasn't finished yet. The second loader will see the actively loading item in cache and await on its promise (avoiding a race condition that would result in a double load).

// The case for this is that the user intends for the IBL to be different
// from the scene background (which may be a skybox or solid color).
if (!!environmentMapUrl) {
if (!!environmentMapUrl && !targetCache.has(environmentMapUrl + ':env')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to have addressed this edge case (loading the same URL for environment vs skybox). However, I think it would be more robust to just have two caches (one for environment, and one for skybox). It's not likely but certainly possible that :env will collide with a real URL, and we could avoid worrying about this problem entirely by maintaining a separate cache.

Sidenote: this whole simultaneous loading arrangement between skybox and envmap screams for a higher level abstraction to save us from repeating ourselves all over the place. No need to do that now, just wanted to note it.

} else {
const nonPmremEnvironmentMap = environmentMap!.texture;
environmentMap = this.pmremPass(nonPmremEnvironmentMap);
targetCache.set(environmentMapUrl, environmentMap);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dualistic quality where the environment map may be PMREM'd or may not be adds an interesting complication. You would need to amend the skybox example I commented about above to do something like this when populating the cache in the environment map case:

environmentCache.set(environmentUrl, this.loadEquirectAsCubeMap(
        environmentUrl,
        progressTracker ? progressTracker.beginActivity() : () => {})
    // Chain the promise so that the cached async work includes the PMREM pass:
    .then((environmentMap: WebGLRenderTarget) => this.pmremPass(environmentMap.texture)));

That would actually end up looking a lot cleaner than what we had before this PR was proposed...

@cdata cdata merged commit 19ba22d into remove_pmrem_flag Jul 12, 2019
@cdata cdata added this to the v0.6.0 milestone Aug 13, 2019
@elalish elalish deleted the cache_env branch September 27, 2019 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants