diff options
Diffstat (limited to 'src/core/delegated_frame_node.cpp')
-rw-r--r-- | src/core/delegated_frame_node.cpp | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/src/core/delegated_frame_node.cpp b/src/core/delegated_frame_node.cpp new file mode 100644 index 000000000..c770f388d --- /dev/null +++ b/src/core/delegated_frame_node.cpp @@ -0,0 +1,519 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// On Mac we need to reset this define in order to prevent definition +// of "check" macros etc. The "check" macro collides with a member function name in QtQuick. +// See AssertMacros.h in the Mac SDK. +#include <QtGlobal> // We need this for the Q_OS_MAC define. +#if defined(Q_OS_MAC) +#undef __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES +#define __ASSERT_MACROS_DEFINE_VERSIONS_WITHOUT_UNDERSCORES 0 +#endif + +#include "delegated_frame_node.h" + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)) +#include "chromium_gpu_helper.h" +#include "type_conversion.h" +#include "yuv_video_node.h" + +#include "base/message_loop/message_loop.h" +#include "base/bind.h" +#include "cc/output/delegated_frame_data.h" +#include "cc/quads/draw_quad.h" +#include "cc/quads/render_pass_draw_quad.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/quads/texture_draw_quad.h" +#include "cc/quads/tile_draw_quad.h" +#include "cc/quads/yuv_video_draw_quad.h" +#include <QOpenGLFramebufferObject> +#include <QSGSimpleTextureNode> +#include <QSGTexture> +#include <QtQuick/private/qquickclipnode_p.h> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtQuick/private/qsgadaptationlayer_p.h> +#include <QtQuick/private/qsgcontext_p.h> +#include <QtQuick/private/qsgrenderer_p.h> +#include <QtQuick/private/qsgtexture_p.h> + +class RenderPassTexture : public QSGTexture +{ +public: + RenderPassTexture(const cc::RenderPass::Id &id, QSGRenderContext *context); + + const cc::RenderPass::Id &id() const { return m_id; } + void bind(); + + int textureId() const { return m_fbo ? m_fbo->texture() : 0; } + QSize textureSize() const { return m_rect.size(); } + bool hasAlphaChannel() const { return m_format != GL_RGB; } + bool hasMipmaps() const { return false; } + + void setRect(const QRect &rect) { m_rect = rect; } + void setFormat(GLenum format) { m_format = format; } + void setDevicePixelRatio(qreal ratio) { m_device_pixel_ratio = ratio; } + QSGNode *rootNode() { return m_rootNode.data(); } + + void grab(); + +private: + cc::RenderPass::Id m_id; + QRect m_rect; + qreal m_device_pixel_ratio; + GLenum m_format; + + QScopedPointer<QSGRootNode> m_rootNode; + QScopedPointer<QSGRenderer> m_renderer; + QScopedPointer<QOpenGLFramebufferObject> m_fbo; + + QSGRenderContext *m_context; +}; + +class MailboxTexture : public QSGTexture { +public: + MailboxTexture(const cc::TransferableResource &resource); + virtual int textureId() const Q_DECL_OVERRIDE { return m_textureId; } + void setTextureSize(const QSize& size) { m_textureSize = size; } + virtual QSize textureSize() const Q_DECL_OVERRIDE { return m_textureSize; } + virtual bool hasAlphaChannel() const Q_DECL_OVERRIDE { return m_hasAlpha; } + void setHasAlphaChannel(bool hasAlpha) { m_hasAlpha = hasAlpha; } + virtual bool hasMipmaps() const Q_DECL_OVERRIDE { return false; } + virtual void bind() Q_DECL_OVERRIDE; + + bool needsToFetch() const { return !m_textureId; } + cc::TransferableResource &resource() { return m_resource; } + void fetchTexture(gpu::gles2::MailboxManager *mailboxManager); + +private: + cc::TransferableResource m_resource; + int m_textureId; + QSize m_textureSize; + bool m_hasAlpha; +}; + +static inline QSharedPointer<RenderPassTexture> findRenderPassTexture(const cc::RenderPass::Id &id, const QList<QSharedPointer<RenderPassTexture> > &list) +{ + Q_FOREACH (const QSharedPointer<RenderPassTexture> &texture, list) + if (texture->id() == id) + return texture; + return QSharedPointer<RenderPassTexture>(); +} + +static QSGNode *buildRenderPassChain(QSGNode *chainParent, const cc::RenderPass *renderPass) +{ + // Chromium already ordered the quads from back to front for us, however the + // Qt scene graph layers individual geometries in their own z-range and uses + // the depth buffer to visually stack nodes according to their item tree order. + // This finds the z-span of all layers so that we can z-compress them to fit + // them between 0.0 and 1.0 on the z axis. + double minZ = 0; + double maxZ = 1; + double src2[8]; + double dst4[16]; + // topleft.x, topleft.y, topRight.y and bottomLeft.x + src2[0] = src2[1] = src2[3] = src2[4] = 0; + + // Go through each layer in this pass and find out their transformed rect. + cc::SharedQuadStateList::const_iterator it = renderPass->shared_quad_state_list.begin(); + cc::SharedQuadStateList::const_iterator sharedStateEnd = renderPass->shared_quad_state_list.end(); + for (; it != sharedStateEnd; ++it) { + gfx::Size &layerSize = (*it)->content_bounds; + // topRight.x + src2[2] = layerSize.width(); + // bottomLeft.y + src2[5] = layerSize.height(); + // bottomRight + src2[6] = layerSize.width(); + src2[7] = layerSize.height(); + (*it)->content_to_target_transform.matrix().map2(src2, 4, dst4); + // Check the mapped corner's z value and track the boundaries. + minZ = std::min(std::min(std::min(std::min(minZ, dst4[2]), dst4[6]), dst4[10]), dst4[14]); + maxZ = std::max(std::max(std::max(std::max(maxZ, dst4[2]), dst4[6]), dst4[10]), dst4[14]); + } + + QSGTransformNode *zCompressNode = new QSGTransformNode; + QMatrix4x4 zCompressMatrix; + zCompressMatrix.scale(1, 1, 1 / (maxZ - minZ)); + zCompressMatrix.translate(0, 0, -minZ); + zCompressNode->setMatrix(zCompressMatrix); + chainParent->appendChildNode(zCompressNode); + return zCompressNode; +} + +static QSGNode *buildLayerChain(QSGNode *chainParent, const cc::SharedQuadState *layerState) +{ + QSGNode *layerChain = chainParent; + if (layerState->is_clipped) { + QQuickDefaultClipNode *clipNode = new QQuickDefaultClipNode(toQt(layerState->clip_rect)); + clipNode->update(); + layerChain->appendChildNode(clipNode); + layerChain = clipNode; + } + if (!layerState->content_to_target_transform.IsIdentity()) { + QSGTransformNode *transformNode = new QSGTransformNode; + transformNode->setMatrix(toQt(layerState->content_to_target_transform.matrix())); + layerChain->appendChildNode(transformNode); + layerChain = transformNode; + } + if (layerState->opacity < 1.0) { + QSGOpacityNode *opacityNode = new QSGOpacityNode; + opacityNode->setOpacity(layerState->opacity); + layerChain->appendChildNode(opacityNode); + layerChain = opacityNode; + } + return layerChain; +} + +RenderPassTexture::RenderPassTexture(const cc::RenderPass::Id &id, QSGRenderContext *context) + : QSGTexture() + , m_id(id) + , m_device_pixel_ratio(1) + , m_format(GL_RGBA) + , m_rootNode(new QSGRootNode) + , m_context(context) +{ +} + +void RenderPassTexture::bind() +{ + glBindTexture(GL_TEXTURE_2D, m_fbo ? m_fbo->texture() : 0); + updateBindOptions(); +} + +void RenderPassTexture::grab() +{ + if (!m_rootNode->firstChild()) { + m_renderer.reset(); + m_fbo.reset(); + return; + } + if (!m_renderer) { + m_renderer.reset(m_context->createRenderer()); + m_renderer->setRootNode(m_rootNode.data()); + } + m_renderer->setDevicePixelRatio(m_device_pixel_ratio); + + if (!m_fbo || m_fbo->size() != m_rect.size() || m_fbo->format().internalTextureFormat() != m_format) + { + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + format.setInternalTextureFormat(m_format); + + m_fbo.reset(new QOpenGLFramebufferObject(m_rect.size(), format)); + glBindTexture(GL_TEXTURE_2D, m_fbo->texture()); + updateBindOptions(true); + } + + m_rootNode->markDirty(QSGNode::DirtyForceUpdate); // Force matrix, clip and opacity update. + m_renderer->nodeChanged(m_rootNode.data(), QSGNode::DirtyForceUpdate); // Force render list update. + + m_renderer->setDeviceRect(m_rect.size()); + m_renderer->setViewportRect(m_rect.size()); + QRectF mirrored(m_rect.left(), m_rect.bottom(), m_rect.width(), -m_rect.height()); + m_renderer->setProjectionMatrixToRect(mirrored); + m_renderer->setClearColor(Qt::transparent); + + m_context->renderNextFrame(m_renderer.data(), m_fbo->handle()); +} + +MailboxTexture::MailboxTexture(const cc::TransferableResource &resource) + : m_resource(resource) + , m_textureId(0) + , m_textureSize(toQt(resource.size)) + , m_hasAlpha(false) +{ +} + +void MailboxTexture::bind() +{ + glBindTexture(GL_TEXTURE_2D, m_textureId); +} + +void MailboxTexture::fetchTexture(gpu::gles2::MailboxManager *mailboxManager) +{ + gpu::gles2::Texture *tex = ConsumeTexture(mailboxManager, GL_TEXTURE_2D, *reinterpret_cast<const gpu::gles2::MailboxName*>(m_resource.mailbox.name)); + + // The texture might already have been deleted (e.g. when navigating away from a page). + if (tex) + m_textureId = service_id(tex); +} + +DelegatedFrameNode::DelegatedFrameNode(QQuickWindow *window) + : m_window(window) + , m_numPendingSyncPoints(0) +{ + setFlag(UsePreprocess); +} + +DelegatedFrameNode::~DelegatedFrameNode() +{ +} + +void DelegatedFrameNode::preprocess() +{ + // With the threaded render loop the GUI thread has been unlocked at this point. + // We can now wait for the Chromium GPU thread to produce textures that will be + // rendered on our quads and fetch the IDs from the mailboxes we were given. + QList<MailboxTexture *> mailboxesToFetch; + Q_FOREACH (const QSharedPointer<MailboxTexture> &mailboxTexture, m_mailboxTextures.values()) + if (mailboxTexture->needsToFetch()) + mailboxesToFetch.append(mailboxTexture.data()); + + if (!mailboxesToFetch.isEmpty()) { + QMutexLocker lock(&m_mutex); + base::MessageLoop *gpuMessageLoop = gpu_message_loop(); + content::SyncPointManager *syncPointManager = sync_point_manager(); + + Q_FOREACH (MailboxTexture *mailboxTexture, mailboxesToFetch) { + m_numPendingSyncPoints++; + AddSyncPointCallbackOnGpuThread(gpuMessageLoop, syncPointManager, mailboxTexture->resource().sync_point, base::Bind(&DelegatedFrameNode::syncPointRetired, this, &mailboxesToFetch)); + } + + m_mailboxesFetchedWaitCond.wait(&m_mutex); + } + + // Then render any intermediate RenderPass in order. + Q_FOREACH (const QSharedPointer<RenderPassTexture> &renderPass, m_renderPassTextures) + renderPass->grab(); +} + +void DelegatedFrameNode::commit(cc::DelegatedFrameData *frameData, cc::TransferableResourceArray *resourcesToRelease) +{ + // Keep the old texture lists around to find the ones we can re-use. + QList<QSharedPointer<RenderPassTexture> > oldRenderPassTextures; + m_renderPassTextures.swap(oldRenderPassTextures); + QMap<int, QSharedPointer<MailboxTexture> > mailboxTextureCandidates; + m_mailboxTextures.swap(mailboxTextureCandidates); + + // A frame's resource_list only contains the new resources to be added to the scene. Quads can + // still reference resources that were added in previous frames. Add them to the list of + // candidates to be picked up by quads, it's then our responsibility to return unused resources + // to the producing child compositor. + for (unsigned i = 0; i < frameData->resource_list.size(); ++i) { + const cc::TransferableResource &res = frameData->resource_list.at(i); + mailboxTextureCandidates[res.id] = QSharedPointer<MailboxTexture>(new MailboxTexture(res)); + } + + // The RenderPasses list is actually a tree where a parent RenderPass is connected + // to its dependencies through a RenderPass::Id reference in one or more RenderPassQuads. + // The list is already ordered with intermediate RenderPasses placed before their + // parent, with the last one in the list being the root RenderPass, the one + // that we displayed to the user. + // All RenderPasses except the last one are rendered to an FBO. + cc::RenderPass *rootRenderPass = frameData->render_pass_list.back(); + + for (unsigned i = 0; i < frameData->render_pass_list.size(); ++i) { + cc::RenderPass *pass = frameData->render_pass_list.at(i); + + QSGNode *renderPassParent = 0; + if (pass != rootRenderPass) { + QSharedPointer<RenderPassTexture> rpTexture = findRenderPassTexture(pass->id, oldRenderPassTextures); + if (!rpTexture) { + QSGRenderContext *sgrc = QQuickWindowPrivate::get(m_window)->context; + rpTexture = QSharedPointer<RenderPassTexture>(new RenderPassTexture(pass->id, sgrc)); + } + m_renderPassTextures.append(rpTexture); + rpTexture->setDevicePixelRatio(m_window->devicePixelRatio()); + rpTexture->setRect(toQt(pass->output_rect)); + rpTexture->setFormat(pass->has_transparent_background ? GL_RGBA : GL_RGB); + renderPassParent = rpTexture->rootNode(); + } else + renderPassParent = this; + + // There is currently no way to know which and how quads changed since the last frame. + // We have to reconstruct the node chain with their geometries on every update. + while (QSGNode *oldChain = renderPassParent->firstChild()) + delete oldChain; + + QSGNode *renderPassChain = buildRenderPassChain(renderPassParent, pass); + const cc::SharedQuadState *currentLayerState = 0; + QSGNode *currentLayerChain = 0; + + cc::QuadList::ConstBackToFrontIterator it = pass->quad_list.BackToFrontBegin(); + cc::QuadList::ConstBackToFrontIterator end = pass->quad_list.BackToFrontEnd(); + for (; it != end; ++it) { + cc::DrawQuad *quad = *it; + + if (currentLayerState != quad->shared_quad_state) { + currentLayerState = quad->shared_quad_state; + currentLayerChain = buildLayerChain(renderPassChain, currentLayerState); + } + + switch (quad->material) { + case cc::DrawQuad::RENDER_PASS: { + const cc::RenderPassDrawQuad *renderPassQuad = cc::RenderPassDrawQuad::MaterialCast(quad); + QSGTexture *texture = findRenderPassTexture(renderPassQuad->render_pass_id, m_renderPassTextures).data(); + // cc::GLRenderer::DrawRenderPassQuad silently ignores missing render passes. + if (!texture) + continue; + + QSGSimpleTextureNode *textureNode = new QSGSimpleTextureNode; + textureNode->setRect(toQt(quad->rect)); + textureNode->setTexture(texture); + currentLayerChain->appendChildNode(textureNode); + break; + } case cc::DrawQuad::TEXTURE_CONTENT: { + const cc::TextureDrawQuad *tquad = cc::TextureDrawQuad::MaterialCast(quad); + QSharedPointer<MailboxTexture> &texture = m_mailboxTextures[tquad->resource_id] = mailboxTextureCandidates.take(tquad->resource_id); + Q_ASSERT(texture); + + // FIXME: TransferableResource::size isn't always set properly for TextureDrawQuads, use the size of its DrawQuad::rect instead. + texture->setTextureSize(toQt(quad->rect.size())); + + // TransferableResource::format seems to always be GL_BGRA even though it might not + // contain any pixel with alpha < 1.0. The information about if they need blending + // for the contents itself is actually stored in quads. + // Tell the scene graph to enable blending for a texture only when at least one quad asks for it. + // Do not rely on DrawQuad::ShouldDrawWithBlending() since the shared_quad_state->opacity + // case will be handled by QtQuick by fetching this information from QSGOpacityNodes. + if (!quad->visible_rect.IsEmpty() && !quad->opaque_rect.Contains(quad->visible_rect)) + texture->setHasAlphaChannel(true); + + QSGSimpleTextureNode *textureNode = new QSGSimpleTextureNode; + textureNode->setTextureCoordinatesTransform(tquad->flipped ? QSGSimpleTextureNode::MirrorVertically : QSGSimpleTextureNode::NoTransform); + textureNode->setRect(toQt(quad->rect)); + textureNode->setFiltering(texture->resource().filter == GL_LINEAR ? QSGTexture::Linear : QSGTexture::Nearest); + textureNode->setTexture(texture.data()); + currentLayerChain->appendChildNode(textureNode); + break; + } case cc::DrawQuad::SOLID_COLOR: { + const cc::SolidColorDrawQuad *scquad = cc::SolidColorDrawQuad::MaterialCast(quad); + QSGRenderContext *sgrc = QQuickWindowPrivate::get(m_window)->context; + QSGRectangleNode *rectangleNode = sgrc->sceneGraphContext()->createRectangleNode(); + + // Qt only supports MSAA and this flag shouldn't be needed. + // If we ever want to use QSGRectangleNode::setAntialiasing for this we should + // try to see if we can do something similar for tile quads first. + Q_UNUSED(scquad->force_anti_aliasing_off); + + rectangleNode->setRect(toQt(quad->rect)); + rectangleNode->setColor(toQt(scquad->color)); + rectangleNode->update(); + currentLayerChain->appendChildNode(rectangleNode); + break; + } case cc::DrawQuad::TILED_CONTENT: { + const cc::TileDrawQuad *tquad = cc::TileDrawQuad::MaterialCast(quad); + QSharedPointer<MailboxTexture> &texture = m_mailboxTextures[tquad->resource_id] = mailboxTextureCandidates.take(tquad->resource_id); + Q_ASSERT(texture); + + if (!quad->visible_rect.IsEmpty() && !quad->opaque_rect.Contains(quad->visible_rect)) + texture->setHasAlphaChannel(true); + + QSGSimpleTextureNode *textureNode = new QSGSimpleTextureNode; + textureNode->setRect(toQt(quad->rect)); + textureNode->setFiltering(texture->resource().filter == GL_LINEAR ? QSGTexture::Linear : QSGTexture::Nearest); + textureNode->setTexture(texture.data()); + + // FIXME: Find out if we can implement a QSGSimpleTextureNode::setSourceRect instead of this hack. + // This has to be done at the end since many QSGSimpleTextureNode methods would overwrite this. + QSGGeometry::updateTexturedRectGeometry(textureNode->geometry(), textureNode->rect(), textureNode->texture()->convertToNormalizedSourceRect(toQt(tquad->tex_coord_rect))); + currentLayerChain->appendChildNode(textureNode); + break; + } case cc::DrawQuad::YUV_VIDEO_CONTENT: { + const cc::YUVVideoDrawQuad *vquad = cc::YUVVideoDrawQuad::MaterialCast(quad); + QSharedPointer<MailboxTexture> &yTexture = m_mailboxTextures[vquad->y_plane_resource_id] = mailboxTextureCandidates.take(vquad->y_plane_resource_id); + QSharedPointer<MailboxTexture> &uTexture = m_mailboxTextures[vquad->u_plane_resource_id] = mailboxTextureCandidates.take(vquad->u_plane_resource_id); + QSharedPointer<MailboxTexture> &vTexture = m_mailboxTextures[vquad->v_plane_resource_id] = mailboxTextureCandidates.take(vquad->v_plane_resource_id); + Q_ASSERT(yTexture && uTexture && vTexture); + + // Do not use a reference for this one, it might be null. + QSharedPointer<MailboxTexture> aTexture; + // This currently requires --enable-vp8-alpha-playback and needs a video with alpha data to be triggered. + if (vquad->a_plane_resource_id) + aTexture = m_mailboxTextures[vquad->a_plane_resource_id] = mailboxTextureCandidates.take(vquad->a_plane_resource_id); + + YUVVideoNode *videoNode = new YUVVideoNode(yTexture.data(), uTexture.data(), vTexture.data(), aTexture.data(), toQt(vquad->tex_scale)); + videoNode->setRect(toQt(quad->rect)); + currentLayerChain->appendChildNode(videoNode); + break; + } default: + qWarning("Unimplemented quad material: %d", quad->material); + } + } + } + + // Send resources of remaining candidates back to the child compositors so that they can be freed or reused. + Q_FOREACH (const QSharedPointer<MailboxTexture> &mailboxTexture, mailboxTextureCandidates.values()) { + // The ResourceProvider ensures that the resource isn't used by the parent compositor's GL + // context in the GPU process by inserting a sync point to be waited for by the child + // compositor's GL context. We don't need this since we are triggering the delegated frame + // ack directly from our rendering thread. At this point (in updatePaintNode) we know that + // a frame that was compositing any of those resources has already been swapped. + // Save a bit of overhead by resetting the sync point that has initially been put there + // for us (mainly to clean the output of --enable-gpu-service-logging). + mailboxTexture->resource().sync_point = 0; + + resourcesToRelease->push_back(mailboxTexture->resource()); + } +} + +void DelegatedFrameNode::fetchTexturesAndUnlockQt(DelegatedFrameNode *frameNode, QList<MailboxTexture *> *mailboxesToFetch) +{ + // Fetch texture IDs from the mailboxes while we're on the GPU thread, where the MailboxManager lives. + gpu::gles2::MailboxManager *mailboxManager = mailbox_manager(); + Q_FOREACH (MailboxTexture *mailboxTexture, *mailboxesToFetch) + mailboxTexture->fetchTexture(mailboxManager); + + // glFlush before yielding to the SG thread, whose context might already start using + // some shared resources provided by the unflushed context here, on the Chromium GPU thread. + glFlush(); + + // Chromium provided everything we were waiting for, let Qt start rendering. + QMutexLocker lock(&frameNode->m_mutex); + frameNode->m_mailboxesFetchedWaitCond.wakeOne(); +} + +void DelegatedFrameNode::syncPointRetired(DelegatedFrameNode *frameNode, QList<MailboxTexture *> *mailboxesToFetch) +{ + // The way that sync points are normally used by the GpuCommandBufferStub is that it asks + // the GpuScheduler to resume the work of the associated GL command stream / context once + // the sync point has been retired by the dependency's context. In other words, a produced + // texture means that the mailbox can be consumed, but the texture itself isn't expected + // to be ready until to control is given back to the GpuScheduler through the event loop. + // Do the same for our implementation by posting a message to the event loop once the last + // of our syncpoints has been retired (the syncpoint callback is called synchronously) and + // only at this point we wake the Qt rendering thread. + QMutexLocker lock(&frameNode->m_mutex); + if (!--frameNode->m_numPendingSyncPoints) + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(&DelegatedFrameNode::fetchTexturesAndUnlockQt, frameNode, mailboxesToFetch)); +} + +#endif // QT_VERSION |