QRhiSwapChain Class

Swapchain resource. More...

Header: #include <QRhiSwapChain>
qmake: QT += rhi
Inherits: QRhiResource

Public Types

enum Flag { SurfaceHasPreMulAlpha, SurfaceHasNonPreMulAlpha, sRGB, UsedAsTransferSource, NoVSync, MinimalBufferCount }
flags Flags

Public Functions

virtual bool buildOrResize() = 0
virtual QRhiCommandBuffer *currentFrameCommandBuffer() = 0
virtual QRhiRenderTarget *currentFrameRenderTarget() = 0
QSize currentPixelSize() const
QRhiRenderBuffer *depthStencil() const
QRhiSwapChain::Flags flags() const
virtual QRhiRenderPassDescriptor *newCompatibleRenderPassDescriptor() = 0
QRhiRenderPassDescriptor *renderPassDescriptor() const
int sampleCount() const
void setDepthStencil(QRhiRenderBuffer *ds)
void setFlags(QRhiSwapChain::Flags f)
void setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)
void setSampleCount(int samples)
void setWindow(QWindow *window)
virtual QSize surfacePixelSize() = 0
QWindow *window() const

Reimplemented Public Functions

virtual QRhiResource::Type resourceType() const override

Protected Variables

QSize m_currentPixelSize
QRhiRenderBuffer *m_depthStencil
QRhiSwapChain::Flags m_flags
QRhiRenderPassDescriptor *m_renderPassDesc
int m_sampleCount
QWindow *m_window

Additional Inherited Members

Detailed Description

Swapchain resource.

A swapchain enables presenting rendering results to a surface. A swapchain is typically backed by a set of color buffers. Of these, one is displayed at a time.

Below is a typical pattern for creating and managing a swapchain and some associated resources in order to render onto a QWindow:


  void init()
  {
      sc = rhi->newSwapChain();
      ds = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
                                QSize(), // no need to set the size yet
                                1,
                                QRhiRenderBuffer::UsedWithSwapChainOnly);
      sc->setWindow(window);
      sc->setDepthStencil(ds);
      rp = sc->newCompatibleRenderPassDescriptor();
      sc->setRenderPassDescriptor(rp);
      resizeSwapChain();
  }

  void resizeSwapChain()
  {
      const QSize outputSize = sc->surfacePixelSize();
      ds->setPixelSize(outputSize);
      ds->build();
      hasSwapChain = sc->buildOrResize();
  }

  void render()
  {
      if (!hasSwapChain || notExposed)
          return;

      if (sc->currentPixelSize() != sc->surfacePixelSize() || newlyExposed) {
          resizeSwapChain();
          if (!hasSwapChain)
              return;
          newlyExposed = false;
      }

      rhi->beginFrame(sc);
      // ...
      rhi->endFrame(sc);
  }

Avoid relying on QWindow resize events to resize swapchains, especially considering that surface sizes may not always fully match the QWindow reported dimensions. The safe, cross-platform approach is to do the check via surfacePixelSize() whenever starting a new frame.

Releasing the swapchain must happen while the QWindow and the underlying native window is fully up and running. Building on the previous example:


  void releaseSwapChain()
  {
      if (hasSwapChain) {
          sc->release();
          hasSwapChain = false;
      }
  }

  // assuming Window is our QWindow subclass
  bool Window::event(QEvent *e)
  {
      switch (e->type()) {
      case QEvent::UpdateRequest: // for QWindow::requestUpdate()
          render();
          break;
      case QEvent::PlatformSurface:
          if (static_cast<QPlatformSurfaceEvent *>(e)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
              releaseSwapChain();
          break;
      default:
          break;
      }
      return QWindow::event(e);
  }

Initializing the swapchain and starting to render the first frame cannot start at any time. The safe, cross-platform approach is to rely on expose events. QExposeEvent is a loosely specified event that is sent whenever a window gets mapped, obscured, and resized, depending on the platform.


  void Window::exposeEvent(QExposeEvent *)
  {
      // initialize and start rendering when the window becomes usable for graphics purposes
      if (isExposed() && !running) {
          running = true;
          init();
      }

      // stop pushing frames when not exposed or size becomes 0
      if ((!isExposed() || (hasSwapChain && sc->surfacePixelSize().isEmpty())) && running)
          notExposed = true;

      // continue when exposed again and the surface has a valid size
      if (isExposed() && running && notExposed && !sc->surfacePixelSize().isEmpty()) {
          notExposed = false;
          newlyExposed = true;
      }

      if (isExposed() && !sc->surfacePixelSize().isEmpty())
          render();
  }

Once the rendering has started, a simple way to request a new frame is QWindow::requestUpdate(). While on some platforms this is merely a small timer, on others it has a specific implementation: for instance on macOS or iOS it may be backed by CVDisplayLink. The example above is already prepared for update requests by handling QEvent::UpdateRequest.

While acting as a QRhiRenderTarget, QRhiSwapChain also manages a QRhiCommandBuffer. Calling QRhi::endFrame() submits the recorded commands and also enqueues a present request. The default behavior is to do this with a swap interval of 1, meaning synchronizing to the display's vertical refresh is enabled. Thus the rendering thread calling beginFrame() and endFrame() will get throttled to vsync. On some backends this can be disabled by passing QRhiSwapChain:NoVSync in flags().

Multisampling (MSAA) is handled transparently to the applications when requested via setSampleCount(). Where applicable, QRhiSwapChain will take care of creating additional color buffers and issuing a multisample resolve command at the end of a frame. For OpenGL, it is necessary to request the appropriate sample count also via QSurfaceFormat, by calling QSurfaceFormat::setDefaultFormat() before initializing the QRhi.

Member Type Documentation

enum QRhiSwapChain::Flag
flags QRhiSwapChain::Flags

Flag values to describe swapchain properties

ConstantValueDescription
QRhiSwapChain::SurfaceHasPreMulAlpha1 << 0Indicates that the target surface has transparency with premultiplied alpha.
QRhiSwapChain::SurfaceHasNonPreMulAlpha1 << 1Indicates the target surface has transparencyt with non-premultiplied alpha.
QRhiSwapChain::sRGB1 << 2Requests to pick an sRGB format for the swapchain and/or its render target views, where applicable. Note that this implies that sRGB framebuffer update and blending will get enabled for all content targeting this swapchain, and opting out is not possible. For OpenGL, set sRGBColorSpace on the QSurfaceFormat of the QWindow in addition.
QRhiSwapChain::UsedAsTransferSource1 << 3Indicates the the swapchain will be used as the source of a readback in QRhiResourceUpdateBatch::readBackTexture().
QRhiSwapChain::NoVSync1 << 4Requests disabling waiting for vertical sync, also avoiding throttling the rendering thread. The behavior is backend specific and applicable only where it is possible to control this. Some may ignore the request altogether. For OpenGL, try instead setting the swap interval to 0 on the QWindow via QSurfaceFormat::setSwapInterval().
QRhiSwapChain::MinimalBufferCount1 << 5Requests creating the swapchain with the minimum number of buffers, which is in practice 2, unless the graphics implementation has a higher minimum number than that. Only applicable with backends where such control is available via the graphics API, for example, Vulkan. By default it is up to the backend to decide what number of buffers it requests (in practice this is almost always either 2 or 3), and it is not the applications' concern. However, on Vulkan for instance the backend will likely prefer the higher number (3), for example to avoid odd performance issues with some Vulkan implementations on mobile devices. It could be that on some platforms it can prove to be beneficial to force the lower buffer count (2), so this flag allows forcing that. Note that all this has no effect on the number of frames kept in flight, so the CPU (QRhi) will still prepare frames at most N - 1 frames ahead of the GPU, even when the swapchain image buffer count larger than N. (N = QRhi::FramesInFlight and typically 2).

The Flags type is a typedef for QFlags<Flag>. It stores an OR combination of Flag values.

Property Documentation

Member Function Documentation

[pure virtual] bool QRhiSwapChain::buildOrResize()

Creates the swapchain if not already done and resizes the swapchain buffers to match the current size of the targeted surface. Call this whenever the size of the target surface is different than before.

Note: call release() only when the swapchain needs to be released completely, typically upon QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed. To perform resizing, just call buildOrResize().

Returns true when successful, false when a graphics operation failed. Regardless of the return value, calling release() is always safe.

[pure virtual] QRhiCommandBuffer *QRhiSwapChain::currentFrameCommandBuffer()

Returns a command buffer on which rendering commands can be recorded. Only valid within a QRhi::beginFrame() - QRhi::endFrame() block where beginFrame() was called with this swapchain.

Note: the value must not be cached and reused between frames

[pure virtual] QRhiRenderTarget *QRhiSwapChain::currentFrameRenderTarget()

Returns a render target that can used with beginPass() in order to render the the swapchain's current backbuffer. Only valid within a QRhi::beginFrame() - QRhi::endFrame() block where beginFrame() was called with this swapchain.

Note: the value must not be cached and reused between frames

QSize QRhiSwapChain::currentPixelSize() const

Returns the size with which the swapchain was last successfully built. Use this to decide if buildOrResize() needs to be called again: if currentPixelSize() != surfacePixelSize() then the swapchain needs to be resized.

See also surfacePixelSize().

QRhiRenderBuffer *QRhiSwapChain::depthStencil() const

See also setDepthStencil().

QRhiSwapChain::Flags QRhiSwapChain::flags() const

See also setFlags().

[pure virtual] QRhiRenderPassDescriptor *QRhiSwapChain::newCompatibleRenderPassDescriptor()

QRhiRenderPassDescriptor *QRhiSwapChain::renderPassDescriptor() const

See also setRenderPassDescriptor().

[override virtual] QRhiResource::Type QRhiSwapChain::resourceType() const

Reimplemented from QRhiResource::resourceType().

Returns the resource type.

int QRhiSwapChain::sampleCount() const

See also setSampleCount().

void QRhiSwapChain::setDepthStencil(QRhiRenderBuffer *ds)

See also depthStencil().

void QRhiSwapChain::setFlags(QRhiSwapChain::Flags f)

See also flags().

void QRhiSwapChain::setRenderPassDescriptor(QRhiRenderPassDescriptor *desc)

See also renderPassDescriptor().

void QRhiSwapChain::setSampleCount(int samples)

See also sampleCount().

void QRhiSwapChain::setWindow(QWindow *window)

See also window().

[pure virtual] QSize QRhiSwapChain::surfacePixelSize()

Returns The size of the window's associated surface or layer. Do not assume this is the same as QWindow::size() * QWindow::devicePixelRatio().

Can be called before buildOrResize() (but with window() already set), which allows setting the correct size for the depth-stencil buffer that is then used together with the swapchain's color buffers. Also used in combination with currentPixelSize() to detect size changes.

See also currentPixelSize().

QWindow *QRhiSwapChain::window() const

See also setWindow().

Member Variable Documentation

Related Non-Members

Macro Documentation