-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Preserve depth buffer between 3D layers + optimize render order #9931
Conversation
😳 looks like all the various ways the different CI runs were failing were all because of d965338, so this is ready for review. A few things I want to point out: One thing I want to flag is that I removed a few of the methods I added in the original fill-extrusion PR, as they're no longer needed here:
While these could be breaking changes if anything externally was using these APIs, I don't imagine they were being used anywhere other than fill-extrusion layers — do you think it's fine to remove these or do they need to be preserved just in case? |
entry.second->startRender(parameters); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Drawing the stencil mask before the 3D pass doesn't seem necessary since we're not using it, right? Drawing the clipping mask into the stencil buffer before means that we're drawing to the FBO before the clear step, which means that the OpenGL implementation has to restore the previous frame's stencil buffer (and it might potentially produce inaccurate clipping masks).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right — I didn't dig into startRender
fully, but it looks like it both generates clipping IDs (I don't know how this works) and uploads buffers. (Without moving this step before drawing, the fill-extrusion primitive drawing runs into EXC_BAD_ACCESS
here: )
And then like I said I know nothing about how the stencil drawing works but it at least reads like the actual rendering of stencil masks is still done in a separate step:
mapbox-gl-native/src/mbgl/renderer/renderer_impl.cpp
Lines 426 to 434 in 7531448
// - CLIPPING MASKS ---------------------------------------------------------------------------- | |
// Draws the clipping masks to the stencil buffer. | |
{ | |
MBGL_DEBUG_GROUP(parameters.context, "clipping masks"); | |
static const style::FillPaintProperties::PossiblyEvaluated properties {}; | |
static const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0); | |
for (const auto& clipID : parameters.clipIDGenerator.getClipIDs()) { |
If that's the case, maybe copying the descriptions of this (comments + debug group) exactly is misleading and should be updated to sound more like "upload buffers + generate clipping IDs." Or does uploading and generating clipping IDs need to be separated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it looks like the code in the (old) CLIPPING MASKS
section does a few things: startRender
collects the clipping IDs (CPU code only, no OpenGL involved here), and uploads buffers (OpenGL is involved here), then proceeds to actually render the clipping mask. I think we can split this up so we have the following code flow:
- Bind
- Call
upload
/startRender
- 3D pass
- Clear
Looking at the current code, this is already how it's set up. I assumed that startRender
would actually be drawing, but that turned out to be false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consolidated this group with the "upload" group above it in 9e786c1.
if (!imagePosA || !imagePosB) { | ||
return; | ||
renderTexture->bind(); | ||
renderTexture->attachRenderbuffer(*parameters.staticData.depthRenderbuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of first creating the texture and FBO (inside OffscreenTexture
), validating + binding, then attaching the framebuffer (which requires revalidation), we should pass the depthRenderbuffer
as an (optional) argument to OffscreenTexture
construction so that we can internally call context.createFramebuffer(const Texture&, const Renderbuffer<RenderbufferType::DepthStencil>&)
instead of exposing an additional bindRenderbuffer
.
Additionally, it looks like the depth renderbuffer is attached on every frame (which requires revalidation on every frame). Moving it to the constructor would solve that problem as well.
class OffscreenTexture { | ||
public: | ||
OffscreenTexture(gl::Context&, | ||
Size size = { 256, 256 }, | ||
OffscreenTextureAttachment type = OffscreenTextureAttachment::None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per above, instead of passing in a type here, we could optionally pass in a gl::Renderbuffer<gl::RenderbufferType::DepthComponent>
reference (or rather have two constructors, one with and one without).
src/mbgl/renderer/renderer_impl.cpp
Outdated
if (!parameters.staticData.depthRenderbuffer || | ||
parameters.staticData.depthRenderbuffer->size != size) { | ||
parameters.staticData.depthRenderbuffer = | ||
parameters.context.createRenderbuffer<gl::RenderbufferType::DepthComponent>(size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before creating any objects here, we should first check if there are even extrusion layers in the stylesheet. Otherwise, all of those resources + backend binding won't be necessary.
src/mbgl/gl/renderbuffer.hpp
Outdated
using type = std::integral_constant<RenderbufferType, renderbufferType>; | ||
Size size; | ||
gl::UniqueRenderbuffer renderbuffer; | ||
UniqueRenderbuffer renderbuffer; | ||
bool dirty; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please document what "dirty" means in this context? (or alternatively write convenience/wrapper functions around clearing the renderbuffer).
|
* Add convenience methods to clarify use of renderbuffer::dirty * Add OffscreenTexture constructor with rbo ref param so that initial framebuffer creation/binding can bind depth rbo at the same time
src/mbgl/renderer/renderer_impl.cpp
Outdated
// depth rbo between them. | ||
if (parameters.staticData.has3D) { | ||
MBGL_DEBUG_GROUP(parameters.context, "3d"); | ||
parameters.backend.bind(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this call isn't necessary. Binding the backend means that the default framebuffer is going to be bound and that its viewport will be set accordingly. However, in the first call to RenderFillExtrusionLayer::render()
, we're calling renderTexture->bind();
, which binds the OffscreenTexture
's framebuffer.
src/mbgl/renderer/renderer_impl.cpp
Outdated
parameters.backend.bind(); | ||
parameters.pass = RenderPass::Pass3D; | ||
|
||
const auto size = parameters.context.viewport.getCurrentValue().size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parameters.context
contains the value of the current OpenGL state (unless it is dirty). Instead of using this value, we should use the canonical value from parameters.state.getSize()
, since the OpenGL state's value could be dirty or set to some other value if we don't bind the backend first (see above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kkaefer interesting — when I change this to parameters.state.getSize()
here (renderbuffer creation) and in framebuffer/texture creation
mapbox-gl-native/src/mbgl/renderer/renderer_impl.cpp
Lines 392 to 404 in 0efb0b7
if (parameters.staticData.has3D) { | |
MBGL_DEBUG_GROUP(parameters.context, "3d"); | |
parameters.backend.bind(); | |
parameters.pass = RenderPass::Pass3D; | |
const auto size = parameters.context.viewport.getCurrentValue().size; | |
if (!parameters.staticData.depthRenderbuffer || | |
parameters.staticData.depthRenderbuffer->size != size) { | |
parameters.staticData.depthRenderbuffer = | |
parameters.context.createRenderbuffer<gl::RenderbufferType::DepthComponent>(size); | |
} | |
parameters.staticData.depthRenderbuffer->shouldClear(true); |
if (parameters.pass == RenderPass::Pass3D) { | |
const auto size = parameters.context.viewport.getCurrentValue().size; | |
if (!renderTexture || renderTexture->getSize() != size) { | |
renderTexture = OffscreenTexture(parameters.context, size, *parameters.staticData.depthRenderbuffer); | |
} |
on my iPhone, it appears you're right that this value is different, but that the dimensions of parameters.state.getSize()
are half those of the others (non-retina). If I change them, fill-extrusion layers degrade to non-retina quality…
src/mbgl/gl/context.cpp
Outdated
checkFramebuffer(); | ||
return { depthTarget.size, std::move(fbo) }; | ||
return { color.size, std::move(fbo) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only formatting/variable names changed here (except for size equality check); can we avoid this change please?
… backend in 3D pass
@kkaefer (commenting here rather than inline as they're getting collapsed and hard to find): In the 3D pass (permalinking mapbox-gl-native/src/mbgl/renderer/renderer_impl.cpp Lines 389 to 416 in 0efb0b7
🤔 |
Good point. We have two different sizes; the canonical size, and the actual framebuffer size. In order to render at the correct resolution, we need the framebuffer size. In your initial code, you obtained that from |
…t get destroyed when removing a layer
…inding or changing state
6f51101
to
f0fa915
Compare
I think this is ready — @kkaefer if you agree then I'll ping a few mobile folks for 👀 on the small platform-specific changes ( |
@tobrun or @Guardiola31337 — can I bug one of you for a quick glance at the Android files changed here? It's pretty straightforward but I have zero Android experience/devices to test on. |
@brunoabinader can I bug you for a quick glance at the Qt changes here? They're pretty straightforward but I haven't tested them on any Qt device 😬 |
Tagging @fabian-guerra for iOS. @lbud I think the changes in MGLMapView are small. Do you need @fabian-guerra to help test on device? |
Thanks @boundsj! I tested on my own 🍎 devices so I feel comfortable with the ios/macos changes functionally. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested on 3 Android devices, didn't notice any regressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working great on Qt! 🚢
src/mbgl/gl/context.cpp
Outdated
auto fbo = createFramebuffer(); | ||
bindFramebuffer = fbo; | ||
// TODO: revert this; just for debugging on CI: | ||
GLuint depthInt = depthTarget.renderbuffer; | ||
Log::Info(Event::General, "bound framebuffer: %d; renderbuffer name: %d", bindFramebuffer, depthInt); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got this on Qt app's stdout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops 🙈 good catch, thanks @brunoabinader!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iOS/macOS code looks good to me. 👍🏼
Port of mapbox/mapbox-gl-js#5101:
Also fixes #9978 (I think)…