Compare commits

...

1 Commits

Author SHA1 Message Date
Powei Feng
3e32e6cf2a backend-test: centralize read pixel logic 2024-05-16 15:08:56 -07:00
4 changed files with 109 additions and 174 deletions

View File

@@ -20,6 +20,7 @@
#include "BackendTest.h"
#include <utils/Log.h>
#include <utils/Hash.h>
#include <fstream>
@@ -149,50 +150,59 @@ void BackendTest::renderTriangle(Handle<HwRenderTarget> renderTarget,
}
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
void* buffer = calloc(1, width * height * 4);
Handle<HwRenderTarget> rt, uint32_t const expectedHash, bool const exportScreenshot) {
auto img = getRenderTargetRGB(rt, width, height);
uint32_t hash = computeImageHash(img);
struct Capture {
uint32_t expectedHash;
char* name;
bool exportScreenshot;
size_t width, height;
};
auto* c = new Capture();
c->expectedHash = expectedHash;
c->name = strdup(testName);
c->exportScreenshot = exportScreenshot;
c->width = width;
c->height = height;
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE,
1, 0, 0, width, [](void* buffer, size_t size, void* user) {
auto* c = (Capture*)user;
// Export a screenshot, if requested.
if (c->exportScreenshot) {
#ifndef IOS
LinearImage image(c->width, c->height, 4);
image = toLinearWithAlpha<uint8_t>(c->width, c->height, c->width * 4,
(uint8_t*) buffer);
const std::string png = std::string(c->name) + ".png";
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
png);
#endif
}
// Hash the contents of the buffer and check that they match.
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
ASSERT_EQ(hash, c->expectedHash) << c->name << " failed: hashes do not match." << std::endl;
free(buffer);
free(c->name);
free(c);
}, (void*)c);
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
if (exportScreenshot) {
writeImage(img, width, height, utils::CString(testName));
}
ASSERT_EQ(hash, expectedHash) << testName << " failed: hashes do not match." << std::endl;
}
void BackendTest::writeImage(utils::FixedCapacityVector<uint8_t> img, size_t w, size_t h,
utils::CString const& fname) {
constexpr size_t MAX_FNAME_LEN = 50;
constexpr size_t FNAME_EXT_LEN = 4;
constexpr size_t TOTAL_LEN = MAX_FNAME_LEN + FNAME_EXT_LEN + 1;
char buf[TOTAL_LEN];
ASSERT_PRECONDITION(fname.size() <= MAX_FNAME_LEN, "File name %s is too long", fname.c_str());
snprintf(buf, TOTAL_LEN, "%s.png", fname.c_str());
#ifndef IOS
LinearImage image(w, h, 4);
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, img.data());
std::ofstream pngstrm(buf, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", buf);
#endif
}
utils::FixedCapacityVector<uint8_t> BackendTest::getRenderTarget(
Handle<HwRenderTarget> rt, size_t width, size_t height, PixelDataFormat format,
PixelDataType dataType) {
auto& api = getDriverApi();
size_t const size = width * height * 4;
utils::FixedCapacityVector<uint8_t> result(size);
PixelBufferDescriptor pb(result.data(), size, format, dataType);
api.readPixels(rt, 0, 0, width, height, std::move(pb));
flushAndWait();
return result;
}
uint32_t BackendTest::computeImageHash(utils::FixedCapacityVector<uint8_t> const& img) {
return utils::hash::murmur3((uint32_t*) img.data(), img.size() / 4, 0);
}
utils::FixedCapacityVector<uint8_t> BackendTest::getRenderTargetRGB(
Handle<HwRenderTarget> rt, size_t width, size_t height) {
return getRenderTarget(rt, width, height, PixelDataFormat::RGBA,
PixelDataType::UBYTE);
}
class Environment : public ::testing::Environment {
public:
virtual void SetUp() override {

View File

@@ -67,6 +67,17 @@ protected:
filament::backend::Driver& getDriver() { return *driver; }
private:
void writeImage(utils::FixedCapacityVector<uint8_t> img, size_t w, size_t h,
utils::CString const& fname);
utils::FixedCapacityVector<uint8_t> getRenderTarget(
filament::backend::Handle<filament::backend::HwRenderTarget> rt, size_t width,
size_t height, filament::backend::PixelDataFormat format,
filament::backend::PixelDataType dataType);
uint32_t computeImageHash(utils::FixedCapacityVector<uint8_t> const& img);
utils::FixedCapacityVector<uint8_t> getRenderTargetRGB(
filament::backend::Handle<filament::backend::HwRenderTarget> rt, size_t width,
size_t height);
filament::backend::Driver* driver = nullptr;
filament::backend::CommandBufferQueue commandBufferQueue;

View File

@@ -60,36 +60,6 @@ struct MaterialParams {
float4 scale;
};
struct ScreenshotParams {
int width;
int height;
const char* filename;
uint32_t pixelHashResult;
};
#ifdef IOS
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt, ScreenshotParams* params) {}
#else
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt, ScreenshotParams* params) {
using namespace image;
const size_t size = params->width * params->height * 4;
void* buffer = calloc(1, size);
auto cb = [](void* buffer, size_t size, void* user) {
ScreenshotParams* params = (ScreenshotParams*) user;
int w = params->width, h = params->height;
const uint32_t* texels = (uint32_t*) buffer;
params->pixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
LinearImage image(w, h, 4);
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
std::ofstream pngstrm(params->filename, std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", params->filename);
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb,
(void*) params);
dapi.readPixels(rt, 0, 0, params->width, params->height, std::move(pb));
}
#endif
static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, MaterialParams params) {
MaterialParams* tmp = new MaterialParams(params);
auto cb = [](void* buffer, size_t size, void* user) {
@@ -243,21 +213,12 @@ TEST_F(BackendTest, ColorMagnify) {
api.endFrame(0);
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMagnify.png" };
api.beginFrame(0, 0, 0);
dumpScreenshot(api, dstRenderTargets[0], &params);
api.commit(swapChain);
constexpr uint32_t expected = 0x410bdd31;
readPixelsAndAssertHash("ColorMagnify", kDstTexWidth, kDstTexHeight, dstRenderTargets[0],
expected, true);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
// Check if the image matches perfectly to our golden run.
const uint32_t expected = 0x410bdd31;
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
EXPECT_TRUE(params.pixelHashResult == expected);
flushAndWait();
// Cleanup.
api.destroyTexture(srcTexture);
@@ -315,17 +276,15 @@ TEST_F(BackendTest, ColorMinify) {
SamplerMagFilter::LINEAR);
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "ColorMinify.png" };
dumpScreenshot(api, dstRenderTargets[0], &params);
api.beginFrame(0, 0, 0);
constexpr uint32_t expected = 0xf3d9c53f;
readPixelsAndAssertHash("ColorMinify", kDstTexWidth, kDstTexHeight, dstRenderTargets[0],
expected, true);
// Wait for the ReadPixels result to come back.
api.commit(swapChain);
api.endFrame(0);
flushAndWait();
// Check if the image matches perfectly to our golden run.
const uint32_t expected = 0xf3d9c53f;
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
EXPECT_TRUE(params.pixelHashResult == expected);
// Cleanup.
api.destroyTexture(srcTexture);
api.destroyTexture(dstTexture);
@@ -416,17 +375,13 @@ TEST_F(BackendTest, ColorResolve) {
SamplerMagFilter::NEAREST);
// Grab a screenshot.
ScreenshotParams sparams{ kDstTexWidth, kDstTexHeight, "ColorResolve.png" };
dumpScreenshot(api, dstRenderTarget, &sparams);
// Wait for the ReadPixels result to come back.
api.beginFrame(0, 0, 0);
constexpr uint32_t expected = 0xebfac2ef;
readPixelsAndAssertHash("ColorResolve", kDstTexWidth, kDstTexHeight, dstRenderTarget,
expected, true);
api.endFrame(0);
flushAndWait();
// Check if the image matches perfectly to our golden run.
const uint32_t expected = 0xebfac2ef;
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sparams.pixelHashResult, expected);
EXPECT_TRUE(sparams.pixelHashResult == expected);
// Cleanup.
api.destroyBufferObject(ubuffer);
api.destroyProgram(program);
@@ -489,21 +444,12 @@ TEST_F(BackendTest, Blit2DTextureArray) {
api.endFrame(0);
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "Blit2DTextureArray.png" };
api.beginFrame(0, 0, 0);
dumpScreenshot(api, dstRenderTarget, &params);
api.commit(swapChain);
constexpr uint32_t expected = 0x8de7d55b;
readPixelsAndAssertHash("Blit2DTextureArray", kDstTexWidth, kDstTexHeight, dstRenderTarget,
expected, true);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
// Check if the image matches perfectly to our golden run.
const uint32_t expected = 0x8de7d55b;
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
EXPECT_TRUE(params.pixelHashResult == expected);
flushAndWait();
// Cleanup.
api.destroyTexture(srcTexture);
@@ -578,28 +524,17 @@ TEST_F(BackendTest, BlitRegion) {
api.commit(swapChain);
api.endFrame(0);
// Grab a screenshot.
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegion.png" };
api.beginFrame(0, 0, 0);
dumpScreenshot(api, dstRenderTarget, &params);
api.commit(swapChain);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
// Check if the image matches perfectly to our golden run.
//
// TODO: for some reason, this test has very, very slight (as in one pixel) differences between
// OpenGL and Metal. So disable golden checking for now.
// Use the compare tool from ImageMagick to see visual differences:
// compare -verbose -metric mae BlitRegion_Metal.png BlitRegion_OpenGL.png difference.png
//
// const uint32_t expected = 0x74fa34ed;
// printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", params.pixelHashResult, expected);
// EXPECT_TRUE(params.pixelHashResult == expected);
// api.beginFrame(0, 0, 0);
// constexpr uint32_t expected = 0x74fa34ed;
// readPixelsAndAssertHash("BlitRegion", kDstTexWidth, kDstTexHeight, dstRenderTarget,
// expected, true);
// api.endFrame(0);
// flushAndWait();
// Cleanup.
api.destroyTexture(srcTexture);
@@ -655,23 +590,22 @@ TEST_F(BackendTest, BlitRegionToSwapChain) {
.height = kDstTexHeight - 10,
};
api.beginFrame(0, 0, 0);
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
dstRect, srcRenderTargets[srcLevel],
srcRect, SamplerMagFilter::LINEAR);
ScreenshotParams params { kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain.png" };
dumpScreenshot(api, dstRenderTarget, &params);
// Push through an empty frame to allow the texture to upload and the blit to execute.
api.beginFrame(0, 0, 0);
api.commit(swapChain);
api.endFrame(0);
// Wait for the ReadPixels result to come back.
api.finish();
executeCommands();
getDriver().purge();
// Grab a screenshot.
api.beginFrame(0, 0, 0);
constexpr uint32_t expected = 0xebfac2ef;
readPixelsAndAssertHash("BlitRegionToSwapChain", kDstTexWidth, kDstTexHeight, dstRenderTarget,
expected, true);
api.endFrame(0);
flushAndWait();
// Cleanup.
api.destroyTexture(srcTexture);

View File

@@ -16,6 +16,7 @@
#include "BackendTest.h"
#include "BackendTestUtils.h"
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -64,8 +65,6 @@ void main() {
fragColor = textureLod(test_tex, uv, params.sourceLevel);
})";
static uint32_t sPixelHashResult = 0;
// Selecting a NPOT texture size seems to exacerbate the bug seen with Intel GPU's.
// Note that Filament uses a higher precision format (R11F_G11F_B10F) but this does not seem
// necessary to trigger the bug.
@@ -99,30 +98,13 @@ static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, Material
dapi.updateBufferObject(ubh, std::move(bd), 0);
}
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
const size_t size = kTexWidth * kTexHeight * 4;
void* buffer = calloc(1, size);
auto cb = [](void* buffer, size_t size, void* user) {
int w = kTexWidth, h = kTexHeight;
const uint32_t* texels = (uint32_t*) buffer;
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
#ifndef IOS
LinearImage image(w, h, 4);
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
#endif
free(buffer);
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
}
// TODO: This test needs work to get Metal and OpenGL to agree on results.
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
// backend's readPixels does not work correctly with textures that have image data uploaded.
TEST_F(BackendTest, FeedbackLoops) {
auto& api = getDriverApi();
auto& driver = getDriver();
uint32_t pixelHashResult = 0;
// The test is executed within this block scope to force destructors to run before
// executeCommands().
@@ -147,7 +129,7 @@ TEST_F(BackendTest, FeedbackLoops) {
program = api.createProgram(std::move(prog));
}
TrianglePrimitive const triangle(getDriverApi());
TrianglePrimitive const triangle(api);
// Create a texture.
auto usage = TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE;
@@ -211,8 +193,8 @@ TEST_F(BackendTest, FeedbackLoops) {
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
api.setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(api, ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
@@ -230,8 +212,8 @@ TEST_F(BackendTest, FeedbackLoops) {
const uint32_t sourceLevel = targetLevel + 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
api.setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(api, ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
@@ -241,14 +223,16 @@ TEST_F(BackendTest, FeedbackLoops) {
api.endRenderPass();
}
getDriverApi().setMinMaxLevels(texture, 0, 0x7f);
api.setMinMaxLevels(texture, 0, 0x7f);
// Read back the render target corresponding to the base level.
//
// NOTE: Calling glReadPixels on any miplevel other than the base level
// seems to be un-reliable on some GPU's.
if (frame == kNumFrames - 1) {
dumpScreenshot(api, renderTargets[0]);
constexpr uint32_t expected = 0x70695aa1;
readPixelsAndAssertHash("feedback", kTexWidth, kTexHeight, renderTargets[0],
expected, true);
}
api.flush();
@@ -256,7 +240,7 @@ TEST_F(BackendTest, FeedbackLoops) {
api.endFrame(0);
api.finish();
executeCommands();
getDriver().purge();
driver.purge();
}
api.destroyProgram(program);
@@ -264,10 +248,6 @@ TEST_F(BackendTest, FeedbackLoops) {
api.destroyTexture(texture);
for (auto rt : renderTargets) api.destroyRenderTarget(rt);
}
const uint32_t expected = 0x70695aa1;
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sPixelHashResult, expected);
EXPECT_TRUE(sPixelHashResult == expected);
}
} // namespace test