Compare commits

...

1 Commits

Author SHA1 Message Date
Powei Feng
b7244464a3 test: fix VK backend FeedbackLoops test 2024-02-22 11:34:03 -08:00
3 changed files with 182 additions and 39 deletions

View File

@@ -18,11 +18,16 @@
#include <private/backend/PlatformFactory.h>
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
#include <backend/platforms/VulkanPlatform.h>
#endif
#include "BackendTest.h"
#include <utils/Hash.h>
#include <fstream>
#include <string>
#include "ShaderGenerator.h"
#include "TrianglePrimitive.h"
@@ -33,6 +38,25 @@ static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND
using namespace filament;
using namespace filament::backend;
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
namespace filament {
class InternalVulkanPlatform : public VulkanPlatform {
public:
InternalVulkanPlatform() {
mCustomization = {
.gpu = { .index = 0 },
};
}
virtual VulkanPlatform::Customization getCustomization() const noexcept override {
return mCustomization;
}
private:
VulkanPlatform::Customization mCustomization;
};
}
#endif
#ifndef IOS
#include <imageio/ImageEncoder.h>
#include <image/ColorTransform.h>
@@ -40,6 +64,11 @@ using namespace filament::backend;
using namespace image;
#endif
namespace {
std::string const SHADER_VULKAN_TAG = "// select(vulkan) ";
std::string const SHADER_METAL_TAG = "// select(metal) ";
}
namespace test {
Backend BackendTest::sBackend = Backend::NOOP;
@@ -67,7 +96,14 @@ BackendTest::~BackendTest() {
void BackendTest::initializeDriver() {
auto backend = static_cast<filament::backend::Backend>(sBackend);
Platform* platform = PlatformFactory::create(&backend);
Platform* platform = nullptr;
if (backend == filament::backend::Backend::VULKAN) {
platform = new InternalVulkanPlatform();
} else {
platform = PlatformFactory::create(&backend);
}
assert_invariant(static_cast<uint8_t>(backend) == static_cast<uint8_t>(sBackend));
Platform::DriverConfig const driverConfig;
driver = platform->createDriver(nullptr, driverConfig);
@@ -193,6 +229,32 @@ void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, si
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
}
std::string BackendTest::transformShaderText(std::string const& shader) const {
std::string replacement;
switch (sBackend) {
case test::Backend::VULKAN:
replacement = SHADER_VULKAN_TAG;
break;
case test::Backend::METAL:
replacement = SHADER_METAL_TAG;
break;
default:
break;
}
if (replacement.size() == 0) {
return shader;
}
std::string replaced = shader;
while (true) {
size_t pos = replaced.find(replacement);
if (pos == std::string::npos) {
break;
}
replaced.replace(pos, replacement.length(), "");
}
return replaced;
}
class Environment : public ::testing::Environment {
public:
virtual void SetUp() override {

View File

@@ -66,6 +66,8 @@ protected:
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
filament::backend::Driver& getDriver() { return *driver; }
std::string transformShaderText(std::string const& shader) const;
private:
filament::backend::Driver* driver = nullptr;

View File

@@ -33,6 +33,8 @@
using namespace image;
#endif
#define DUMP_INTERMEDIATE_SCREENSHOTS 1
////////////////////////////////////////////////////////////////////////////////////////////////////
// Shaders
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -42,6 +44,7 @@ layout(location = 0) in vec4 mesh_position;
void main() {
// Hack: move and scale triangle so that it covers entire viewport.
gl_Position = vec4((mesh_position.xy + 0.5) * 5.0, 0.0, 1.0);
// select(vulkan) gl_Position.y = - gl_Position.y;
})";
static std::string fullscreenFs = R"(#version 450 core
@@ -50,7 +53,7 @@ layout(location = 0) out vec4 fragColor;
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
// This parameter is ignored for other backends.
layout(location = 0, set = 1) uniform sampler2D test_tex;
layout(binding = 0, set = 1) uniform sampler2D test_tex;
uniform Params {
highp float fbWidth;
@@ -60,7 +63,18 @@ uniform Params {
} params;
void main() {
vec2 fbsize = vec2(params.fbWidth, params.fbHeight);
vec2 uv = (gl_FragCoord.xy + 0.5) / fbsize;
float offset = 0;
// select(vulkan) offset = -offset;
vec2 uv = (gl_FragCoord.xy + offset) / fbsize;
// select(vulkan) uv.y = 1.0 - uv.y;
vec4 oo = vec4(0, 0, 0, 0);
if (uv.x > .5) {
oo += vec4(0.5, 0.0, 0.0, 1.0);
}
if (uv.y > .5) {
oo += vec4(0.0, 0.5, 0.0, 1.0);
}
// fragColor = oo;
fragColor = textureLod(test_tex, uv, params.sourceLevel);
})";
@@ -99,23 +113,58 @@ 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;
static void readScreenshot(DriverApi& dapi, Handle<HwRenderTarget> renderTargets[kNumLevels],
uint32_t target, std::function<void(void*)> output) {
Handle<HwRenderTarget> rt = renderTargets[target];
size_t const readWidth = kTexWidth >> target;
size_t const readHeight = kTexHeight >> target;
size_t const size = readWidth * readHeight * 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);
struct ReadPixelUserData {
std::function<void(void*)> onRead;
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
ReadPixelUserData* userdata = new ReadPixelUserData { output };
auto cb = [](void* buffer, size_t size, void* user) {
ReadPixelUserData* data = (ReadPixelUserData*) user;
data->onRead(buffer);
free(buffer);
delete data;
};
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb,
userdata);
dapi.readPixels(rt, 0, 0, readWidth, readHeight, std::move(pb));
}
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> renderTargets[kNumLevels],
uint32_t target, uint32_t numFrame, bool downscale, test::Backend backend,
std::string fname="") {
Handle<HwRenderTarget> rt = renderTargets[target];
size_t const readWidth = kTexWidth >> target;
size_t const readHeight = kTexHeight >> target;
size_t const size = readWidth * readHeight * 4;
if (fname.empty()) {
std::string backendStr = "gl";
if (backend == test::Backend::METAL) {
backendStr = "mtl";
} else if (backend == test::Backend::VULKAN) {
backendStr = "vk";
}
std::string const down = downscale ? "down" : "up";
fname = "feedback_" + backendStr + "_" + std::to_string(numFrame) + "_" + down + "_" +
std::to_string(target) + ".png";
}
utils::slog.e <<"writing: " << fname << utils::io::endl;
readScreenshot(dapi, renderTargets, target,
[w = readWidth, h = readHeight, size, fname](void* buffer) {
uint32_t const* 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(fname.c_str(), std::ios::binary | std::ios::trunc);
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", fname.c_str());
#endif
});
}
// TODO: This test needs work to get Metal and OpenGL to agree on results.
@@ -131,6 +180,8 @@ TEST_F(BackendTest, FeedbackLoops) {
auto swapChain = createSwapChain();
api.makeCurrent(swapChain, swapChain);
bool const backendIsVulkan = sBackend == test::Backend::VULKAN;
// Create a program.
ProgramHandle program;
{
@@ -139,10 +190,12 @@ TEST_F(BackendTest, FeedbackLoops) {
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
.build();
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, &sib);
ShaderGenerator shaderGen(transformShaderText(fullscreenVs),
transformShaderText(fullscreenFs), sBackend, sIsMobilePlatform, &sib);
Program prog = shaderGen.getProgram(api);
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
Program::Sampler psamplers[] = {utils::CString("test_tex"), 0};
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers,
sizeof(psamplers) / sizeof(psamplers[0]));
prog.uniformBlockBindings({{"params", 1}});
program = api.createProgram(std::move(prog));
}
@@ -167,10 +220,25 @@ TEST_F(BackendTest, FeedbackLoops) {
const size_t size = kTexHeight * kTexWidth * 4;
uint8_t* buffer = (uint8_t*) malloc(size);
for (int r = 0, i = 0; r < kTexHeight; r++) {
int const adjustedR = backendIsVulkan ? r : kTexHeight - r - 1;
for (int c = 0; c < kTexWidth; c++, i += 4) {
auto hval = 0x1f * c / (kTexWidth - 1);
if (hval > 0x05) {
hval = 0x1f;
} else {
hval = 0x00;
}
auto vval = 0x1f * adjustedR / (kTexHeight - 1);
if (vval > 0x05) {
vval = 0x1f;
} else {
vval = 0x00;
}
buffer[i + 0] = 0x10;
buffer[i + 1] = 0x1f * r / (kTexHeight - 1);
buffer[i + 1] = 0x1f * adjustedR / (kTexHeight - 1);
// buffer[i + 1] = vval;
buffer[i + 2] = 0x1f * c / (kTexWidth - 1);
// buffer[i + 2] = hval;
buffer[i + 3] = 0xf0;
}
}
@@ -179,7 +247,6 @@ TEST_F(BackendTest, FeedbackLoops) {
api.update3DImage(texture, 0, 0, 0, 0, kTexWidth, kTexHeight, 1, std::move(pb));
for (int frame = 0; frame < kNumFrames; frame++) {
// Prep for rendering.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::NONE;
@@ -191,8 +258,9 @@ TEST_F(BackendTest, FeedbackLoops) {
state.program = program;
SamplerGroup samplers(1);
SamplerParams sparams = {};
sparams.filterMag = SamplerMagFilter::LINEAR;
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
sparams.filterMag = SamplerMagFilter::NEAREST;
// sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
sparams.filterMin = SamplerMinFilter::NEAREST;
samplers.setSampler(0, { texture, sparams });
auto sgroup =
api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
@@ -207,19 +275,24 @@ TEST_F(BackendTest, FeedbackLoops) {
// Downsample passes
params.flags.discardStart = TargetBufferFlags::ALL;
state.rasterState.disableBlending();
for (int targetLevel = 1; targetLevel < kNumLevels; targetLevel++) {
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
for (int targetLevel = 0; targetLevel < kNumLevels; targetLevel++) {
if (targetLevel > 0) {
const uint32_t sourceLevel = targetLevel - 1;
params.viewport.width = kTexWidth >> targetLevel;
params.viewport.height = kTexHeight >> targetLevel;
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
uploadUniforms(getDriverApi(), ubuffer, {
.fbWidth = float(params.viewport.width),
.fbHeight = float(params.viewport.height),
.sourceLevel = float(sourceLevel),
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
}
#if DUMP_INTERMEDIATE_SCREENSHOTS
dumpScreenshot(api, renderTargets, targetLevel, frame, true, sBackend);
#endif
}
// Upsample passes
@@ -239,16 +312,22 @@ TEST_F(BackendTest, FeedbackLoops) {
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 1);
api.endRenderPass();
#if DUMP_INTERMEDIATE_SCREENSHOTS
dumpScreenshot(api, renderTargets, targetLevel, frame, false, sBackend);
#endif
}
getDriverApi().setMinMaxLevels(texture, 0, 0x7f);
getDriverApi().destroySamplerGroup(sgroup);
getDriverApi().destroyBufferObject(ubuffer);
// 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]);
dumpScreenshot(api, renderTargets, 0, frame, false, sBackend, "Feedback.png");
}
api.flush();