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

Implemented ScatterplotLayer #124

Merged
merged 2 commits into from
May 4, 2020
Merged

Conversation

ilijapuaca
Copy link
Contributor

Two things that I had to cover that are specific to this layer:

  • It's using layer uniforms in the fragment shader, so I've added support for specifying the stage uniforms should be bound in
  • It was using triangle fan which doesn't seem to be supported in WebGPU. I've converted the points so that they define the same geometry using triangle strips

That being said I don't think these look the way they're supposed to look, they seem to be squares instead of circles that I'm seeing on the web. Also, when zooming in very closely, they seem to stop scaling at some point. I'm seeing weird geometry in the geometry inspector (could just be a bug in the tool, it's pretty flimsy sometimes) although all of the values seem valid, no NaNs or anything.

I also did not test the line component, are there any examples that make use of it?

Screen Shot 2020-05-02 at 14 37 30

Screen Shot 2020-05-02 at 15 01 58

Copy link
Contributor

@ibgreen ibgreen left a comment

Choose a reason for hiding this comment

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

Very nice, this is progressing really fast now!

auto createScatterplotLayer(const std::string &dataPath) -> std::shared_ptr<ScatterplotLayer::Props> {
auto scatterplotLayerProps = std::make_shared<ScatterplotLayer::Props>();
scatterplotLayerProps->id = "airports";
scatterplotLayerProps->getPosition = [](const Row &row) -> mathgl::Vector3<float> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Use return type deduction to minimize the amount of boilerplate

auto [](const Row &row)  { return row.getVector3<float>("coordinates"); };

The method is already calling out the return type (which is also why I was softly lobbying for templates, responding to question in another PR).

If we just call the helper variable props we may be able to fit it into one line, which would increase readability (at least according to my personal aestethics).

@@ -67,10 +67,10 @@ class LineLayer::Props : public Layer::Props {
return new LineLayer{std::dynamic_pointer_cast<LineLayer::Props>(props)};
}

std::string widthUnits{"pixels"}; // 'pixels',
float widthScale{1}; // {type: 'number', value: 1, min: 0},
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: We haven't captured the min values in the prop types, so we can't check those.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How should we deal with these kind of ranges in general? Is there a way to get these clamped implicitly without relying on a backing type?

@@ -140,6 +114,9 @@ void ScatterplotLayer::initializeState() {
auto lineWidth = std::make_shared<arrow::Field>("instanceLineWidths", arrow::float32());
auto getLineWidth = std::bind(&ScatterplotLayer::getLineWidthData, this, std::placeholders::_1);
this->attributeManager->add(garrow::ColumnBuilder{lineWidth, getLineWidth});

// TODO(ilija@unfolded.ai): Where should we initialize models?
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, creating Models in initializeState is what we do. Because at this time, the layerContext has been assigned so WGPU device etc is available at this time.

auto lineWidthScale = props->lineWidthScale * widthMultiplier;

ScatterplotLayerUniforms layerUniforms{
props->opacity, props->radiusScale, props->radiusMinPixels, props->radiusMaxPixels,
Copy link
Contributor

Choose a reason for hiding this comment

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

Long lists of positional parameters of same type (float etc) looks very error prone as the compiler can only do limited checking.

Either use the C++20 designated initializers or assign one by one?

for (auto const& model : this->getModels()) {
// Layer uniforms are currently bound to index 1
model->setUniforms(
std::make_shared<garrow::Array>(this->context->device, &layerUniforms, 1, wgpu::BufferUsage::Uniform), 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like we are creating and destroying a lot of GPU buffers.

Fine for now just to get rendering working, but longer term, GPUBuffers should be populated in updateState if possible so that they are ready to just set 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.

Moved buffer creation, data setting and binding across initialize/update/draw state methods.

We now create a single buffer that gets filled with data, which works well except that updateState isn't called when viewport updates (is that supposed to be the case?), so widthMultiplier isn't correct.

Temporarily made it so that updateState is called from drawState, which can easily be removed once diffing/dirty flags work correctly

Copy link
Contributor

Choose a reason for hiding this comment

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

We now create a single buffer that gets filled with data, which works well except that updateState isn't called when viewport updates (is that supposed to be the case?), so widthMultiplier isn't correct.

I suggest we design this somewhat independently from JS. The JS API didn't moved all uniform updating into updateState. It also implements the "uniform animation" feature in a JavaScript dependent way.

Updating when viewport changes can trigger a lot of updates, e.g. when using multiple views we get multiple updates every frame. So we want to minimize calls if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure I fully understand where we should be doing the updates then, let's sync on this one

@@ -152,7 +152,7 @@ auto Model::_createBindGroupLayout(wgpu::Device device, const std::vector<Unifor
-> wgpu::BindGroupLayout {
std::vector<wgpu::BindGroupLayoutBinding> bindings;
for (uint32_t i = 0; i < uniforms.size(); i++) {
auto binding = wgpu::BindGroupLayoutBinding{i, wgpu::ShaderStage::Vertex, wgpu::BindingType::UniformBuffer,
auto binding = wgpu::BindGroupLayoutBinding{i, uniforms[i].shaderStage, wgpu::BindingType::UniformBuffer,
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we could just default this to always assign to both, so apps don't need to track this? Or do we get warnings and inefficiencies if we assign it but then it is not used?

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 not what the implications of doing that would be, if there's an additional overhead (I assume there is, else it would be done by default?) I'd prefer we declare it explicitly. If not we can of course get rid of it

@ibgreen
Copy link
Contributor

ibgreen commented May 2, 2020

That being said I don't think these look the way they're supposed to look, they seem to be squares instead of circles that I'm seeing on the web. Also, when zooming in very closely, they seem to stop scaling at some point. I'm seeing weird geometry in the geometry inspector (could just be a bug in the tool, it's pretty flimsy sometimes) although all of the values seem valid, no NaNs or anything.

An initial key observation is that the "circles" in the scatterplot layer are created in the fragment shader, not the vertex shader. I.e. we do not have a geometry that approximates a circle. We have a square geometry, and we discard any pixels that are at length(position) > 1 (or position * position > 1).

It is then worth verifying that this calculation/approach is still working here - the code looks OK from a quick scan, but worth poking around commenting things out etc and make sure it has the intended effect.

Also, a trivial mistake could have been made when converting the geometry. It should be a "unit" square.

I also did not test the line component, are there any examples that make use of it?

Assuming you are talking about the outline around the circles, that is a fairly recent addition to the scatterplotlayer and I don't know if any examples that use it (@Pessimistress may know more)

ilijapuaca added a commit that referenced this pull request May 4, 2020
@ilijapuaca
Copy link
Contributor Author

Addressed pending comments. Spent some time debugging potential issues with circle rounding, it seems to be a precision problem, where we'd end up with a larger radius than we're supposed to, leaving us with a circle that isn't rounded perfectly.

Changing SMOOTH_EDGE_RADIUS in geometry.glsl.h to 0 seems to completely get rid of the issue. I am not clear on the specifics about smoothedge/smoothstep, I see that it's a way to interpolate values, but I am not sure why it'd cause results we're seeing when it has the same value in JS.

I am not sure what other implications updating that value might have, so I left it as-is, but that's the only thing I found that looked suspicious. Will revisit based on feedback there, and merge this to avoid code pilling up

@ilijapuaca ilijapuaca merged commit 3611925 into master May 4, 2020
@ilijapuaca ilijapuaca deleted the feature/scatterplot-layer branch May 8, 2020 06:39
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.

None yet

2 participants