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

Buffer refactor #6504

Merged
merged 8 commits into from
Oct 5, 2016
Merged

Buffer refactor #6504

merged 8 commits into from
Oct 5, 2016

Conversation

jfirebaugh
Copy link
Contributor

As part of the general OpenGL code refactor effort, we should redesign how we handle buffers. The main design goal is to split the current responsibilities of the current Buffer class template.

  • Building/filling an array of data (in the worker) should be done with plain std::vector<Vertex> (or std::array<Vertex, N> for fixed-sized arrays). Vertex is a stand in for any user-defined, plain-old-struct type with appropriate data members for the vertex attributes.
  • Separately, (on the main thread) create an object representing the OpenGL buffer, passing the vector or array. Creation of this object handles OpenGL buffer allocation and data upload in one step.

Secondary design goals:

  • Separate, dedicated types for vertex buffers and index buffers. (Technically we have this already since Buffer is templated on GLenum target, but we want to remove GLenum from the public interface.)
  • Set up for the vertex buffer type to carry layout information about its elements. (Necessary for C++ bindings for OpenGL #6468.)

API sketch:

// Vertex buffers

template <class ElementType>
class VertexBufferSlice {
public:
    std::size_t offset() const;
    std::size_t size() const;

    const VertexBuffer<ElementType>& buffer;
};

template <class ElementType>
class VertexBuffer {
public:
    std::size_t size() const;
    VertexBufferSlice<ElementType> slice(std::size_t offset, std::size_t len) const;
    operator VertexBufferSlice<ElementType> () const { return slice(0, size()); }

private:
    UniqueBuffer buffer;
};

template <class Arraylike> // see https://github.com/mapbox/jni.hpp/blob/master/include/jni/arraylike.hpp
VertexBuffer<ArraylikeElement<ArrayLike>> Context::createVertexBuffer(const Arraylike& data);

// Index (a.k.a. element) buffers

template <class Primitive, class IndexType = uint16_t>
class IndexBufferSlice { ... };

template <class Primitive, class IndexType = uint16_t>
class IndexBuffer { ... };

// Where Primitive is one of `Points`, `Lines`, `LineStrip`, `Triangles`:
// https://github.com/mapbox/mapbox-gl-native/blob/3bf08b4cd89b71010bc25dbf74fe3c7752918faa/src/mbgl/gl/primitives.hpp#L14-L17

template <class Primitive, class IndexType>
using IndexBufferElementType;

template <> using IndexBufferElementType<Points> = IndexType;
template <> using IndexBufferElementType<Lines> = IndexType;
template <> using IndexBufferElementType<LineStrip> = IndexType;
template <> using IndexBufferElementType<Triangles> = std::array<IndexType, 3>;

template <class Primitive, class Arraylike>
IndexBuffer<Primitive, IndexType> Context::createIndexBuffer(const Arraylike& data) {
    static_assert(ArraylikeElement<ArrayLike> == IndexBufferElementType<Primitive>);
    ...
}

@jfirebaugh jfirebaugh self-assigned this Sep 29, 2016
@kkaefer
Copy link
Member

kkaefer commented Sep 29, 2016

This could also include a refactor that uses structs as the members of the buffers instead of writing explicit accessors (using #pragma pack(1)).

With MTLVertexDescriptor, the Metal API has a similar concept for describing the buffer layout and how they map to a shader's attributes.

@kkaefer kkaefer added refactor Core The cross-platform C++ core, aka mbgl labels Sep 29, 2016
@mention-bot
Copy link

@jfirebaugh, thanks for your PR! By analyzing this pull request, we identified @mikemorris, @ansis and @springmeyer to be potential reviewers.

CircleVertexBuffer vertexBuffer_;
TriangleElementsBuffer elementsBuffer_;
std::vector<CircleVertex> vertexes;
std::vector<gl::Triangle<uint16_t>> indexes;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kkaefer Should we hard-code uint16_t as the index type? So a declaration like this would be:

std::vector<gl::Triangle> indexes;

Copy link
Member

Choose a reason for hiding this comment

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

OpenGL only supports 16bit unsigned indices anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, this is an OpenGL ES limitation and I was looking at generic OpenGL documentation for glDrawElements. OpenGL ES also supports uint8_t indexes, but I don't think we'll ever use those. So I'll deparameterize this.

@jfirebaugh jfirebaugh force-pushed the buffer-refactor branch 2 times, most recently from db104b0 to a327bf5 Compare September 30, 2016 01:37
namespace gl {

struct ElementOffset {
std::size_t value;
Copy link
Member

Choose a reason for hiding this comment

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

size_t is 8 bytes on 64-bit platforms, can we use uint32_t here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why? If you want to match GLsizei, it should be int32_t, or actually uint16_t because this must be a non-negative value.


template <class Primitive>
class IndexBuffer {
public:
Copy link
Member

Choose a reason for hiding this comment

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

Could we static_assert that Primitive is one of Line and Triangle?

template <class Vertex>
class VertexBuffer {
public:
static constexpr std::size_t vertexSize = sizeof(Vertex);
Copy link
Member

Choose a reason for hiding this comment

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

Could we use using vertexSize = std::integral_constant<std::size_t, sizeof(Vertex)>; to avoid static data members that need initialization?

static_cast<uint16_t>(triangleIndex + 2)});
buffer.triangles.push_back({static_cast<uint16_t>(triangleIndex + 1),
static_cast<uint16_t>(triangleIndex + 2),
static_cast<uint16_t>(triangleIndex + 3)});
Copy link
Member

Choose a reason for hiding this comment

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

why push_back and not emplace_back?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

emplace_back doesn't work with aggregate initialization. I'll add constructors to Line and Triangle so we can use it.


struct TextBuffer {
TextVertexBuffer vertices;
TriangleElementsBuffer triangles;
std::vector<TextureRectVertex> vertexes;
Copy link
Member

Choose a reason for hiding this comment

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

Why switch from vertices/indices to vertexes/indexes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For indexes I actually meant to use lines or triangles depending on the primitive. I'll switch back to vertices.

void CircleVertex::bind(const int8_t* offset) {
static_assert(sizeof(CircleVertex) == 4, "expected CircleVertex size");

MBGL_BIND_VERTEX_ATTRIBUTE(CircleVertex, a_pos, offset);
Copy link
Member

Choose a reason for hiding this comment

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

This is an incorrect place for using a_pos; those names/attributes are shader-specific and not Vertex type specific.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Vertex attribute binding involves some shader-specific things and some vertex-specific things:

  • Shader-specific: attribute name, index, and GLSL type.
  • Vertex-specific: member name, offset, and C++ type.

I'd like to come up with a design where:

  • A given C++ vertex type can be used with multiple shaders.
  • A shader requires one particular vertex type, enforced at compile time.
  • Attribute indexes are defined per-Shader-subclass, rather than in Shader, as they are now. Not all shaders have all attributes.
  • Debug builds verify that the GLSL and native types are consistent, using glGetActiveAttrib to introspect the GLSL type. (We should do this for uniforms as well.)
  • I'm open to a design that doesn't enforce that the GLSL attribute name matches the C++ member name, but it also doesn't seem bad to do so. Why would we need them to differ?

I think the starting point here is to introduce a counterpart to uniform.{hpp,cpp} for attributes and add members to each Shader subclass -- e.g.:

AttributeVector<int16_t, 2> a_pos = { "a_pos", *this };

Then see where that leads the binding code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sketched here but currently stuck on a tricky problem with type erasing pointer-to-members.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

We still have the issue of Vertex types having to have the same names as the shaders. In our code, we (almost?) always do it like that, but there's no reason why we need to do it like that. I.e. VertexType::a_pos could be bound to Shader::a_pos, but we could also name the position in the VertexType something else.


// This has to be a macro because it uses the offsetof macro, which is the only legal way to get a member offset.
#define MBGL_BIND_VERTEX_ATTRIBUTE(VertexType, member, offset) \
::mbgl::gl::bindVertexAttribute(Shader::member, &VertexType::member, offset, offsetof(VertexType, member))
Copy link
Member

Choose a reason for hiding this comment

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

We should be using std::is_standard_layout to ensure that we can safely use offsetof. We should also assert that all values we get from offsetof are multiples of 4.

It also assumes that the shader and the vertex type have identically named member names. I feel a little bit uneasy about that, but can't quite put my finger on it.

} {}

int16_t a_pos[2];
int16_t a_texture_pos[2];
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 make all of these members const?

class RasterShader : public Shader {
public:
RasterShader(gl::Context&, Defines defines = None);

void bind(int8_t* offset) final;
void bind(const gl::VertexBuffer<RasterVertex>&,
const int8_t* offset);
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 rename offset to something like begin to avoid confusion of the offset within a buffer with the offset of an attribute within a vertex?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My plan here is to replace both parameters with const gl::VertexBufferSlice<RasterVertex>&, but I'd like to put that off until a followup PR.

@jfirebaugh jfirebaugh force-pushed the buffer-refactor branch 9 times, most recently from 18ddf48 to 95bbe9e Compare October 3, 2016 18:37
@jfirebaugh
Copy link
Contributor Author

@kkaefer Re-review?

Template on shader types, rather than count. This allows the compiler to enforce using the correct VAO for the shader and PaintMode. This fixes OverdrawMode with circle layers.

While here, avoid using unique_ptrs for groups. Instead, ensure ElementGroup is movable.
static_assert(std::is_standard_layout<V>::value, "vertex type must use standard layout");
static_assert(memberOffset % 4 == 0, "vertex attribute must be optimally aligned");
static_assert(1 <= N && N <= 4, "count must be 1, 2, 3, or 4");
static_assert(sizeof(V) <= std::numeric_limits<GLsizei>::max(), "vertex type is too big");
Copy link
Member

Choose a reason for hiding this comment

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

lol


ElementGroup(uint32_t vertex_length_ = 0, uint32_t elements_length_ = 0)
: vertex_length(vertex_length_)
, elements_length(elements_length_)
Copy link
Member

Choose a reason for hiding this comment

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

Our current formatting guide does not have leading commas in initializer lists. We should either change this formatting or the formatting guidelines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was fixed in a followup commit.

modified(std::move(modified_)),
expires(std::move(expires_)),
debugMode(debugMode_) {
std::vector<PlainVertex> buildTextVertexes(const OverscaledTileID& id,
Copy link
Member

Choose a reason for hiding this comment

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

There are still a few instances of vertexes in the code.

void CircleVertex::bind(const int8_t* offset) {
static_assert(sizeof(CircleVertex) == 4, "expected CircleVertex size");

MBGL_BIND_VERTEX_ATTRIBUTE(CircleVertex, a_pos, offset);
Copy link
Member

Choose a reason for hiding this comment

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

We still have the issue of Vertex types having to have the same names as the shaders. In our code, we (almost?) always do it like that, but there's no reason why we need to do it like that. I.e. VertexType::a_pos could be bound to Shader::a_pos, but we could also name the position in the VertexType something else.

@jfirebaugh jfirebaugh merged commit f438bb6 into master Oct 5, 2016
@jfirebaugh jfirebaugh deleted the buffer-refactor branch October 5, 2016 17:52
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 refactor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants