Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

[core] Rework attribute binding (again) #9433

Merged
merged 13 commits into from
Jul 12, 2017
Merged

[core] Rework attribute binding (again) #9433

merged 13 commits into from
Jul 12, 2017

Conversation

jfirebaugh
Copy link
Contributor

@jfirebaugh jfirebaugh commented Jul 6, 2017

These changes are necessary for programs whose set of active attributes is not fixed at compile time by a template parameter pack, but rather varies based on the generated shader text at runtime. In such cases, the attribute location of a given named attribute may vary between instances of the same Program.

Previously, attribute bindings were implicitly associated with a location based on template parameter order, and -1 was used to indicate an inactive attribute. This left us unable to disable the appropriate attribute when it went from active to inactive. Now, the state tracker for bindings explicitly associates locations and state, and an empty optional is used to indicate an inactive attribute.

In addition, a gl::VertexArray class is now exposed, allowing more flexibility in the relationship between Programs, Segments, and attribute bindings. The final commit in this PR adjusts that relationship to match gl-js, reduce rebinds, and work around buggy VAO implementations.

VertexArray uses a pimpl idiom in order to support implementations that lack the VAO extension. In that case, all VertexArrays share global binding state, reflecting the platform reality in the absence of VAOs, while still providing a uniform API.

Fixes #4681
Fixes #8658
Fixes #9406


@jfirebaugh jfirebaugh requested review from kkaefer, ansis and lbud July 6, 2017 15:27
@jfirebaugh
Copy link
Contributor Author

iOS bench results:

Result:
| paris      | 59.9 fps |
| paris2     | 60.1 fps |
| alps       | 60.0 fps |
| us east    | 60.4 fps |
| greater la | 60.2 fps |
| sf         | 60.3 fps |
| oakland    | 60.4 fps |
| germany    | 58.3 fps |
Total FPS: 479.7
Average FPS: 60.0

@jfirebaugh jfirebaugh added the Core The cross-platform C++ core, aka mbgl label Jul 6, 2017
@jfirebaugh jfirebaugh force-pushed the attribute-location branch 2 times, most recently from 5998e07 to 353d1bf Compare July 6, 2017 15:43
@jfirebaugh
Copy link
Contributor Author

Rendering test failure on regressions/mapbox-gl-js#4860. @ChrisLoer is this getting an ignore or fix soon?

@jfirebaugh jfirebaugh force-pushed the attribute-location branch 2 times, most recently from 581cc21 to 3dd30ef Compare July 6, 2017 16:14
@ChrisLoer
Copy link
Contributor

Oops, sorry about that, I noticed that was a failure on native yesterday but hadn't submitted a PR yet: mapbox/mapbox-gl-js#4947

@lbud
Copy link
Contributor

lbud commented Jul 8, 2017

😅

I don't want to pretend that my own ✅ represents anything more than a very vague understanding of the actual implementation here, but it sounds good to me.

Also, without it (on master) I'm getting this error in api-gl:

    [Fri, 07 Jul 2017 20:06:25 GMT] [info] [visual-test] 14/3616/6065 - tiles-traffic-night-v2
libc++abi.dylib: terminating with uncaught exception of type mbgl::gl::Error: glDisableVertexAttribArray(location): Error GL_INVALID_VALUE at mapbox-gl-native/src/mbgl/gl/attribute.cpp:55

(Rebasing on this branch fixes it.)

@tobrun
Copy link
Member

tobrun commented Jul 10, 2017

tested this PR on a Samsung Galaxy J3 (2016) and hitting java.lang.Error: array::at when showing a map. Verfied that this device works on master. Emulator is not showing this issue. Below the full stacktrace though not very actionable.

7 - 10 08: 53: 25.839 5402 - 5402 / com.mapbox.mapboxsdk.testapp E / AndroidRuntime: FATAL EXCEPTION: main
Process: com.mapbox.mapboxsdk.testapp, PID: 5402
java.lang.Error: array::at
at com.mapbox.mapboxsdk.maps.NativeMapView.nativeRender(Native Method)
at com.mapbox.mapboxsdk.maps.NativeMapView.render(NativeMapView.java: 158)
at com.mapbox.mapboxsdk.maps.MapView.onDraw(MapView.java: 416)
at android.view.View.draw(View.java: 16536)
at android.widget.FrameLayout.draw(FrameLayout.java: 598)
at android.view.View.updateDisplayListIfDirty(View.java: 15466)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java: 3697)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java: 3676)
at android.view.View.updateDisplayListIfDirty(View.java: 15426)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java: 3697)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java: 3676)
at android.view.View.updateDisplayListIfDirty(View.java: 15426)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java: 3697)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java: 3676)
at android.view.View.updateDisplayListIfDirty(View.java: 15426)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java: 3697)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java: 3676)
at android.view.View.updateDisplayListIfDirty(View.java: 15426)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java: 3697)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java: 3676)
at android.view.View.updateDisplayListIfDirty(View.java: 15426)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java: 3697)
at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java: 3676)
at android.view.View.updateDisplayListIfDirty(View.java: 15426)
at android.view.View.getDisplayList(View.java: 15488)
at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java: 309)
at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java: 315)
at android.view.ThreadedRenderer.draw(ThreadedRenderer.java: 354)
at android.view.ViewRootImpl.draw(ViewRootImpl.java: 2956)
at android.view.ViewRootImpl.performDraw(ViewRootImpl.java: 2753)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java: 2339)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java: 1314)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java: 7057)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java: 829)
at android.view.Choreographer.doCallbacks(Choreographer.java: 606)
at android.view.Choreographer.doFrame(Choreographer.java: 576)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java: 815)
at android.os.Handler.handleCallback(Handler.java: 739)
at android.os.Handler.dispatchMessage(Handler.java: 95)
at android.os.Looper.loop(Looper.java: 145)
at android.app.ActivityThread.main(ActivityThread.java: 7007)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java: 372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: 1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 1199)


for (int32_t i = 0; i < attributeCount; i++) {
activeAttributes.emplace(getAttributeName(id, maxAttributeLength, i));
std::string attributeName;
Copy link
Member

Choose a reason for hiding this comment

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

We could hoist this to outside the loop.


namespace mbgl {

template <class Attributes>
Copy link
Member

Choose a reason for hiding this comment

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

This class doesn't use this template parameter. Consider removing it?

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's still useful as a means of type safety -- prevents mismatching a segment for the wrong set of attributes to Program::draw.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, do you think this affects code size?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since there are no member functions, and the constructor is likely inlined, I don't think it will be affected. I'm hopeful this PR will reduce code size overall, since the template parameters were removed from AttributeBinding. I'll run bloaty and see.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No difference:

     VM SIZE                               FILE SIZE
 ++++++++++++++ GROWING                 ++++++++++++++
  +0.1% +1.95Ki __TEXT,__text           +1.95Ki  +0.1%
  +0.3%     +48 __DATA,__data               +48  +0.3%
  +0.0%     +46 __TEXT,__cstring            +46  +0.0%

 -------------- SHRINKING               --------------
  -0.6% -1.11Ki [None]                  -1.14Ki  -0.6%
  -0.3%    -836 __TEXT,__gcc_except_tab    -836  -0.3%
  -0.0%     -80 __TEXT,__const              -80  -0.0%
  -0.1%     -44 __TEXT,__unwind_info        -44  -0.1%

  [ = ]       0 TOTAL                       -32  -0.0%

class SegmentVector : public std::vector<Segment<Attributes>> {
public:
SegmentVector() = default;
};
Copy link
Member

Choose a reason for hiding this comment

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

While we're at it, we can change this to a typedef.

@@ -155,7 +156,7 @@ class ConstantSymbolSizeBinder final : public SymbolSizeBinder {
}

SymbolSizeAttributes::Bindings attributeBindings() const override {
return SymbolSizeAttributes::Bindings { gl::DisabledAttribute() };
return SymbolSizeAttributes::Bindings { std::experimental::nullopt };
Copy link
Member

Choose a reason for hiding this comment

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

Can we alias this in optional.hpp similar to std::experimental::optional<T>?

@@ -0,0 +1,40 @@
#pragma once
Copy link
Member

Choose a reason for hiding this comment

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

I believe this should be in renderer, not programs. The latter only contains code that is specific to our shader programs, but Segment isn't.

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 think it's better here. The division as I see it is:

  • programs -- code closely related to OpenGL, but specific to Mapbox GL in some way (so doesn't belong in gl). segment.hpp is similar to attributes.hpp and uniforms.hpp in this sense.
  • renderer -- code that works with the Mapbox GL domain model (sources, layers, etc.) to facilitate rendering.

State<value::BindElementBuffer> indexBuffer;

using AttributeState = State<value::VertexAttribute, Context&, AttributeLocation>;
std::array<AttributeState, MAX_ATTRIBUTES> bindings;
Copy link
Member

Choose a reason for hiding this comment

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

I'm a little bit concerned about the overall size of this struct (it's 672 bytes on a 64 bit platform). In particular, we're duplicating the reference to Context 8 times as part of this state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you see a way to avoid duplicating the reference? It seems like it would require refactoring how we pass additional arguments to Set and probably replacing State::operator= with State::set. If we're going to do that, I'd rather do it in a separate refactor.


BufferID vertexBuffer;
std::size_t vertexSize;
std::size_t vertexOffset;
Copy link
Member

Choose a reason for hiding this comment

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

Can we reduce the overall size of this struct a little bit by using smaller types? OpenGL only uses 32 bit types for size + offsets anyway, so there's no need to use 64 bit types on a 64 bit platform. attributeSize can only be 1, 2, 3, or 4.

}
};

using UniqueVertexArrayState = std::unique_ptr<VertexArrayState, std::function<void (VertexArrayState*)>>;
Copy link
Member

Choose a reason for hiding this comment

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

Instead of storing a std::function, can we use something like this:

template <typename T>
struct ConditionalDeleter {
    const bool destroy = true;
    void operator()(T* ptr) const {
        if (destroy) {
            std::default_delete<T>()(ptr);
        }
    }
};

@@ -15,7 +15,7 @@ using VertexArrayID = uint32_t;
using FramebufferID = uint32_t;
using RenderbufferID = uint32_t;

using AttributeLocation = int32_t;
using AttributeLocation = uint32_t;
Copy link
Member

Choose a reason for hiding this comment

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

glGetAttribLocation returns signed integers. I know we aren't using that call, so a comment explaining the discrepancy would be useful.

optional<gl::VertexArray>& vertexArray = segment.vertexArrays[layerID];

if (!vertexArray) {
vertexArray = context.createVertexArray();
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using optional here, can we use key presence as an indicator for whether we need to create the vertex array?

@jfirebaugh
Copy link
Contributor Author

@tobrun I'm wondering if the issue you're experiencing is connected with program binary caching. Does it work on a fresh install?

@jfirebaugh jfirebaugh force-pushed the attribute-location branch 2 times, most recently from 58251be to 3566295 Compare July 10, 2017 21:16
@tobrun
Copy link
Member

tobrun commented Jul 11, 2017

removing the app and reinstalling doesn't show the crash. Sorry for the noise!

@kkaefer
Copy link
Member

kkaefer commented Jul 11, 2017

@tobrun no that's a good call. It means there's an issue with our upgrade path. The binary program caching system tries to restore the programs (which also stores the attribute assignments), but the order/numbering doesn't match our code anymore. It restores the code because the hash is only based on the actual file content. This means whenever we modify anything about how we assign attribute bindings, we must also force a hash change.

@jfirebaugh jfirebaugh force-pushed the attribute-location branch 2 times, most recently from 91e52b8 to a28be5a Compare July 11, 2017 18:44
These changes are necessary for programs whose set of active attributes is not fixed at compile time by a template parameter pack, but rather varies based on the generated shader text at runtime. In such cases, the attribute location of a given named attribute may vary between instances of the same Program.

Previously, attribute bindings were implicitly associated with a location based on template parameter order, and -1 was used to indicate an inactive attribute. This left us unable to disable the appropriate attribute when it went from active to inactive. Now, the state tracker for bindings explicitly associates locations and state, and an empty optional is used to indicate an inactive attribute.

In addition, a gl::VertexArray class is now exposed, allowing more flexibility in the relationship between Programs, Segments, and attribute bindings. In this commit, that relationship does not change, but the subsequent commit adjusts it to match gl-js, reduce rebinds, and work around buggy VAO implementations.

VertexArray uses a pimpl idiom in order to support implementations that lack the VAO extension. In that case, all VertexArrays share global binding state, reflecting the platform reality in the absence of VAOs, while still providing a uniform API.
Reduces rebinding, matches gl-js, and works around the buggy VAO implementation on PowerVR SGX544 GPUs.
@jfirebaugh
Copy link
Contributor Author

I've added a couple speculative commits to try to solve the issue with cached program binaries -- if those don't fix it, I'll continue the investigation in #9473.

@jfirebaugh jfirebaugh merged commit 11952db into master Jul 12, 2017
@jfirebaugh jfirebaugh deleted the attribute-location branch July 12, 2017 22:22
@jfirebaugh jfirebaugh mentioned this pull request Jul 13, 2017
2 tasks
@tobrun tobrun mentioned this pull request Jul 20, 2017
15 tasks
astojilj added a commit that referenced this pull request Apr 18, 2019
Remove re-linking programs as redundant.
It costs (cheaper to link once than twice) and is not that common GL API
usage pattern (subjective).
Initial idea was to enable work on optimization what would reduce number
of attrib setup calls in case that VAO is not available (#9433).

As such optimization is not implemented, we can remove re-linking.

Related to closed PR #9433 and PR #11583.
astojilj added a commit that referenced this pull request May 20, 2019
Remove re-linking programs as redundant.
It costs (cheaper to link once than twice) and is not that common GL API
usage pattern (subjective).
Initial idea was to enable work on optimization what would reduce number
of attrib setup calls in case that VAO is not available (#9433).

As such optimization is not implemented, we can remove re-linking.

Related to closed PR #9433 and PR #11583.
astojilj added a commit that referenced this pull request May 20, 2019
Remove re-linking programs as redundant.
It costs (cheaper to link once than twice) and (subjective) is not that common GL API
usage pattern, although perfectly legal and permitted.
Initial idea, behind the removed code, was to enable work on optimization
that would reduce number of attrib setup calls in case when VAO is not
available (as described in #9433).

As such optimization is not implemented, and it is arguable if it makes sense
to do it now, we can remove re-linking.

Related to closed PRs #9433 and PR #11583.

I have [measured the time spent just on relinking](https://gist.github.com/astojilj/29bd5a5c5dc0b2d9f29ecb660da07fbf) using release build on iPhone SE (A9, same as iPhone 6S):

- 1st run after reboot or installation
Total 37.14ms, average per program:1.86ms

- reopening
Total: 2.47ms, average per program: 0.12ms

This time we save using the patch here.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Core The cross-platform C++ core, aka mbgl
Projects
None yet
5 participants