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):
Additional context
Note that I'm doing this in headless mode.
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!
Most helpful comment
Instead of sleeping, do
engine->flushAndWait().