|
|
|
@@ -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();
|
|
|
|
|