Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce uniform bindings #6018

Merged
merged 11 commits into from
Aug 8, 2018
Merged

Introduce uniform bindings #6018

merged 11 commits into from
Aug 8, 2018

Conversation

lbud
Copy link
Contributor

@lbud lbud commented Jan 18, 2018

This PR

  • introduces uniform binding classes that manage state for uniform bindings
  • Introduces render/program/*_program.js files (open to renaming — these may be better for now as *_program_uniforms.js or something) which create uniform bindings for per-layer uniforms (henceforth fixedUniforms) and perform those uniform calculations (moved from draw_* functions)
  • Refactors Program#draw functions to
    • set color, stencil, and depth modes
    • set all uniforms (both fixedUniforms and paint property-based uniforms, henceforth binderUniforms; the state bindings for these are initialized upon program construction)
  • Moves to drawing exclusively with drawElements (no drawArrays) so that all drawing is done through the same Program#draw path
    • Introduces SegmentVector#simpleSegment in order to create necessary segments to manage these simple draw calls; we create these in lieu of VAOs in painter setup
  • Removes almost all of the "first" / "programChanged" logic, hoping that managing uniform state mitigates perf concerns enough (may revisit — see below)

Current benchmark results (updated 3/19): https://bl.ocks.org/anonymous/raw/cc118c1ebafcbb06a7d8fdd8c2b50c8b/

Launch Checklist

  • briefly describe the changes in this PR
  • write tests for all new functionality
  • post satisfactory benchmark scores (not good enough yet)
  • manually test the debug page

@lbud lbud requested a review from anandthakker January 18, 2018 22:42
Copy link
Contributor

@anandthakker anandthakker left a comment

Choose a reason for hiding this comment

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

This is looking great!

The main big-picture question I'm pondering is whether we can/should push the typing further.

I.e.:

  • For each program type (fill, circle, etc.), declare a base object mapping uniform names to their types.
  • Parameterize Uniforms, UniformValues, UniformLocations, and Program itself on that base object. (We'd need to figure out how to deal with the dynamically-determined paint property uniforms that come out of ProgramConfiguration#getUniforms().)

export interface UniformInterface<T> {
context: Context;
set(location: WebGLUniformLocation, value: T): void;
_set(location: WebGLUniformLocation, value: T): void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the purpose of _set to allow each concrete uniform subclass to call the correct gl.uniform*() method? If so, rather than having it in this public interface, it can just be declared as a property on the Uniform<T> class _set: (location: WebGLUniformLocation, value: T) => void;

let diff = false;
if (!this.current && v) {
diff = true;
} else if (Array.isArray(this.current) && Array.isArray(v) && this.current !== v) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this.current !== v means we're assuming array values are never mutated in place. That's probably a valid assumption, flagging just in case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤔 it feels like a valid assumption, is currently true, and would obviously be more performant to proceed as such, although I suppose there's nothing preventing someone from doing this…

Copy link
Contributor

Choose a reason for hiding this comment

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

Remember how for performance reasons we ended up inlining the this.current tests for Value implementations? Let's do the same for uniforms. In addition to the likely performance advantage, we can reduce the required assumptions by tailoring conditionals to the value type, e.g. for Uniform2f: if (this.current !== v || this.current[0] !== v[0] || this.current[1] !== v[1]) {.

}
}

function drawFillTiles(painter, sourceCache, layer, coords, drawFn) {
if (pattern.isPatternMissing(layer.paint.get('fill-pattern'), painter)) return;
function drawFillTiles(painter, sourceCache, layer, coords, depthMode, colorMode, fillTiles) {
Copy link
Contributor

Choose a reason for hiding this comment

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

small clarification: maybe change fillTiles to something like phase: 'fill' | 'outline'?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to change to isOutline to maintain a simple boolean while being a bit more descriptive.

tile: {tileID: OverscaledTileID, tileSize: number}
): UniformValues {
return util.extend(pattern.prepare(image, painter),
pattern.setTile(tile, painter),
Copy link
Contributor

Choose a reason for hiding this comment

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

Since pattern.prepare() and pattern.setTile() are state-mutating methods, I'm wary of them being called from within backgroundUniformValues(), etc., which seem intended to be "pure" functions that just return computed uniform values.

@lbud lbud force-pushed the state-uniforms branch 6 times, most recently from df5fbd9 to c9049a9 Compare February 28, 2018 00:12
@lbud
Copy link
Contributor Author

lbud commented Feb 28, 2018

Updated OP with current benchmarks; the Paint benchmark is reliably performing about 2 ms slower than master at the moment :(


import type Context from '../gl/context';

export interface UniformInterface<T> {
Copy link
Contributor

Choose a reason for hiding this comment

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

UniformInterface isn't imported anywhere; I think it can be removed (along with the implements below).


export type UniformValue = number | Array<number> | Float32Array;
export type UniformValues<Us: Object>
= $Exact<$ObjMap<Us, <V>(u: Uniform<V>) => UniformValue>>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ideally this would be:

    = $Exact<$ObjMap<Us, <V>(u: Uniform<V>) => V>>;

That would permit the type checker to verify that Uniforms<Us>#set is receiving the right types of values in the second parameter.

There are a number of things that need to happen to make this possible. A starting point is changing:

class Uniform2fv extends Uniform<Array<number>> {
    _set(location: WebGLUniformLocation, v: Array<number>): void {
        this.context.gl.uniform2fv(location, v);
    }
}

to:

class Uniform2f extends Uniform<[number, number]> {
    _set(location: WebGLUniformLocation, v: [number, number]): void {
        this.context.gl.uniform2f(location, v[0], v[1]);
    }
}

(And so on for the other Uniform* classes.)

let diff = false;
if (!this.current && v) {
diff = true;
} else if (Array.isArray(this.current) && Array.isArray(v) && this.current !== v) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remember how for performance reasons we ended up inlining the this.current tests for Value implementations? Let's do the same for uniforms. In addition to the likely performance advantage, we can reduce the required assumptions by tailoring conditionals to the value type, e.g. for Uniform2f: if (this.current !== v || this.current[0] !== v[0] || this.current[1] !== v[1]) {.

@lbud lbud force-pushed the state-uniforms branch 6 times, most recently from 7988f3d to 637a096 Compare March 7, 2018 00:44
@lbud
Copy link
Contributor Author

lbud commented Mar 7, 2018

For performance reasons, in 637a096 I reverted the ProgramConfiguration#setUniformsProgramConfiguration#getUniforms switch, but rather than calling gl.uniform** setters directly from the binders they have UniformBinding members. This allows us to save time across e.g. different tiles within the same layer in a frame, and also allows us to reuse those across different frames iff the ProgramConfiguration hasn't been used in a different Program and vice versa (bound uniforms remain bound to those locations on a given program even when the active program is changed). The current benchmarks (also updated in OP) are https://bl.ocks.org/anonymous/raw/d3873066e1fe5d97d9c0db5879c0c66d/ : some of the individual layer benchmarks show slight degradation, but the aggregate Paint benchmark is now stable and I think the real gains here are in map styles with multiple layer types.

@@ -90,7 +90,6 @@ class Program<Us: UniformBindings> {
}
}

this.binderUniforms = configuration.getUniformBindings(context);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should still live on Program, so that sets are deduped across different ProgramConfigurations.

@lbud lbud requested a review from jfirebaugh March 16, 2018 00:45
firstTile = false;
// once refactored so that bound texture state is managed, we'll also be able to remove this firstTile/programChanged logic
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like the specific issue here is that painter.lineAtlas.bind(context) must currently be called after lineSDFUniformValues(...), because the latter produces side effects in the lineAtlas when it calls lineAtlas.getDash(...).

@lbud
Copy link
Contributor Author

lbud commented Mar 17, 2018

Before 8230e38: https://bl.ocks.org/anonymous/raw/7f4b0baa728fc39995ab1c13a267cdef/
After: https://bl.ocks.org/anonymous/raw/51cc28f88a2eb55da5e8a3b03dac3f05/ — a few individual layer benchmarks are still slightly slower, but less so than before…

@lbud lbud force-pushed the state-uniforms branch 4 times, most recently from 6d03fa5 to e5b91bd Compare March 21, 2018 22:09
@lbud
Copy link
Contributor Author

lbud commented Mar 21, 2018

e5b91bd improves upon layer performance by switching ProgramConfiguration#binders from an object to an array, so that setUniforms can iterate over an array (with pre-cached length) rather than hash properties (jsperf experiment). Current benchmarks: http://bl.ocks.org/lbud/raw/f7e5f62c79d77f5c554ed8a4978c61d8/ (LayerLine floats back and forth around master performance, sometimes slightly better and sometimes slightly worse, but consistently better than before this commit.)

@lbud lbud force-pushed the state-uniforms branch 2 times, most recently from fa68809 to e019157 Compare June 14, 2018 21:36
@lbud lbud force-pushed the state-uniforms branch from e019157 to 147deca Compare June 14, 2018 23:50
@lbud
Copy link
Contributor Author

lbud commented Jun 18, 2018

Benchmarks for current rebased version: http://bl.ocks.org/lbud/raw/9920556dde95d6b156e838559b36596d/

There are a few layer-specific benchmarks that are still slower. The paint benchmark is consistently performing as well if not slightly better than 0.45 + master.

  • I can't reproduce the disparity within LayerCircle, LayerSymbol, or StyleLayerCreate during individual runs
  • LayerBackground and LayerLine are consistently performing slightly slower. I'm inclined to not worry about a background benchmark as maps typically have only one or, at most, a few background layers

@anandthakker
Copy link
Contributor

Re-rebased. LayerBackground is still about 100 microseconds slower, but I agree with @lbud that this is acceptable for background layers. LayerLine is also still about 60 microseconds-- ~1.5 standard deviations -- slower. But given that Paint shows no significant difference, I'm inclined to say we should go ahead with this.

screen shot 2018-08-06 at 10 22 35 am

screen shot 2018-08-06 at 10 31 44 am

screen shot 2018-08-06 at 10 33 21 am

@anandthakker anandthakker force-pushed the state-uniforms branch 3 times, most recently from c34e74d to 3042bd2 Compare August 8, 2018 12:43
Copy link
Contributor

@jfirebaugh jfirebaugh left a comment

Choose a reason for hiding this comment

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

🎉

@jfirebaugh jfirebaugh merged commit 855b850 into master Aug 8, 2018
@jfirebaugh jfirebaugh deleted the state-uniforms branch August 8, 2018 18:41
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.

3 participants