diff --git a/client/renderers/EGL/CMakeLists.txt b/client/renderers/EGL/CMakeLists.txt index d1433afd..31408c6c 100644 --- a/client/renderers/EGL/CMakeLists.txt +++ b/client/renderers/EGL/CMakeLists.txt @@ -63,6 +63,7 @@ build_shaders( shader/ffx_cas.frag shader/ffx_fsr1_easu.frag shader/ffx_fsr1_rcas.frag + shader/downscale.frag ) make_defines( @@ -91,6 +92,7 @@ add_library(renderer_EGL STATIC ffx.c filter_ffx_cas.c filter_ffx_fsr1.c + filter_downscale.c ${EGL_SHADER_OBJS} "${CMAKE_CURRENT_BINARY_DIR}/shader/desktop_rgb.def.h" ${PROJECT_TOP}/repos/cimgui/imgui/backends/imgui_impl_opengl3.cpp diff --git a/client/renderers/EGL/desktop.c b/client/renderers/EGL/desktop.c index 0874fa11..4df1777b 100644 --- a/client/renderers/EGL/desktop.c +++ b/client/renderers/EGL/desktop.c @@ -171,8 +171,9 @@ bool egl_desktopInit(EGL * egl, EGL_Desktop ** desktop_, EGLDisplay * display, return false; } - egl_postProcessAdd(desktop->pp, &egl_filterFFXCASOps ); - egl_postProcessAdd(desktop->pp, &egl_filterFFXFSR1Ops); + egl_postProcessAdd(desktop->pp, &egl_filterDownscaleOps); + egl_postProcessAdd(desktop->pp, &egl_filterFFXCASOps ); + egl_postProcessAdd(desktop->pp, &egl_filterFFXFSR1Ops ); return true; } diff --git a/client/renderers/EGL/filter_downscale.c b/client/renderers/EGL/filter_downscale.c new file mode 100644 index 00000000..c7aec947 --- /dev/null +++ b/client/renderers/EGL/filter_downscale.c @@ -0,0 +1,263 @@ +/** + * Looking Glass + * Copyright © 2017-2021 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "filter.h" +#include "framebuffer.h" + +#include + +#include "common/debug.h" +#include "common/option.h" +#include "cimgui.h" + +#include "basic.vert.h" +#include "downscale.frag.h" + +typedef struct EGL_FilterDownscale +{ + EGL_Filter base; + + EGL_Shader * shader; + bool enable; + EGL_Uniform uniform; + + enum EGL_PixelFormat pixFmt; + unsigned int width, height; + float pixelSize; + float vOffset, hOffset; + bool prepared; + + EGL_Framebuffer * fb; + GLuint sampler; +} +EGL_FilterDownscale; + +static void egl_filterDownscaleEarlyInit(void) +{ + // doesn't really make sense to have any options for this filter + // as it's per title. We need presets to make this nicer to use. + static struct Option options[] = + { + { 0 } + }; + + option_register(options); +} + +static bool egl_filterDownscaleInit(EGL_Filter ** filter) +{ + EGL_FilterDownscale * this = calloc(1, sizeof(*this)); + if (!this) + { + DEBUG_ERROR("Failed to allocate ram"); + return false; + } + + if (!egl_shaderInit(&this->shader)) + { + DEBUG_ERROR("Failed to initialize the shader"); + goto error_this; + } + + if (!egl_shaderCompile(this->shader, + b_shader_basic_vert , b_shader_basic_vert_size, + b_shader_downscale_frag, b_shader_downscale_frag_size) + ) + { + DEBUG_ERROR("Failed to compile the shader"); + goto error_shader; + } + + this->uniform.type = EGL_UNIFORM_TYPE_3F; + this->uniform.location = + egl_shaderGetUniform(this->shader, "uConfig"); + + if (!egl_framebufferInit(&this->fb)) + { + DEBUG_ERROR("Failed to initialize the framebuffer"); + goto error_shader; + } + + glGenSamplers(1, &this->sampler); + glSamplerParameteri(this->sampler, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glSamplerParameteri(this->sampler, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE); + glSamplerParameteri(this->sampler, GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE); + + this->enable = false; + this->pixelSize = 2.0f; + this->vOffset = 0.0f; + this->hOffset = 0.0f; + + *filter = &this->base; + return true; + +error_shader: + egl_shaderFree(&this->shader); + +error_this: + free(this); + return false; +} + +static void egl_filterDownscaleFree(EGL_Filter * filter) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); + + egl_shaderFree(&this->shader); + egl_framebufferFree(&this->fb); + glDeleteSamplers(1, &this->sampler); + free(this); +} + +static bool egl_filterDownscaleImguiConfig(EGL_Filter * filter) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); + + bool redraw = false; + bool enable = this->enable; + + igCheckbox("Enable", &enable); + if (enable != this->enable) + { + this->enable = enable; + redraw = true; + } + + int majorPixelSize = floor(this->pixelSize); + int minorPixelSize = (this->pixelSize - majorPixelSize) * 10.0f; + + igSliderInt("Major Pixel Size", &majorPixelSize, 1, 10, NULL, 0); + igSliderInt("Minor Pixel Size", &minorPixelSize, 0, 9, NULL, 0); + + float pixelSize = (float)majorPixelSize + (float)minorPixelSize / 10.0f; + igText("Pixel Size: %.2f", pixelSize); + igText("Resolution: %dx%d", this->width, this->height); + if (pixelSize != this->pixelSize) + { + this->pixelSize = pixelSize; + redraw = true; + } + + float vOffset = this->vOffset; + igSliderFloat("V-Offset", &vOffset, -2, 2, NULL, 0); + if (vOffset != this->vOffset) + { + this->vOffset = vOffset; + redraw = true; + } + + float hOffset = this->hOffset; + igSliderFloat("H-Offset", &hOffset, -2, 2, NULL, 0); + if (hOffset != this->hOffset) + { + this->hOffset = hOffset; + redraw = true; + } + + if (redraw) + this->prepared = false; + + return redraw; +} + +static bool egl_filterDownscaleSetup(EGL_Filter * filter, + enum EGL_PixelFormat pixFmt, unsigned int width, unsigned int height) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); + + width = (float)width / this->pixelSize; + height = (float)height / this->pixelSize; + + if (this->prepared && + pixFmt == this->pixFmt && + this->width == width && + this->height == height) + return this->pixelSize > 1.0f; + + if (!egl_framebufferSetup(this->fb, pixFmt, width, height)) + return false; + + this->pixFmt = pixFmt; + this->width = width; + this->height = height; + this->prepared = false; + + return this->pixelSize > 1.0f; +} + +static void egl_filterDownscaleGetOutputRes(EGL_Filter * filter, + unsigned int *width, unsigned int *height) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); + *width = this->width; + *height = this->height; +} + +static bool egl_filterDownscalePrepare(EGL_Filter * filter) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); + + if (!this->enable) + return false; + + if (this->prepared) + return true; + + this->uniform.f[0] = this->pixelSize; + this->uniform.f[1] = this->vOffset; + this->uniform.f[2] = this->hOffset; + egl_shaderSetUniforms(this->shader, &this->uniform, 1); + this->prepared = true; + + return true; +} + +static GLuint egl_filterDownscaleRun(EGL_Filter * filter, EGL_Model * model, + GLuint texture) +{ + EGL_FilterDownscale * this = UPCAST(EGL_FilterDownscale, filter); + + egl_framebufferBind(this->fb); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + glGenerateMipmap(GL_TEXTURE_2D); + glBindSampler(0, this->sampler); + + egl_shaderUse(this->shader); + egl_modelRender(model); + + return egl_framebufferGetTexture(this->fb); +} + +EGL_FilterOps egl_filterDownscaleOps = +{ + .name = "Downscaler", + .type = EGL_FILTER_TYPE_DOWNSCALE, + .earlyInit = egl_filterDownscaleEarlyInit, + .init = egl_filterDownscaleInit, + .free = egl_filterDownscaleFree, + .imguiConfig = egl_filterDownscaleImguiConfig, + .setup = egl_filterDownscaleSetup, + .getOutputRes = egl_filterDownscaleGetOutputRes, + .prepare = egl_filterDownscalePrepare, + .run = egl_filterDownscaleRun +}; diff --git a/client/renderers/EGL/filters.h b/client/renderers/EGL/filters.h index 60a09293..8029aa39 100644 --- a/client/renderers/EGL/filters.h +++ b/client/renderers/EGL/filters.h @@ -20,5 +20,6 @@ #pragma once +extern EGL_FilterOps egl_filterDownscaleOps; extern EGL_FilterOps egl_filterFFXCASOps; extern EGL_FilterOps egl_filterFFXFSR1Ops; diff --git a/client/renderers/EGL/postprocess.c b/client/renderers/EGL/postprocess.c index 76f6c9d6..b0d670f9 100644 --- a/client/renderers/EGL/postprocess.c +++ b/client/renderers/EGL/postprocess.c @@ -32,6 +32,7 @@ static const EGL_FilterOps * EGL_Filters[] = { + &egl_filterDownscaleOps, &egl_filterFFXFSR1Ops, &egl_filterFFXCASOps }; diff --git a/client/renderers/EGL/shader/downscale.frag b/client/renderers/EGL/shader/downscale.frag new file mode 100644 index 00000000..78614c25 --- /dev/null +++ b/client/renderers/EGL/shader/downscale.frag @@ -0,0 +1,26 @@ +#version 300 es +precision mediump float; + +in vec2 fragCoord; +out vec4 fragColor; + +uniform sampler2D texture; +uniform vec3 uConfig; + +void main() +{ + float pixelSize = uConfig.x; + float vOffset = uConfig.y; + float hOffset = uConfig.z; + + vec2 inRes = vec2(textureSize(texture, 0)); + ivec2 point = ivec2( + (floor((fragCoord * inRes) / pixelSize) * pixelSize) + + pixelSize / 2.0f + ); + + point.x += int(pixelSize * hOffset); + point.y += int(pixelSize * vOffset); + + fragColor = texelFetch(texture, point, 0); +}