Filament: OpenGLDriver::readPixels not getting called

Created on 9 Nov 2020  路  6Comments  路  Source: google/filament

Describe the bug
When calling renderer->readPixels, I noticed that the callback wasn't getting hit.

  int outputSize = width * height * 4;
  uint8_t* pixels = new uint8_t[outputSize];
  filament::backend::PixelBufferDescriptor bufferA(
    pixels,
    outputSize,
    backend::PixelBufferDescriptor::PixelDataFormat::RGBA,
    backend::PixelBufferDescriptor::PixelDataType::UBYTE,
     [](void* buffer, size_t size, void* user) {
      // this isn't getting hit
  }, nullptr);
  renderer->readPixels(target, 0, 0, width, height, std::move(bufferA));

When debugging, it seems the OpenGLDriver::readPixels is not getting called. I added some print statements to confirm

void FRenderer::readPixels(Handle<HwRenderTarget> renderTargetHandle,
        uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
        backend::PixelBufferDescriptor&& buffer) {
    ....
    // I added this just to make sure I could properly call into driver
    std::cout << "Calling into driver isFrameTimeSupported" << std::endl;
    driver.isFrameTimeSupported();
    std::cout << "Calling into driver readPixels" << std::endl;
    driver.readPixels(renderTargetHandle, xoffset, yoffset, width, height, std::move(buffer));
void FRenderer::readPixels(Handle<HwRenderTarget> renderTargetHandle,
        uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
        backend::PixelBufferDescriptor&& buffer) {
    std::cout << "Hit FRender readPixels with handle" << std::endl;



md5-714fa2ad70df3f5c901615f20a4b2523



...
Calling into driver isFrameTimeSupported
Hit isFrameTimeSupported()
Calling into driver readPixels
...



md5-807c4ad35a57ce75054314266cd5adca



#define SYSTRACE_TAG 0

#include <filament/Camera.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/RenderTarget.h>
#include <filament/Renderer.h>
#include <filament/Scene.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <filament/VertexBuffer.h>
#include <filament/View.h>
#include <filament/Viewport.h>
#include <utils/EntityManager.h>

#include "third_party/filament/gen/osx/generated/resources/materials.h"
#include "third_party/filament/gen/osx/generated/resources/resources.h"

#include <iostream>
#include <unistd.h>

#include <OpenGL/OpenGL.h>

using namespace filament;

struct Vertex {
  math::float2 position;
  math::float2 uv;
};

static const Vertex TRIANGLE_VERTICES[3] = {
    {{1, 0}, 0xffff0000u},
    {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u},
    {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu},
};

static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 };

int main(int argc, char **argv) {
  Engine* engine = Engine::create();
  // I think this creates a headless swap chain
  SwapChain* swapChain = engine->createSwapChain(width, height, 0);
  View* view = engine->createView();
  Scene* scene = engine->createScene();
  Renderer* renderer = engine->createRenderer();

  utils::Entity camera = utils::EntityManager::get().create();
  Camera* cam = engine->createCamera(camera);
  const double aspect = (double) width / height;
  const double left   = -2.0 * aspect;
  const double right  =  2.0 * aspect;
  const double bottom = -2.0;
  const double top    =  2.0;
  const double near   =  0.0;
  const double far    =  1.0;
  cam->setProjection(Camera::Projection::ORTHO, left, right, bottom, top, near, far);
  view->setScene(scene);

  // this is our offscreen render target
  Texture* color = Texture::Builder()
        .width(width).height(height).levels(1)
        .usage(Texture::Usage::COLOR_ATTACHMENT | Texture::Usage::SAMPLEABLE)
        .format(Texture::InternalFormat::RGBA16F).build(*engine);
  Texture* depth = Texture::Builder()
        .width(width).height(height).levels(1)
        .usage(Texture::Usage::DEPTH_ATTACHMENT)
        .format(Texture::InternalFormat::DEPTH24).build(*engine);
  RenderTarget* target = RenderTarget::Builder()
        .texture(RenderTarget::COLOR, color)
        .texture(RenderTarget::DEPTH, depth)
        .build(*engine);

  view->setName("offscreen");
  view->setRenderTarget(target);
  view->setViewport(Viewport(0, 0, width, height));
  view->setScene(scene);
  view->setCamera(cam);

  renderer->setClearOptions({
    .clearColor = { 0.0f, 0.13f, 0.0f, 1.0f },
    .clear = true
  });

  Skybox* skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine);
  scene->setSkybox(skybox);

  // ADD TRIANGLE
  VertexBuffer* vb = VertexBuffer::Builder()
          .vertexCount(3)
          .bufferCount(1)
          .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12)
          .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12)
          .normalized(VertexAttribute::COLOR)
          .build(*engine);
  vb->setBufferAt(*engine, 0,
          VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr));
  IndexBuffer* ib = IndexBuffer::Builder()
          .indexCount(3)
          .bufferType(IndexBuffer::IndexType::USHORT)
          .build(*engine);
  ib->setBuffer(*engine,
          IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr));
  Material* mat = Material::Builder()
          .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE)
          .build(*engine);
  utils::Entity renderable = utils::EntityManager::get().create();
  RenderableManager::Builder(1)
          .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }})
          .material(0, mat->getDefaultInstance())
          .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vb, ib, 0, 3)
          .culling(false)
          .receiveShadows(false)
          .castShadows(false)
          .build(*engine, renderable);
  scene->addEntity(renderable);


  if (renderer->beginFrame(swapChain)) {
    renderer->render(view);
    renderer->endFrame();
  }

  // get back what we have rendered
  int outputSize = width * height * 4;
  uint8_t* pixels = new uint8_t[outputSize];
  filament::backend::PixelBufferDescriptor bufferA(
    pixels,
    outputSize,
    backend::PixelBufferDescriptor::PixelDataFormat::RGBA,
    backend::PixelBufferDescriptor::PixelDataType::UBYTE,
     [](void* buffer, size_t size, void* user) {
       // TODO
  }, nullptr);
  renderer->readPixels(target, 0, 0, width, height, std::move(bufferA));

  return 0;
}

Expected behavior
I expected OpenGLDriver::readPixels to be called and that PixelBufferDescriptor callback would be called

Desktop (please complete the following information):

  • OS: macOS Catalina
  • Backend: OpenGL

Additional context
Note that I'm doing this in headless mode.

Most helpful comment

Instead of sleeping, do engine->flushAndWait().

All 6 comments

renderer->readPixels() is not a blocking call, you are exiting the application before the driver/backend implementation can be executed.

In your case you probably want to use a latch or a fence, or simply exit the app from the callback.

I am using a 10 second sleep on the main thread after renderer->readPixels(target, 0, 0, width, height, std::move(bufferA)); and readPixels isn't called. Is that expected?
https://pubs.opengroup.org/onlinepubs/009695399/functions/sleep.html

I'll look into a latch or fence.

Added this to the end of my main function and the driver.readPixels hasn't been called:

  while (true) {
    C8Log("Sleeping...");
    std::this_thread::sleep_for(std::chrono::seconds(10));
    C8Log("Awake!");
  }

Instead of sleeping, do engine->flushAndWait().

Perfect, that worked great! Thanks @prideout and @romainguy!

Was this page helpful?
0 / 5 - 0 ratings