use FSR upscaling for all quality levels above LOW

Now that FSR scaling is cheap enough on mobile, we use it for quality
levels MEDIUM to ULTRA.

MEDIUM and HIGH use the mobile optimized version, which has a slightly
lower quality, ULTRA uses the original version.

Additionally the RCAS sharpening pass can always be disabled by setting
the sharpen value to 0.
This commit is contained in:
Mathias Agopian
2021-09-03 12:15:38 -07:00
committed by Mathias Agopian
parent 04acd1885a
commit 09a38f4e43
15 changed files with 182 additions and 152 deletions

View File

@@ -6,9 +6,9 @@ A new header is inserted each time a *tag* is created.
## v1.12.4 (currently main branch)
- engine: New night adaptation API on `ColorGrading`. This API can be used to create an effect that
that simulates color and brightness shits in human vision in low-light conditions.
that simulates color and brightness shifts in human vision in low-light conditions.
- engine: improved performance of AMD FidelityFX FSR1 by 4.6x, it now runs in about 2ms in 4K.
- engine: Dynamic resolution quality `MEDIUM`, `HIGH` and `ULTRA` now all use AMD FidelityFX FSR1
## v1.12.3

View File

@@ -131,19 +131,19 @@ public class View {
public float maxScale = 1.0f;
/**
* Sharpness when QualityLevel.ULTRA is used [0, 2].
* 0 is the sharpest setting, 2 is the smoothest setting.
* The default is set to 0.2
* Sharpness when QualityLevel.MEDIUM or higher is used [0, 1].
* 0 is disabled, 1 is the sharpest setting.
* The default is set to 0.9
*/
public float sharpness = 0.2f;
public float sharpness = 0.9f;
/**
* Upscaling quality
* LOW: bilinear filtered blit. Fastest, poor quality
* MEDIUM: 16-tap optimized tent filter.
* HIGH: 36-tap optimized tent filter.
* ULTRA: AMD FidelityFX FSR1. Slowest, very high quality.
* Requires a well anti-aliased (MSAA or TAA), noise free scene.
* MEDIUM: AMD FidelityFX FSR1 w/ mobile optimizations no RCAS sharpening pass
* HIGH: AMD FidelityFX FSR1 w/ mobile optimizations + RCAS
* ULTRA: AMD FidelityFX FSR1
* FSR1 require a well anti-aliased (MSAA or TAA), noise free scene.
*
* The default upscaling quality is set to LOW.
*/

View File

@@ -98,10 +98,7 @@ class MainActivity : Activity() {
val view = modelViewer.view
view.dynamicResolutionOptions = view.dynamicResolutionOptions.apply {
enabled = true
quality = View.QualityLevel.ULTRA
homogeneousScaling = true
minScale = 0.5f
maxScale = 0.5f
quality = View.QualityLevel.MEDIUM
}
view.ambientOcclusionOptions = view.ambientOcclusionOptions.apply {

View File

@@ -171,6 +171,7 @@ set(PRIVATE_HDRS
set(MATERIAL_SRCS
src/materials/fsr/fsr_easu.mat
src/materials/fsr/fsr_easu_mobile.mat
src/materials/fsr/fsr_rcas.mat
src/materials/colorGrading/colorGrading.mat
src/materials/colorGrading/colorGradingAsSubpass.mat
@@ -186,8 +187,6 @@ set(MATERIAL_SRCS
src/materials/dof/dofMedian.mat
src/materials/flare/flare.mat
src/materials/blitLow.mat
src/materials/blitMedium.mat
src/materials/blitHigh.mat
src/materials/bloom/bloomDownsample.mat
src/materials/bloom/bloomUpsample.mat
src/materials/ssao/bilateralBlur.mat
@@ -363,6 +362,13 @@ add_custom_command(
OUTPUT "${MATERIAL_DIR}/fsr_easu.filamat"
DEPENDS src/materials/fsr/ffx_a.h
DEPENDS src/materials/fsr/ffx_fsr1.h
APPEND
)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/fsr_easu_mobile.filamat"
DEPENDS src/materials/fsr/ffx_a.h
DEPENDS src/materials/fsr/ffx_fsr1.h
DEPENDS src/materials/fsr/ffx_fsr1_mobile.fs
APPEND
)

View File

@@ -71,17 +71,17 @@ enum class BlendMode : uint8_t {
struct DynamicResolutionOptions {
math::float2 minScale = math::float2(0.5f); //!< minimum scale factors in x and y
math::float2 maxScale = math::float2(1.0f); //!< maximum scale factors in x and y
float sharpness = 0.2f; //!< sharpness when QualityLevel::ULTRA is used [0 (sharpest), 2 (smoothest)]
float sharpness = 0.9f; //!< sharpness when QualityLevel::MEDIUM or higher is used [0 (disabled), 1 (sharpest)]
bool enabled = false; //!< enable or disable dynamic resolution
bool homogeneousScaling = false; //!< set to true to force homogeneous scaling
/**
* Upscaling quality
* LOW: bilinear filtered blit. Fastest, poor quality
* MEDIUM: 16-tap optimized tent filter.
* HIGH: 36-tap optimized tent filter.
* ULTRA: AMD FidelityFX FSR1. Slowest, very high quality.
* Requires a well anti-aliased (MSAA or TAA), noise free scene.
* LOW: bilinear filtered blit. Fastest, poor quality
* MEDIUM: AMD FidelityFX FSR1 w/ mobile optimizations
* HIGH: AMD FidelityFX FSR1 w/ mobile optimizations
* ULTRA: AMD FidelityFX FSR1
* FSR1 require a well anti-aliased (MSAA or TAA), noise free scene.
*
* The default upscaling quality is set to LOW.
*/

View File

@@ -100,11 +100,11 @@ void FrameInfoManager::update(Config const& config, FrameInfoManager::duration l
const duration targetWithHeadroom = config.targetFrameTime * (1.0f - config.headRoomRatio);
const duration measured = denoisedFrameTime;
// We use a P.I.D. controller below to figure out the scaling factor to apply. In practice we
// We use a P.I.D. controller below to figure out the scaling factor to apply. In practice, we
// don't use the Derivative gain (so it's really a PI controller).
const float Kp = (1.0f - std::exp(-config.oneOverTau));
const float Ki = Kp / 10.0f;
const float Kd = 0.0;
const float Kd = 0.0f;
history[0].pid.error = (targetWithHeadroom - measured) / targetWithHeadroom;
history[0].pid.integral = history[1].pid.integral + Ki * history[0].pid.error;
@@ -113,7 +113,7 @@ void FrameInfoManager::update(Config const& config, FrameInfoManager::duration l
const float derivative = Kd * (history[0].pid.error - history[1].pid.error);
const float out = Kp * history[0].pid.error + history[0].pid.integral + derivative;
// maps the command to a ratio, it really doesn't mater much how the conversion is done
// maps the command to a ratio, it really doesn't matter much how the conversion is done
// the system will find the right value automatically
const float scale = std::exp2(out);
history[0].scale = scale;

View File

@@ -203,9 +203,7 @@ struct MaterialInfo {
static const MaterialInfo sMaterialList[] = {
{ "bilateralBlur", MATERIAL(BILATERALBLUR) },
{ "bilateralBlurBentNormals", MATERIAL(BILATERALBLURBENTNORMALS) },
{ "blitHigh", MATERIAL(BLITHIGH) },
{ "blitLow", MATERIAL(BLITLOW) },
{ "blitMedium", MATERIAL(BLITMEDIUM) },
{ "bloomDownsample", MATERIAL(BLOOMDOWNSAMPLE) },
{ "bloomUpsample", MATERIAL(BLOOMUPSAMPLE) },
{ "colorGrading", MATERIAL(COLORGRADING) },
@@ -228,6 +226,7 @@ static const MaterialInfo sMaterialList[] = {
{ "taa", MATERIAL(TAA) },
{ "vsmMipmap", MATERIAL(VSMMIPMAP) },
{ "fsr_easu", MATERIAL(FSR_EASU) },
{ "fsr_easu_mobile", MATERIAL(FSR_EASU_MOBILE) },
{ "fsr_rcas", MATERIAL(FSR_RCAS) },
};
@@ -267,7 +266,7 @@ void PostProcessManager::init() noexcept {
*static_cast<uint32_t *>(dataZero.buffer) = 0;
std::generate_n((uint8_t*)dataStarburst.buffer, 256,
[&dist = mUniformDistribution, &gen = mEngine.getRandomEngine()]() {
float r = 0.5 + 0.5 * dist(gen);
float r = 0.5f + 0.5f * dist(gen);
return uint8_t(r * 255.0f);
});
driver.update2DImage(mDummyOneTexture, 0, 0, 0, 1, 1, std::move(dataOne));
@@ -2276,9 +2275,11 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
Handle<HwRenderPrimitive> fullScreenRenderPrimitive = mEngine.getFullScreenRenderPrimitive();
if (translucent && dsrOptions.quality == QualityLevel::ULTRA) {
bool lowQualityFallback = false;
if (translucent && dsrOptions.quality != QualityLevel::LOW) {
// FidelityFX-FSR doesn't support the alpha channel currently
dsrOptions.quality = QualityLevel::MEDIUM;
dsrOptions.quality = QualityLevel::LOW;
lowQualityFallback = true;
}
struct QuadBlitData {
@@ -2300,12 +2301,12 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
auto const& inputDesc = resources.getDescriptor(data.input);
auto const& outputDesc = resources.getDescriptor(data.output);
const StaticString blitterNames[4] = { "blitLow", "blitMedium", "blitHigh", "fsr_easu" };
const StaticString blitterNames[4] = { "blitLow", "fsr_easu_mobile", "fsr_easu_mobile", "fsr_easu" };
unsigned index = std::min(3u, (unsigned)dsrOptions.quality);
auto& material = getPostProcessMaterial(blitterNames[index]);
FMaterialInstance* const mi = material.getMaterialInstance();
if (dsrOptions.quality == QualityLevel::ULTRA) {
if (dsrOptions.quality != QualityLevel::LOW) {
FSRUniforms uniforms;
FSR_ScalingSetup(&uniforms, {
.viewportWidth = inputDesc.width,
@@ -2335,7 +2336,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
PipelineState pipeline(material.getPipelineState());
if (translucent) {
assert_invariant(dsrOptions.quality != QualityLevel::ULTRA);
assert_invariant(dsrOptions.quality == QualityLevel::LOW);
pipeline.rasterState.blendFunctionSrcRGB = BlendFunction::ONE;
pipeline.rasterState.blendFunctionSrcAlpha = BlendFunction::ONE;
pipeline.rasterState.blendFunctionDstRGB = BlendFunction::ONE_MINUS_SRC_ALPHA;
@@ -2348,8 +2349,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
auto output = ppQuadBlit->output;
if (dsrOptions.quality == QualityLevel::ULTRA) {
// if we had to take the low quality fallback, we still do the "sharpen pass"
if (dsrOptions.sharpness > 0.0f && (dsrOptions.quality != QualityLevel::LOW || lowQualityFallback)) {
auto& ppFsrRcas = fg.addPass<QuadBlitData>("FidelityFX FSR1 Rcas",
[&](FrameGraph::Builder& builder, auto& data) {
data.input = builder.sample(output);
@@ -2367,7 +2368,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
FMaterialInstance* const mi = material.getMaterialInstance();
FSRUniforms uniforms;
FSR_SharpeningSetup(&uniforms, { .sharpness = dsrOptions.sharpness });
FSR_SharpeningSetup(&uniforms, { .sharpness = 2.0f - 2.0f * dsrOptions.sharpness });
mi->setParameter("RcasCon", uniforms.RcasCon);
mi->setParameter("color", color, { }); // uses texelFetch
mi->setParameter("resolution", float4{
@@ -2376,8 +2377,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
mi->commit(driver);
mi->use(driver);
PipelineState pipeline(material.getPipelineState());
assert_invariant(!translucent);
const uint8_t variant = uint8_t(translucent ?
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
PipelineState pipeline(material.getPipelineState(variant));
driver.beginRenderPass(out.target, out.params);
driver.draw(pipeline, fullScreenRenderPrimitive);
driver.endRenderPass();

View File

@@ -128,7 +128,8 @@ float2 FView::updateScale(FrameInfo const& info) noexcept {
DynamicResolutionOptions const& options = mDynamicResolution;
if (options.enabled) {
if (!UTILS_UNLIKELY(info.valid)) {
mScale = 1.0f;
// always clamp to the min/max scale range
mScale = clamp(1.0f, options.minScale, options.maxScale);
return mScale;
}

View File

@@ -1,47 +0,0 @@
material {
name : blitHigh,
parameters : [
{
type : sampler2d,
name : color,
precision: medium
},
{
type : float4,
name : resolution,
precision: high
}
],
variables : [
vertex
],
depthWrite : false,
depthCulling : false,
domain: postprocess
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
}
}
fragment {
void dummy() {}
void postProcess(inout PostProcessInputs postProcess) {
highp vec2 uv = variable_vertex.xy; // interpolated to pixel center
vec4 c0, c1;
c0 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(-1, -1));
c0 += textureLodOffset(materialParams_color, uv, 0.0, ivec2( 1, -1));
c0 += textureLodOffset(materialParams_color, uv, 0.0, ivec2( 1, 1));
c0 += textureLodOffset(materialParams_color, uv, 0.0, ivec2(-1, 1));
c0 += 4.0 * textureLod(materialParams_color, uv, 0.0);
c1 = textureLodOffset(materialParams_color, uv, 0.0, ivec2(-1, 0));
c1 += textureLodOffset(materialParams_color, uv, 0.0, ivec2( 0, -1));
c1 += textureLodOffset(materialParams_color, uv, 0.0, ivec2( 1, 0));
c1 += textureLodOffset(materialParams_color, uv, 0.0, ivec2( 0, 1));
postProcess.color = (c0 + 2.0 * c1) * (1.0 / 16.0);
}
}

View File

@@ -1,42 +0,0 @@
material {
name : blitMedium,
parameters : [
{
type : sampler2d,
name : color,
precision: medium
},
{
type : float4,
name : resolution,
precision: high
}
],
variables : [
vertex
],
depthWrite : false,
depthCulling : false,
domain: postprocess
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV;
}
}
fragment {
void postProcess(inout PostProcessInputs postProcess) {
highp vec2 uv = variable_vertex.xy; // interpolated to pixel center
highp float du = 0.5 * materialParams.resolution.z;
highp float dv = 0.5 * materialParams.resolution.w;
vec4 c;
c = textureLod(materialParams_color, uv + vec2(-du, -dv), 0.0);
c += textureLod(materialParams_color, uv + vec2( du, -dv), 0.0);
c += textureLod(materialParams_color, uv + vec2( du, dv), 0.0);
c += textureLod(materialParams_color, uv + vec2(-du, dv), 0.0);
postProcess.color = c * 0.25;
}
}

View File

@@ -314,15 +314,15 @@ A_STATIC void FsrEasuConOffset(
//------------------------------------------------------------------------------------------------------------------------------
void FsrEasuF(
out AF3 pix,
AF2 ip, // Integer pixel position in output.
AF4 con0, // Constants generated by FsrEasuCon().
AF4 con1,
AF4 con2,
AF4 con3){
highp AF2 ip, // Integer pixel position in output.
highp AF4 con0, // Constants generated by FsrEasuCon().
highp AF4 con1,
highp AF4 con2,
highp AF4 con3){
//------------------------------------------------------------------------------------------------------------------------------
// Get position of 'f'.
AF2 pp=ip*con0.xy+con0.zw;
AF2 fp=floor(pp);
highp AF2 pp=ip*con0.xy+con0.zw;
highp AF2 fp=floor(pp);
pp-=fp;
//------------------------------------------------------------------------------------------------------------------------------
// 12-tap kernel.
@@ -341,11 +341,11 @@ A_STATIC void FsrEasuConOffset(
// a b
// r g <- unused (z)
// Allowing dead-code removal to remove the 'z's.
AF2 p0=fp*con1.xy+con1.zw;
highp AF2 p0=fp*con1.xy+con1.zw;
// These are from p0 to avoid pulling two constants on pre-Navi hardware.
AF2 p1=p0+con2.xy;
AF2 p2=p0+con2.zw;
AF2 p3=p0+con3.xy;
highp AF2 p1=p0+con2.xy;
highp AF2 p2=p0+con2.zw;
highp AF2 p3=p0+con3.xy;
AF4 bczzR=FsrEasuRF(p0);
AF4 bczzG=FsrEasuGF(p0);
AF4 bczzB=FsrEasuBF(p0);

View File

@@ -63,9 +63,6 @@ fragment {
#define FSR_EASU_F 1
#include "ffx_fsr1.h"
#define FILAMENT_FSR_DERINGING
#include "ffx_fsr1_mobile.fs"
#if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER)
#define gather textureGather
#else
@@ -92,18 +89,12 @@ fragment {
return gather(materialParams_color, p, 2);
}
AF3 FsrEasuSampleF(highp AF2 p) {
return textureLod(materialParams_color, p, 0.0).rgb;
}
void postProcess(inout PostProcessInputs postProcess) {
highp vec2 p = floor(variable_vertex.xy);
AF3 pix;
// just use FsrEasuF() for the original full quality EASU
// note that FsrEasuF() also technically needs "precision highp float" currently
FsrEasuL(pix, p,
FsrEasuF(pix, p,
materialParams.EasuCon0,
materialParams.EasuCon1,
materialParams.EasuCon2,

View File

@@ -0,0 +1,113 @@
material {
name : fsr_easu_mobile,
parameters : [
{
type : sampler2d,
name : color,
precision: medium
},
{
type : float2,
name : textureSize,
precision: high
},
{
type : float4,
name : resolution,
precision: high
},
{
type : float4,
name : EasuCon0,
precision: high
},
{
type : float4,
name : EasuCon1,
precision: high
},
{
type : float4,
name : EasuCon2,
precision: high
},
{
type : float4,
name : EasuCon3,
precision: high
}
],
variables : [
vertex
],
depthWrite : false,
depthCulling : false,
domain: postprocess
}
vertex {
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
postProcess.vertex.xy = postProcess.normalizedUV * materialParams.resolution.xy;
}
}
fragment {
precision mediump float;
precision highp int;
#define A_GPU 1
#define A_GLSL 1
#include "ffx_a.h"
#define FSR_EASU_F 1
#include "ffx_fsr1.h"
#define FILAMENT_FSR_DERINGING
#include "ffx_fsr1_mobile.fs"
#if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER)
#define gather textureGather
#else
vec4 gather(const mediump sampler2D color, highp vec2 p, const int comp) {
highp ivec2 i = ivec2(p * materialParams.textureSize - 0.5);
vec4 d;
d[0] = texelFetchOffset(color, i, 0, ivec2(0, 1))[comp];
d[1] = texelFetchOffset(color, i, 0, ivec2(1, 1))[comp];
d[2] = texelFetchOffset(color, i, 0, ivec2(1, 0))[comp];
d[3] = texelFetchOffset(color, i, 0, ivec2(0, 0))[comp];
return d;
}
#endif
AF4 FsrEasuRF(highp AF2 p) {
return gather(materialParams_color, p, 0);
}
AF4 FsrEasuGF(highp AF2 p) {
return gather(materialParams_color, p, 1);
}
AF4 FsrEasuBF(highp AF2 p) {
return gather(materialParams_color, p, 2);
}
AF3 FsrEasuSampleF(highp AF2 p) {
return textureLod(materialParams_color, p, 0.0).rgb;
}
void postProcess(inout PostProcessInputs postProcess) {
highp vec2 p = floor(variable_vertex.xy);
AF3 pix;
FsrEasuL(pix, p,
materialParams.EasuCon0,
materialParams.EasuCon1,
materialParams.EasuCon2,
materialParams.EasuCon3);
postProcess.color.rgb = pix;
postProcess.color.a = 1.0; // TODO: FSR doesn't support transparency.
}
}

View File

@@ -40,6 +40,9 @@ fragment {
#define A_GLSL 1
#include "ffx_a.h"
#define FSR_RCAS_F 1
#if !POST_PROCESS_OPAQUE
# define FSR_RCAS_PASSTHROUGH_ALPHA
#endif
#include "ffx_fsr1.h"
AF4 FsrRcasLoadF(ASU2 p) {
@@ -52,8 +55,13 @@ fragment {
highp uvec2 p = uvec2(variable_vertex.xy);
FsrRcasF(postProcess.color.r, postProcess.color.g, postProcess.color.b,
#if !POST_PROCESS_OPAQUE
postProcess.color.a,
#endif
p, materialParams.RcasCon);
postProcess.color.a = 1.0; // TODO: FSR doesn't support transparency.
#if POST_PROCESS_OPAQUE
postProcess.color.a = 1.0;
#endif
}
}

View File

@@ -346,7 +346,7 @@ static void createGroundPlane(Engine* engine, Scene* scene, App& app) {
static float sGlobalScale = 1.0f;
static float sGlobalScaleAnamorphism = 0.0f;
static int sGlobalScaleQuality = 0;
static float sGlobalScaleSharpness = 0.2f;
static float sGlobalScaleSharpness = 0.9f;
int main(int argc, char** argv) {
App app;
@@ -578,7 +578,7 @@ int main(int argc, char** argv) {
ImGui::SliderFloat("scale", &sGlobalScale, 0.25f, 1.0f);
ImGui::SliderFloat("anamorphism", &sGlobalScaleAnamorphism, -1.0f, 1.0f);
ImGui::SliderInt("quality", &sGlobalScaleQuality, 0, 3);
ImGui::SliderFloat("sharpness", &sGlobalScaleSharpness, 0.0f, 2.0f);
ImGui::SliderFloat("sharpness", &sGlobalScaleSharpness, 0.0f, 1.0f);
}