// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See fsr1.h. #include "fsr1.h" #include #include #include // AMD's headers (thirdparty/fsr1, MIT), as text for the shaders: k_ffx_a and // k_ffx_fsr1 (runtime/CMakeLists.txt). #include // The same headers' CPU code, for the constants (it defines short type // names and many helpers this file doesn't use). #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-parameter" #define A_CPU 1 #include #include #pragma GCC diagnostic pop namespace video::fsr1 { namespace { std::string Text(const unsigned char* bytes) { return std::string(reinterpret_cast(bytes)); } // Both shaders: AMD's 32-bit GLSL path (no extensions needed). constexpr const char* kPrelude = R"(#version 460 #define A_GPU 1 #define A_GLSL 1 )"; // EASU, one 8x8 group per 64 pixels of the picture. Its 12 taps come // from 4 gathers (FsrEasuRF..BF at the corners between texels); // under Hor+ and Vert+ (bandOn) each of a gather's 2x2 texels is // fetched instead and kept in this pixel's span of the band's three // along each axis (band_filter.h's SpanOf and ClampTexel), so a strip // pixel reads only the strip and a band pixel only the band. // band: the band's columns x..y and rows z..w in texels (video::BandOf). constexpr const char* kEasuHead = R"( layout(local_size_x = 8, local_size_y = 8) in; layout(set = 0, binding = 0) uniform sampler2D src; layout(set = 0, binding = 1, rgba8) uniform writeonly image2D dst; layout(push_constant) uniform P { uvec4 con0, con1, con2, con3; ivec4 band; ivec2 outSize; ivec2 srcSize; uint bandOn; } p; // This pixel's span: its first and last texels along each axis. ivec2 spanLo = ivec2(0), spanHi = ivec2(0); // band_filter.h's SpanOf: [0, lo), [lo, hi) or [hi, size) holding t, as its first and end texels. ivec2 spanOf(float t, int lo, int hi, int size) { return t < float(lo) ? ivec2(0, lo) : t < float(hi) ? ivec2(lo, hi) : ivec2(hi, size); } vec3 texelIn(ivec2 t) { return texelFetch(src, clamp(t, spanLo, spanHi), 0).rgb; } // What a gather at q reads, each texel kept in the span, in a gather's // order: (x0, y1), (x1, y1), (x1, y0), (x0, y0). vec4 quadIn(vec2 q, int c) { ivec2 t = ivec2(floor(q * vec2(p.srcSize) - 0.5)); return vec4(texelIn(t + ivec2(0, 1))[c], texelIn(t + ivec2(1, 1))[c], texelIn(t + ivec2(1, 0))[c], texelIn(t)[c]); } AF4 FsrEasuRF(AF2 q) { return p.bandOn == 0u ? textureGather(src, q, 0) : quadIn(q, 0); } AF4 FsrEasuGF(AF2 q) { return p.bandOn == 0u ? textureGather(src, q, 1) : quadIn(q, 1); } AF4 FsrEasuBF(AF2 q) { return p.bandOn == 0u ? textureGather(src, q, 2) : quadIn(q, 2); } #define FSR_EASU_F 1 )"; constexpr const char* kEasuMain = R"( void main() { uvec2 ip = gl_GlobalInvocationID.xy; if (ip.x >= uint(p.outSize.x) || ip.y >= uint(p.outSize.y)) return; if (p.bandOn != 0u) { // Where this pixel's centre falls in the frame image (texels). vec2 pos = (vec2(ip) + 0.5) * vec2(p.srcSize) / vec2(p.outSize); ivec2 sx = spanOf(pos.x, p.band.x, p.band.y, p.srcSize.x), sy = spanOf(pos.y, p.band.z, p.band.w, p.srcSize.y); spanLo = ivec2(sx.x, sy.x); spanHi = ivec2(sx.y, sy.y) - 1; } AF3 c; FsrEasuF(c, ip, p.con0, p.con1, p.con2, p.con3); imageStore(dst, ivec2(ip), vec4(c, 1.0)); } )"; // RCAS, drawn over the picture's place in the swapchain image (the // viewport at origin, outSize): src is EASU's image. Under Hor+ and // Vert+ a neighbour on the other side of the band's edge (its centre // maps into another span than this pixel's, by EASU's rule) reads // this pixel instead: the span's edge, as ClampTexel keeps a tap. // con.x 0 (Sharpening 0): EASU's picture as it is (RCAS's own // arithmetic would still scale it by its approximate reciprocal). constexpr const char* kRcasHead = R"( layout(location = 0) in vec2 uv; layout(location = 0) out vec4 color; layout(set = 0, binding = 0) uniform sampler2D src; layout(push_constant) uniform P { uvec4 con; ivec4 band; ivec2 origin; ivec2 outSize; ivec2 srcSize; uint bandOn; } p; ivec2 centre = ivec2(0), centreSpan = ivec2(0); // Which of the band's three spans along an axis holds output pixel i's centre: 0, 1 or 2. int spanIndex(int i, int lo, int hi, int outSize, int srcSize) { float t = (float(i) + 0.5) * float(srcSize) / float(outSize); return t < float(lo) ? 0 : t < float(hi) ? 1 : 2; } AF4 FsrRcasLoadF(ASU2 q) { ivec2 t = clamp(q, ivec2(0), p.outSize - 1); if (p.bandOn != 0u) { if (spanIndex(t.x, p.band.x, p.band.y, p.outSize.x, p.srcSize.x) != centreSpan.x) t.x = centre.x; if (spanIndex(t.y, p.band.z, p.band.w, p.outSize.y, p.srcSize.y) != centreSpan.y) t.y = centre.y; } return texelFetch(src, t, 0); } void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} #define FSR_RCAS_F 1 )"; constexpr const char* kRcasMain = R"( void main() { centre = clamp(ivec2(gl_FragCoord.xy) - p.origin, ivec2(0), p.outSize - 1); if (p.bandOn != 0u) centreSpan = ivec2(spanIndex(centre.x, p.band.x, p.band.y, p.outSize.x, p.srcSize.x), spanIndex(centre.y, p.band.z, p.band.w, p.outSize.y, p.srcSize.y)); if (p.con.x == 0u) { color = vec4(texelFetch(src, centre, 0).rgb, 1.0); return; } AF1 r, g, b; FsrRcasF(r, g, b, uvec2(centre), p.con); color = vec4(r, g, b, 1.0); } )"; } std::array EasuConstants(uint32_t srcW, uint32_t srcH, uint32_t dstW, uint32_t dstH) { std::array con{}; FsrEasuCon(&con[0], &con[4], &con[8], &con[12], AF1(srcW), AF1(srcH), AF1(srcW), AF1(srcH), AF1(dstW), AF1(dstH)); return con; } std::array RcasConstants(float stops) { std::array con{}; if (!std::isfinite(stops)) return con; // off: con.x 0 (the shader then copies EASU's picture) FsrRcasCon(con.data(), AF1(stops)); return con; } std::string EasuGlsl() { return kPrelude + Text(k_ffx_a) + kEasuHead + Text(k_ffx_fsr1) + kEasuMain; } std::string RcasGlsl() { return kPrelude + Text(k_ffx_a) + kRcasHead + Text(k_ffx_fsr1) + kRcasMain; } }