diff --git a/app/src/main/assets/sgsr1_shader_mobile_edge_direction.frag b/app/src/main/assets/sgsr1_shader_mobile_edge_direction.frag
new file mode 100644
index 0000000000..277e50d23f
--- /dev/null
+++ b/app/src/main/assets/sgsr1_shader_mobile_edge_direction.frag
@@ -0,0 +1,137 @@
+#version 310 es
+
+//============================================================================================================
+//
+// Copyright (c) 2025, Qualcomm Innovation Center, Inc. All rights reserved.
+// SPDX-License-Identifier: BSD-3-Clause
+//
+//============================================================================================================
+
+// Fallback if the defines are not injected dynamically
+#ifndef SRC_W
+#define SRC_W 1280.0
+#define SRC_H 720.0
+#define INV_SRC_W (1.0 / 1280.0)
+#define INV_SRC_H (1.0 / 720.0)
+#endif
+
+precision mediump float;
+precision mediump int;
+
+////////////////////////
+// USER CONFIGURATION //
+////////////////////////
+
+#define EdgeThreshold (8.0 / 255.0)
+#define EdgeSharpness 2.0
+
+////////////////////////
+////////////////////////
+////////////////////////
+
+// ViewportInfo is a compile-time constant expression for optimal performance
+const mediump vec4 ViewportInfo = vec4(INV_SRC_W, INV_SRC_H, SRC_W, SRC_H);
+
+uniform mediump sampler2D ps0;
+
+// Variables passed down from the Vertex Shader to avoid per-pixel calculations
+in mediump vec2 v_texCoord;
+in mediump vec2 v_imgCoord;
+
+out mediump vec4 out_Target0;
+
+mediump float fastLanczos2(mediump float x)
+{
+ mediump float wA = x - 4.0;
+ mediump float wB = x * wA - wA;
+ return wB * (wA * wA);
+}
+
+mediump vec2 weightY(mediump vec2 d, mediump float c, mediump vec3 data)
+{
+ mediump float std = data.x;
+ mediump vec2 dir = data.yz;
+
+ mediump float edgeDis = dot(d, dir.yx);
+ mediump float x = dot(d, d) + (edgeDis * edgeDis) * (clamp((c * c) * std, 0.0, 1.0) * 0.7 - 1.0);
+
+ return vec2(1.0, c) * fastLanczos2(x);
+}
+
+mediump vec2 edgeDirection(mediump vec4 left, mediump vec4 right)
+{
+ mediump float RxLz = right.x - left.z;
+ mediump float RwLy = right.w - left.y;
+ mediump vec2 delta = vec2(RxLz + RwLy, RxLz - RwLy);
+ mediump float lengthInv = inversesqrt(dot(delta, delta) + 3.075740e-05);
+
+ return delta * lengthInv;
+}
+
+void main()
+{
+ mediump vec4 color = textureLod(ps0, v_texCoord, 0.0);
+
+ mediump vec2 coord = floor(v_imgCoord) * ViewportInfo.xy;
+ mediump vec2 pl = fract(v_imgCoord);
+
+ mediump vec4 left = textureGather(ps0, coord, 1);
+
+ mediump float edgeVote = abs(left.z - left.y) + abs(color.y - left.y) + abs(color.y - left.z);
+
+ if(edgeVote > EdgeThreshold)
+ {
+ coord.x += ViewportInfo.x;
+
+ mediump vec4 right = textureGather(ps0, coord + vec2(ViewportInfo.x, 0.0), 1);
+ mediump vec4 upDown;
+ upDown.xy = textureGather(ps0, coord + vec2(0.0, -ViewportInfo.y), 1).wz;
+ upDown.zw = textureGather(ps0, coord + vec2(0.0, ViewportInfo.y), 1).yx;
+
+ mediump float mean = (left.y + left.z + right.x + right.w) * 0.25;
+
+ left -= mean;
+ right -= mean;
+ upDown -= mean;
+ color.w = color.y - mean;
+
+ mediump float sum = dot(abs(left), vec4(1.0))
+ + dot(abs(right), vec4(1.0))
+ + dot(abs(upDown), vec4(1.0));
+
+ mediump float sumMean = 10.14185 / sum;
+ mediump float std = sumMean * sumMean;
+
+ mediump vec3 data = vec3(std, edgeDirection(left, right));
+
+ mediump vec2 aWY = weightY(pl + vec2(0.0, 1.0), upDown.x, data);
+ aWY += weightY(pl + vec2(-1.0, 1.0), upDown.y, data);
+ aWY += weightY(pl + vec2(-1.0, -2.0), upDown.z, data);
+ aWY += weightY(pl + vec2(0.0, -2.0), upDown.w, data);
+ aWY += weightY(pl + vec2(1.0, -1.0), left.x, data);
+ aWY += weightY(pl + vec2(0.0, -1.0), left.y, data);
+ aWY += weightY(pl, left.z, data);
+ aWY += weightY(pl + vec2(1.0, 0.0), left.w, data);
+ aWY += weightY(pl + vec2(-1.0, -1.0), right.x, data);
+ aWY += weightY(pl + vec2(-2.0, -1.0), right.y, data);
+ aWY += weightY(pl + vec2(-2.0, 0.0), right.z, data);
+ aWY += weightY(pl + vec2(-1.0, 0.0), right.w, data);
+
+ mediump float finalY = aWY.y / aWY.x;
+
+ mediump vec2 leftYZ_rightXW = vec2(max(left.y, left.z), max(right.x, right.w));
+ mediump float maxY = max(leftYZ_rightXW.x, leftYZ_rightXW.y);
+
+ mediump vec2 min_leftYZ_rightXW = vec2(min(left.y, left.z), min(right.x, right.w));
+ mediump float minY = min(min_leftYZ_rightXW.x, min_leftYZ_rightXW.y);
+
+ mediump float deltaY = clamp(EdgeSharpness * finalY, minY, maxY) - color.w;
+
+ deltaY = clamp(deltaY, -23.0 / 255.0, 23.0 / 255.0);
+
+ color.rgb = clamp(color.rgb + deltaY, 0.0, 1.0);
+ }
+
+ color.w = 1.0;
+ out_Target0 = color;
+}
diff --git a/app/src/main/java/com/limelight/binding/video/GlesPassthroughBridge.java b/app/src/main/java/com/limelight/binding/video/GlesPassthroughBridge.java
new file mode 100644
index 0000000000..2c9b312850
--- /dev/null
+++ b/app/src/main/java/com/limelight/binding/video/GlesPassthroughBridge.java
@@ -0,0 +1,517 @@
+package com.limelight.binding.video;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLExt;
+import android.opengl.EGLSurface;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES31;
+import android.view.Surface;
+import com.limelight.LimeLog;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * This class acts as a bridge between the MediaCodec decoder and the final display surface.
+ * It implements a Two-Pass OpenGL ES 3.1 Pipeline to support standard sampler2D shaders (like SGSR1).
+ *
+ * Pass 1: Renders the hardware-decoded OES texture (samplerExternalOES) into a standard 2D texture via an FBO.
+ * Pass 2: Applies the custom post-processing shader (e.g., SGSR1) on the 2D texture and renders it to the display.
+ */
+public class GlesPassthroughBridge implements SurfaceTexture.OnFrameAvailableListener {
+ private static final String TAG = "GlesBridge";
+
+ // --- PASS 1: OES to 2D Texture ---
+ // Vertex shader for the first pass (hardware decode to FBO)
+ private static final String BLIT_VERTEX_SHADER =
+ "#version 310 es\n" +
+ "uniform mat4 uSTMatrix;\n" +
+ "in vec4 aPosition;\n" +
+ "in vec4 aTexCoord;\n" +
+ "out vec2 vTexCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = aPosition;\n" +
+ " vTexCoord = (uSTMatrix * aTexCoord).xy;\n" + // Apply the SurfaceTexture transform matrix
+ "}\n";
+
+ // Fragment shader for the first pass (reads from OES texture)
+ private static final String BLIT_FRAGMENT_SHADER =
+ "#version 310 es\n" +
+ "#extension GL_OES_EGL_image_external_essl3 : require\n" +
+ "precision mediump float;\n" +
+ "in vec2 vTexCoord;\n" +
+ "uniform samplerExternalOES sTexture;\n" +
+ "out vec4 fragColor;\n" +
+ "void main() {\n" +
+ " fragColor = texture(sTexture, vTexCoord);\n" +
+ "}\n";
+
+ // Fallback shader used if the external custom shader (e.g., SGSR) fails to load
+ private static final String FALLBACK_POST_FRAGMENT_SHADER =
+ "#version 310 es\n" +
+ "precision mediump float;\n" +
+ "in vec2 v_texCoord;\n" +
+ "in vec2 v_imgCoord;\n" +
+ "uniform sampler2D sTexture;\n" +
+ "out vec4 fragColor;\n" +
+ "void main() {\n" +
+ " fragColor = texture(sTexture, v_texCoord);\n" +
+ "}\n";
+
+ private final Context context;
+
+ // EGL State
+ private EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
+ private EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
+ private EGLSurface eglSurface = EGL14.EGL_NO_SURFACE;
+
+ // Shader Programs
+ private int blitProgram = 0;
+ private int postProgram = 0;
+
+ // Textures and Framebuffers
+ private int oesTextureId; // Texture ID for the hardware decoder output
+ private int fboTextureId; // Standard 2D texture ID attached to the FBO
+ private int fboId; // Framebuffer Object ID for the first pass
+
+ // Surface management
+ private SurfaceTexture surfaceTexture;
+ private Surface decoderSurface;
+ private final float[] transformMatrix = new float[16];
+
+ // Handles for Pass 1 (Blit)
+ private int blit_uSTMatrixHandle;
+ private int blit_aPositionHandle;
+ private int blit_aTexCoordHandle;
+ private int blit_sTextureHandle;
+
+ // Handles for Pass 2 (Custom Shader)
+ private int post_aPositionHandle;
+ private int post_aTexCoordHandle;
+ private int post_sTextureHandle;
+
+ // Full-screen quad vertices (Position X, Y, Texture X, Y)
+ private static final float[] VERTICES = {
+ -1.0f, -1.0f, 0.0f, 0.0f, // Bottom Left
+ 1.0f, -1.0f, 1.0f, 0.0f, // Bottom Right
+ -1.0f, 1.0f, 0.0f, 1.0f, // Top Left
+ 1.0f, 1.0f, 1.0f, 1.0f, // Top Right
+ };
+ private FloatBuffer vertexBuffer;
+
+ // Concurrency and synchronization
+ private final Object frameAvailableLock = new Object();
+ private boolean frameAvailable = false;
+
+ // Resolution information
+ private int streamWidth, streamHeight;
+ private int displayWidth, displayHeight;
+
+ public GlesPassthroughBridge(Context context) {
+ this.context = context;
+
+ // Allocate direct memory for the vertex buffer
+ vertexBuffer = ByteBuffer.allocateDirect(VERTICES.length * 4)
+ .order(ByteOrder.nativeOrder())
+ .asFloatBuffer();
+ vertexBuffer.put(VERTICES).position(0);
+ }
+
+ /**
+ * Utility method to load a shader from the assets folder.
+ * @param fileName The name of the shader file in assets.
+ * @return The string content of the shader, or null if loading failed.
+ */
+ private String loadAsset(String fileName) {
+ StringBuilder sb = new StringBuilder();
+ try (InputStream is = context.getAssets().open(fileName);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+ } catch (IOException e) {
+ LimeLog.severe(TAG + ": Failed to load asset: " + fileName + " - " + e.getMessage());
+ return null;
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Helper method to check for OpenGL errors and log them.
+ * @param op The operation that was just performed.
+ */
+ private void checkGlError(String op) {
+ int error;
+ while ((error = GLES31.glGetError()) != GLES31.GL_NO_ERROR) {
+ LimeLog.severe(TAG + ": " + op + ": glError " + error);
+ }
+ }
+
+ /**
+ * Initializes the EGL context, the shader programs, and the rendering surfaces.
+ * Must be called before any rendering takes place.
+ *
+ * @param targetSurface The final display surface (e.g., from a SurfaceView).
+ * @param streamWidth The width of the incoming video stream.
+ * @param streamHeight The height of the incoming video stream.
+ */
+ public void initialize(Surface targetSurface, int streamWidth, int streamHeight) {
+ this.streamWidth = streamWidth;
+ this.streamHeight = streamHeight;
+
+ // Initialize EGL Display
+ eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+ int[] version = new int[2];
+ EGL14.eglInitialize(eglDisplay, version, 0, version, 1);
+
+ // Configure EGL for OpenGL ES 3.0+
+ int[] configAttribs = {
+ EGL14.EGL_RENDERABLE_TYPE, EGLExt.EGL_OPENGL_ES3_BIT_KHR,
+ EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8,
+ EGL14.EGL_NONE
+ };
+ EGLConfig[] configs = new EGLConfig[1];
+ int[] numConfigs = new int[1];
+ EGL14.eglChooseConfig(eglDisplay, configAttribs, 0, configs, 0, 1, numConfigs, 0);
+
+ // Create EGL Context
+ int[] contextAttribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE };
+ eglContext = EGL14.eglCreateContext(eglDisplay, configs[0], EGL14.EGL_NO_CONTEXT, contextAttribs, 0);
+
+ // Create EGL Surface connected to the target display
+ eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0], targetSurface, new int[]{EGL14.EGL_NONE}, 0);
+
+ if (eglSurface == EGL14.EGL_NO_SURFACE) {
+ int error = EGL14.eglGetError();
+ LimeLog.severe(TAG + ": eglCreateWindowSurface failed: " + error);
+ throw new IllegalStateException("eglCreateWindowSurface failed with error " + error);
+ }
+
+ if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
+ int error = EGL14.eglGetError();
+ LimeLog.severe(TAG + ": eglMakeCurrent failed: " + error);
+ throw new IllegalStateException("eglMakeCurrent failed with error " + error);
+ }
+
+ // Disable VSync synchronization inside EGL to avoid adding latency
+ EGL14.eglSwapInterval(eglDisplay, 0);
+
+ // Query the actual dimensions of the display surface
+ int[] queryW = new int[1], queryH = new int[1];
+ EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_WIDTH, queryW, 0);
+ EGL14.eglQuerySurface(eglDisplay, eglSurface, EGL14.EGL_HEIGHT, queryH, 0);
+ this.displayWidth = queryW[0];
+ this.displayHeight = queryH[0];
+
+ // 1. Setup Blit Program (OES -> 2D Texture)
+ blitProgram = createProgram(BLIT_VERTEX_SHADER, BLIT_FRAGMENT_SHADER);
+ blit_uSTMatrixHandle = GLES31.glGetUniformLocation(blitProgram, "uSTMatrix");
+ blit_aPositionHandle = GLES31.glGetAttribLocation(blitProgram, "aPosition");
+ blit_aTexCoordHandle = GLES31.glGetAttribLocation(blitProgram, "aTexCoord");
+ blit_sTextureHandle = GLES31.glGetUniformLocation(blitProgram, "sTexture");
+
+ // 2. Setup Post-Process Program (Custom Shader)
+ setupPostProgram();
+
+ // 3. Generate Textures
+ int[] textures = new int[2];
+ GLES31.glGenTextures(2, textures, 0);
+ oesTextureId = textures[0];
+ fboTextureId = textures[1];
+
+ // Configure OES Texture (used by MediaCodec)
+ GLES31.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId);
+ GLES31.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES31.GL_TEXTURE_MIN_FILTER, GLES31.GL_NEAREST);
+ GLES31.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES31.GL_TEXTURE_MAG_FILTER, GLES31.GL_NEAREST);
+ GLES31.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES31.GL_TEXTURE_WRAP_S, GLES31.GL_CLAMP_TO_EDGE);
+ GLES31.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES31.GL_TEXTURE_WRAP_T, GLES31.GL_CLAMP_TO_EDGE);
+
+ // Configure Standard 2D Texture (used as FBO color attachment)
+ GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, fboTextureId);
+ GLES31.glTexImage2D(GLES31.GL_TEXTURE_2D, 0, GLES31.GL_RGBA, streamWidth, streamHeight, 0, GLES31.GL_RGBA, GLES31.GL_UNSIGNED_BYTE, null);
+ GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MIN_FILTER, GLES31.GL_LINEAR);
+ GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_MAG_FILTER, GLES31.GL_LINEAR);
+ GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_S, GLES31.GL_CLAMP_TO_EDGE);
+ GLES31.glTexParameteri(GLES31.GL_TEXTURE_2D, GLES31.GL_TEXTURE_WRAP_T, GLES31.GL_CLAMP_TO_EDGE);
+
+ // 4. Setup Framebuffer Object (FBO) for Pass 1
+ int[] fbos = new int[1];
+ GLES31.glGenFramebuffers(1, fbos, 0);
+ fboId = fbos[0];
+ GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, fboId);
+ GLES31.glFramebufferTexture2D(GLES31.GL_FRAMEBUFFER, GLES31.GL_COLOR_ATTACHMENT0, GLES31.GL_TEXTURE_2D, fboTextureId, 0);
+
+ // Ensure the Framebuffer is complete
+ int status = GLES31.glCheckFramebufferStatus(GLES31.GL_FRAMEBUFFER);
+ if (status != GLES31.GL_FRAMEBUFFER_COMPLETE) {
+ LimeLog.severe(TAG + ": Framebuffer incomplete: " + status);
+ }
+ // Unbind FBO
+ GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
+
+ // Create SurfaceTexture to receive hardware decoded frames
+ surfaceTexture = new SurfaceTexture(oesTextureId);
+ surfaceTexture.setOnFrameAvailableListener(this);
+ decoderSurface = new Surface(surfaceTexture);
+
+ // IMPORTANT: Unbind context so it can be picked up by the rendering thread!
+ EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
+
+ LimeLog.info(TAG + ": GLES 3.1 2-Pass Bridge initialized: Stream=" + streamWidth + "x" + streamHeight + " Display=" + displayWidth + "x" + displayHeight);
+ }
+
+ private void setupPostProgram() {
+ if (postProgram != 0) {
+ GLES31.glDeleteProgram(postProgram);
+ postProgram = 0;
+ }
+
+ String postSource = loadAsset("sgsr1_shader_mobile_edge_direction.frag");
+
+ // Define stream size resolutions constants directly into the GLSL Code at compile time
+ // This leverages "Constant Folding", an optimization where the GPU calculates all
+ // resolutions and math functions explicitly at compile time instead of per-frame.
+ String compileTimeDefines = "\n#define SRC_W " + streamWidth + ".0\n" +
+ "#define SRC_H " + streamHeight + ".0\n" +
+ "#define INV_SRC_W " + (1.0f / streamWidth) + "\n" +
+ "#define INV_SRC_H " + (1.0f / streamHeight) + "\n";
+
+ if (postSource == null || postSource.trim().isEmpty()) {
+ LimeLog.warning(TAG + ": sgsr1_shader_mobile_edge_direction.frag is missing or empty. Using fallback shader.");
+ postSource = FALLBACK_POST_FRAGMENT_SHADER;
+ } else {
+ // Inject the dynamic resolutions into the source code of the Fragment Shader.
+ postSource = postSource.replaceFirst("(#version 310 es)", "$1" + compileTimeDefines);
+ }
+
+ // Vertex shader for the final post-processing pass
+ // It has been highly optimized to pre-calculate image coordinates across the 4 corners
+ // of the vertex, allowing the hardware interpolator to naturally pass the coordinate
+ // per-fragment down into the pipeline for completely free calculations.
+ String postVertexShader =
+ "#version 310 es\n" +
+ compileTimeDefines +
+ "in vec4 aPosition;\n" +
+ "in vec4 aTexCoord;\n" +
+ "out vec2 v_texCoord;\n" +
+ "out vec2 v_imgCoord;\n" +
+ "void main() {\n" +
+ " gl_Position = aPosition;\n" +
+ " v_texCoord = aTexCoord.xy;\n" +
+ " v_imgCoord = (aTexCoord.xy * vec2(SRC_W, SRC_H)) + vec2(-0.5, 0.5);\n" +
+ "}\n";
+
+ postProgram = createProgram(postVertexShader, postSource);
+ post_aPositionHandle = GLES31.glGetAttribLocation(postProgram, "aPosition");
+ post_aTexCoordHandle = GLES31.glGetAttribLocation(postProgram, "aTexCoord");
+
+ // Find sampler uniform (supports different naming conventions for flexibility)
+ post_sTextureHandle = GLES31.glGetUniformLocation(postProgram, "sTexture");
+ if (post_sTextureHandle == -1) post_sTextureHandle = GLES31.glGetUniformLocation(postProgram, "uTexture");
+ if (post_sTextureHandle == -1) post_sTextureHandle = GLES31.glGetUniformLocation(postProgram, "Source");
+ if (post_sTextureHandle == -1) post_sTextureHandle = GLES31.glGetUniformLocation(postProgram, "ps0"); // Used by SGSR1
+ }
+
+ /**
+ * @return The surface to pass to the MediaCodec decoder.
+ */
+ public Surface getDecoderSurface() {
+ return decoderSurface;
+ }
+
+ /**
+ * Callback fired by Android when a new frame is decoded and ready to be processed.
+ */
+ @Override
+ public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+ synchronized (frameAvailableLock) {
+ frameAvailable = true;
+ frameAvailableLock.notifyAll(); // Wake up the rendering thread
+ }
+ }
+
+ /**
+ * Renders a single frame. Should be called repeatedly from a dedicated rendering thread.
+ *
+ * @param presentationTimeNanos The presentation time for the current frame.
+ */
+ public void renderFrame(long presentationTimeNanos) {
+ if (eglDisplay == EGL14.EGL_NO_DISPLAY) return;
+
+ // Wait for a new frame to be available from the decoder
+ synchronized (frameAvailableLock) {
+ long startTime = System.currentTimeMillis();
+ while (!frameAvailable) {
+ try {
+ frameAvailableLock.wait(50);
+ // Timeout after 50ms to prevent deadlocks
+ if (System.currentTimeMillis() - startTime > 50) break;
+ } catch (InterruptedException e) { return; }
+ }
+ frameAvailable = false;
+ }
+
+ // Ensure the EGL context is current on the rendering thread
+ if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
+ LimeLog.severe(TAG + ": eglMakeCurrent failed in renderFrame");
+ return;
+ }
+
+ // Consume the new frame from the decoder
+ surfaceTexture.updateTexImage();
+ surfaceTexture.getTransformMatrix(transformMatrix);
+
+ // ==========================================================
+ // PASS 1: Decode to 2D Texture (FBO)
+ // ==========================================================
+
+ // Bind the FBO to render off-screen
+ GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, fboId);
+ GLES31.glViewport(0, 0, streamWidth, streamHeight);
+ GLES31.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GLES31.glClear(GLES31.GL_COLOR_BUFFER_BIT);
+
+ GLES31.glUseProgram(blitProgram);
+
+ // Pass the SurfaceTexture transform matrix to correct orientation
+ if (blit_uSTMatrixHandle >= 0) GLES31.glUniformMatrix4fv(blit_uSTMatrixHandle, 1, false, transformMatrix, 0);
+ if (blit_sTextureHandle >= 0) GLES31.glUniform1i(blit_sTextureHandle, 0);
+
+ // Bind the hardware OES texture
+ GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
+ GLES31.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, oesTextureId);
+
+ // Draw a full-screen quad to execute the blit
+ drawQuad(blit_aPositionHandle, blit_aTexCoordHandle);
+
+ // ==========================================================
+ // PASS 2: Post-Process to Screen
+ // ==========================================================
+
+ // Bind the default framebuffer to render to the display
+ GLES31.glBindFramebuffer(GLES31.GL_FRAMEBUFFER, 0);
+ GLES31.glViewport(0, 0, displayWidth, displayHeight);
+ GLES31.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ GLES31.glClear(GLES31.GL_COLOR_BUFFER_BIT);
+
+ GLES31.glUseProgram(postProgram);
+
+ if (post_sTextureHandle >= 0) GLES31.glUniform1i(post_sTextureHandle, 0);
+
+ // Bind the 2D texture populated during Pass 1
+ GLES31.glActiveTexture(GLES31.GL_TEXTURE0);
+ GLES31.glBindTexture(GLES31.GL_TEXTURE_2D, fboTextureId);
+
+ // Draw the final post-processed image
+ drawQuad(post_aPositionHandle, post_aTexCoordHandle);
+
+ // Present the frame to the display
+ EGLExt.eglPresentationTimeANDROID(eglDisplay, eglSurface, presentationTimeNanos);
+ EGL14.eglSwapBuffers(eglDisplay, eglSurface);
+ }
+
+ /**
+ * Draws a standard full-screen quad using the allocated vertex buffer.
+ *
+ * @param posHandle The GL attribute handle for vertex positions.
+ * @param texHandle The GL attribute handle for texture coordinates.
+ */
+ private void drawQuad(int posHandle, int texHandle) {
+ if (posHandle >= 0) {
+ GLES31.glEnableVertexAttribArray(posHandle);
+ vertexBuffer.position(0);
+ // Each vertex is 4 floats: [PosX, PosY, TexU, TexV]. Stride = 4 * 4 bytes = 16.
+ GLES31.glVertexAttribPointer(posHandle, 2, GLES31.GL_FLOAT, false, 16, vertexBuffer);
+ }
+ if (texHandle >= 0) {
+ GLES31.glEnableVertexAttribArray(texHandle);
+ vertexBuffer.position(2); // Offset to start at TexU
+ GLES31.glVertexAttribPointer(texHandle, 2, GLES31.GL_FLOAT, false, 16, vertexBuffer);
+ }
+
+ // Execute the draw call
+ GLES31.glDrawArrays(GLES31.GL_TRIANGLE_STRIP, 0, 4);
+
+ // Cleanup attribute bindings
+ if (posHandle >= 0) GLES31.glDisableVertexAttribArray(posHandle);
+ if (texHandle >= 0) GLES31.glDisableVertexAttribArray(texHandle);
+ }
+
+ /**
+ * Compiles and links a complete GLSL program from vertex and fragment shader source code.
+ */
+ private int createProgram(String vertexSource, String fragmentSource) {
+ int vertexShader = loadShader(GLES31.GL_VERTEX_SHADER, vertexSource);
+ if (vertexShader == 0) return 0;
+ int fragmentShader = loadShader(GLES31.GL_FRAGMENT_SHADER, fragmentSource);
+ if (fragmentShader == 0) return 0;
+
+ int program = GLES31.glCreateProgram();
+ if (program != 0) {
+ GLES31.glAttachShader(program, vertexShader);
+ GLES31.glAttachShader(program, fragmentShader);
+ GLES31.glLinkProgram(program);
+
+ int[] linkStatus = new int[1];
+ GLES31.glGetProgramiv(program, GLES31.GL_LINK_STATUS, linkStatus, 0);
+ if (linkStatus[0] != GLES31.GL_TRUE) {
+ LimeLog.severe(TAG + ": Could not link program: " + GLES31.glGetProgramInfoLog(program));
+ GLES31.glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ return program;
+ }
+
+ /**
+ * Compiles a single shader object.
+ */
+ private int loadShader(int type, String shaderCode) {
+ int shader = GLES31.glCreateShader(type);
+ GLES31.glShaderSource(shader, shaderCode);
+ GLES31.glCompileShader(shader);
+
+ int[] compiled = new int[1];
+ GLES31.glGetShaderiv(shader, GLES31.GL_COMPILE_STATUS, compiled, 0);
+ if (compiled[0] == 0) {
+ LimeLog.severe(TAG + ": Could not compile shader " + type + ": " + GLES31.glGetShaderInfoLog(shader));
+ GLES31.glDeleteShader(shader);
+ shader = 0;
+ }
+ return shader;
+ }
+
+ /**
+ * Frees all allocated OpenGL and EGL resources.
+ */
+ public void release() {
+ if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
+ EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
+ if (eglSurface != EGL14.EGL_NO_SURFACE) {
+ EGL14.eglDestroySurface(eglDisplay, eglSurface);
+ }
+ EGL14.eglDestroyContext(eglDisplay, eglContext);
+ EGL14.eglReleaseThread();
+ EGL14.eglTerminate(eglDisplay);
+ }
+ if (surfaceTexture != null) {
+ surfaceTexture.release();
+ }
+ if (decoderSurface != null) {
+ decoderSurface.release();
+ }
+ eglDisplay = EGL14.EGL_NO_DISPLAY;
+ eglContext = EGL14.EGL_NO_CONTEXT;
+ eglSurface = EGL14.EGL_NO_SURFACE;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
index d24ec0dc72..50e69659b7 100644
--- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
+++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
@@ -35,6 +35,7 @@
import android.os.SystemClock;
import android.util.Range;
import android.view.Choreographer;
+import android.view.Surface;
import android.view.SurfaceHolder;
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements Choreographer.FrameCallback {
@@ -79,6 +80,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C
private boolean foreground = true;
private PerfOverlayListener perfListener;
+ private GlesPassthroughBridge glesBridge;
+ private float videoSrUpscaleRatio;
+
private static final int CR_MAX_TRIES = 10;
private static final int CR_RECOVERY_TYPE_NONE = 0;
private static final int CR_RECOVERY_TYPE_FLUSH = 1;
@@ -537,7 +541,20 @@ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
LimeLog.info("Configuring with format: "+format);
- videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
+ if (prefs.videoSr) {
+ // SGSR1 Upscaling
+ glesBridge = new GlesPassthroughBridge(this.context);
+ glesBridge.initialize(renderTarget.getSurface(), initialWidth, initialHeight);
+ Surface decoderSurface = glesBridge.getDecoderSurface();
+
+ videoDecoder.configure(format, decoderSurface, null, 0);
+
+ this.videoSrUpscaleRatio = renderTarget.getSurfaceFrame().width() / (float)initialWidth;
+ } else {
+ videoDecoder.configure(format, renderTarget.getSurface(), null, 0);
+
+ this.videoSrUpscaleRatio = 1.0f;
+ }
configuredFormat = format;
@@ -590,6 +607,10 @@ private boolean tryConfigureDecoder(MediaCodecInfo selectedDecoderInfo, MediaFor
videoDecoder.release();
videoDecoder = null;
}
+ if (!configured && glesBridge != null) {
+ glesBridge.release();
+ glesBridge = null;
+ }
}
return configured;
}
@@ -761,6 +782,10 @@ private boolean doCodecRecoveryIfRequired(int quiescenceFlag) {
LimeLog.warning("Trying to restart decoder after CodecException");
try {
videoDecoder.stop();
+ if (glesBridge != null) {
+ glesBridge.release();
+ glesBridge = null;
+ }
configureAndStartDecoder(configuredFormat);
codecRecoveryType.set(CR_RECOVERY_TYPE_NONE);
} catch (IllegalArgumentException e) {
@@ -784,6 +809,10 @@ private boolean doCodecRecoveryIfRequired(int quiescenceFlag) {
LimeLog.warning("Trying to reset decoder after CodecException");
try {
videoDecoder.reset();
+ if (glesBridge != null) {
+ glesBridge.release();
+ glesBridge = null;
+ }
configureAndStartDecoder(configuredFormat);
codecRecoveryType.set(CR_RECOVERY_TYPE_NONE);
} catch (IllegalArgumentException e) {
@@ -807,6 +836,10 @@ private boolean doCodecRecoveryIfRequired(int quiescenceFlag) {
videoDecoder.release();
try {
+ if (glesBridge != null) {
+ glesBridge.release();
+ glesBridge = null;
+ }
int err = initializeDecoder(true);
if (err != 0) {
throw new IllegalStateException("Decoder reset failed: " + err);
@@ -1001,6 +1034,10 @@ public void doFrame(long frameTimeNanos) {
videoDecoder.releaseOutputBuffer(nextOutputBuffer, true);
}
+ if (prefs.videoSr && glesBridge != null) {
+ glesBridge.renderFrame(frameTimeNanos);
+ }
+
lastRenderedFrameTimeNanos = frameTimeNanos;
activeWindowVideoStats.totalFramesRendered++;
} catch (IllegalStateException ignored) {
@@ -1094,6 +1131,10 @@ public void run() {
}
}
+ if (prefs.videoSr && glesBridge != null) {
+ glesBridge.renderFrame(0);
+ }
+
activeWindowVideoStats.totalFramesRendered++;
}
else {
@@ -1298,6 +1339,10 @@ public void stop() {
@Override
public void cleanup() {
+ if (glesBridge != null) {
+ glesBridge.release();
+ glesBridge = null;
+ }
videoDecoder.release();
}
@@ -1445,6 +1490,11 @@ public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int dec
StringBuilder sb = new StringBuilder();
sb.append(context.getString(R.string.perf_overlay_streamdetails, initialWidth + "x" + initialHeight, fps.totalFps)).append('\n');
sb.append(context.getString(R.string.perf_overlay_decoder, decoder)).append('\n');
+ // Add the video enhancement line if SR is enabled
+ if (prefs.videoSr) {
+ // Pre-calculated ratio during decoder configuration
+ sb.append(context.getString(R.string.perf_overlay_video_enhancement, videoSrUpscaleRatio)).append('\n');
+ }
sb.append(context.getString(R.string.perf_overlay_incomingfps, fps.receivedFps)).append('\n');
sb.append(context.getString(R.string.perf_overlay_renderingfps, fps.renderedFps)).append('\n');
sb.append(context.getString(R.string.perf_overlay_netdrops,
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index 8ed01e3610..c3b3d13f6e 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -68,6 +68,7 @@ public enum AnalogStickForScrolling {
private static final String GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING = "checkbox_gamepad_touchpad_as_mouse";
private static final String GAMEPAD_MOTION_SENSORS_PREF_STRING = "checkbox_gamepad_motion_sensors";
private static final String GAMEPAD_MOTION_FALLBACK_PREF_STRING = "checkbox_gamepad_motion_fallback";
+ private static final String VIDEO_SR_PREF_STRING = "checkbox_video_sr";
static final String DEFAULT_RESOLUTION = "1280x720";
static final String DEFAULT_FPS = "60";
@@ -108,6 +109,7 @@ public enum AnalogStickForScrolling {
private static final boolean DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE = false;
private static final boolean DEFAULT_GAMEPAD_MOTION_SENSORS = true;
private static final boolean DEFAULT_GAMEPAD_MOTION_FALLBACK = false;
+ private static final boolean DEFAULT_VIDEO_SR = false;
public static final int FRAME_PACING_MIN_LATENCY = 0;
public static final int FRAME_PACING_BALANCED = 1;
@@ -155,6 +157,7 @@ public enum AnalogStickForScrolling {
public boolean gamepadMotionSensors;
public boolean gamepadTouchpadAsMouse;
public boolean gamepadMotionSensorsFallbackToDevice;
+ public boolean videoSr;
public static boolean isNativeResolution(int width, int height) {
// It's not a native resolution if it matches an existing resolution option
@@ -601,6 +604,7 @@ else if (audioConfig.equals("51")) {
config.gamepadTouchpadAsMouse = prefs.getBoolean(GAMEPAD_TOUCHPAD_AS_MOUSE_PREF_STRING, DEFAULT_GAMEPAD_TOUCHPAD_AS_MOUSE);
config.gamepadMotionSensors = prefs.getBoolean(GAMEPAD_MOTION_SENSORS_PREF_STRING, DEFAULT_GAMEPAD_MOTION_SENSORS);
config.gamepadMotionSensorsFallbackToDevice = prefs.getBoolean(GAMEPAD_MOTION_FALLBACK_PREF_STRING, DEFAULT_GAMEPAD_MOTION_FALLBACK);
+ config.videoSr = prefs.getBoolean(VIDEO_SR_PREF_STRING, DEFAULT_VIDEO_SR);
return config;
}
diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java
index 7070104168..e366bc9b05 100644
--- a/app/src/main/java/com/limelight/preferences/StreamSettings.java
+++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java
@@ -1,8 +1,10 @@
package com.limelight.preferences;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.ConfigurationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.MediaCodecInfo;
@@ -277,6 +279,21 @@ public void onCreate(Bundle savedInstanceState) {
addPreferencesFromResource(R.xml.preferences);
PreferenceScreen screen = getPreferenceScreen();
+ // Check for GLES 3.1 support (Required for advanced upscaling features like textureGather)
+ ActivityManager activityManager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
+ ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
+ boolean supportsGles31 = configurationInfo.reqGlEsVersion >= 0x30001;
+
+ // Handle Video Super Resolution checkbox
+ CheckBoxPreference videoSrPref = (CheckBoxPreference) findPreference("checkbox_video_sr");
+ if (videoSrPref != null) {
+ if (!supportsGles31) {
+ // Disable and gray out if GLES 3.1 is not available
+ videoSrPref.setEnabled(false);
+ videoSrPref.setSummary(videoSrPref.getSummary() + " (Requires GLES 3.1)");
+ }
+ }
+
// hide on-screen controls category on non touch screen devices
if (!getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
PreferenceCategory category =
@@ -492,7 +509,7 @@ public void run() {
@Override
public void run() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity());
- setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1080P);
+ setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.RES_1440P);
resetBitrateToDefault(prefs, null, null);
}
});
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index fffdb4af55..7e92442e7e 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -140,4 +140,7 @@
Използване на сензорния екран като тракпад
Автоматично откриване на наличен контролер
Премахването на отметката от тази опция принуждава контролер винаги да присъства
+ Подобряване на видеото: (x%1$.2f) Shader SGSR1
+ Видео супер резолюция
+ Използвайте хардуерно ускорение за подобряване на яснотата на изображението
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index 1947ff11a0..66fc117a80 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -247,4 +247,7 @@
Frame pacing videa
Zvolte, jak vyvážit zpoždění videa a jeho plynulost
Preferovat nejnižší zpoždění
+ Vylepšení videa: (x%1$.2f) Shader SGSR1
+ Super rozlišení videa
+ Využijte hardwarovou akceleraci ke zvýšení čistoty obrazu
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 03788348a7..5862519631 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -275,4 +275,7 @@
Keine (beide Sticks bewegen die Maus)
Rechter Analogstick
Linker Analogstick
+ Video-Verbesserung: (x%1$.2f) SGSR1
+ Video-Super-Resolution
+ Hardwarebeschleunigung nutzen, um die Bildklarheit zu verbessern
\ No newline at end of file
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index e9468b5613..748f0b02e2 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -212,4 +212,7 @@
\nΔοκιμάστε να απενεργοποιήσετε τη λειτουργία HDR, να αλλάξετε την ανάλυση ροής ή να αλλάξετε την ανάλυση οθόνης του υπολογιστή φιλοξενίας.
Λήξη Υποστήριξης NVIDA GameStream
Εάν ο υπολογιστής οικοδεσπότης χρησιμοποιεί το Sunshine, πλοηγηθείτε στην διαδικτυακή διεπαφή χρήστη του Sunshine για να εισάγετε το PIN.
+ Βελτίωση βίντεο: (x%1$.2f) Shader SGSR1
+ Super Resolution Βίντεο
+ Αξιοποιήστε την επιτάχυνση υλικού για να βελτιώσετε την ευκρίνεια της εικόνας
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index d1c5090127..752b4ef0a9 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -287,4 +287,7 @@
Ajustar la intensidad del ruido emulado
Amplifica o reduce la intensidad de la vibración de tu dispositivo
%
+ Mejora de video: (x%1$.2f) SGSR1
+ Súper resolución de vídeo
+ Aproveche la aceleración de hardware para mejorar la claridad de la imagen
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 6e80fac9e8..1a486ae091 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -285,4 +285,7 @@
Ajuster l\'intensité de la vibration émulée
Amplifie ou réduit l\'intensité de la vibration sur votre appareil
%
+ Amélioration vidéo : (x%1$.2f) SGSR1
+ Super Résolution Vidéo
+ Utiliser l\'accélération matérielle pour améliorer la clarté de l\'image
\ No newline at end of file
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index f937f3b4a9..b69c7b8651 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -259,4 +259,7 @@
(Tájkép)
Host feldolgozási késleltetés min/max/átlag: %1$.1f/%2$.1f/%3$.1f ms
A legalacsonyabb késleltetés előnyben részesítése
+ Videó javítása: (x%1$.2f) Shader SGSR1
+ Videó szuperfelbontás
+ Hardveres gyorsítás használata a kép tisztaságának javítása érdekében
\ No newline at end of file
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index d112010d10..8f71ebb2a0 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -211,4 +211,7 @@
Aktifkan mode pengamat Gambar-dalam-Gambar
Bahasa yang digunakan untuk Moonlight
Izinkan GFE mengubah setelan game untuk streaming yang optimal
+ Peningkatan Video: (x%1$.2f) Shader SGSR1
+ Super Resolusi Video
+ Manfaatkan akselerasi perangkat keras untuk meningkatkan kejelasan gambar
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 8913aea019..c436e2f6d0 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -279,4 +279,7 @@
Se il tuo PC host sta eseguendo Sunshine, vai nella pagina web locale di Sunshine ed inserisci il PIN.
Controlla sempre il mouse col touchpad
Latenza di codifica da parte dell\'host min/max/average: %1$.1f/%2$.1f/%3$.1f ms
+ Miglioramento video: (x%1$.2f) SGSR1
+ Super risoluzione video
+ Sfrutta l\'accelerazione hardware per migliorare la nitidezza dell\'immagine
\ No newline at end of file
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index ec17472f1f..0a99f76fab 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -152,4 +152,7 @@
הפעל תמיכה באקולייזר
הגדרות קלט
השתמש במסך מגע כמשטח מגע
+ שיפור וידאו: (x%1$.2f) Shader SGSR1
+ סופר-רזולוציה בווידאו
+ השתמש בהאצת חומרה כדי לשפר את בהירות התמונה
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 92d25d6d31..83be4daada 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -137,4 +137,7 @@
ネットワークテスト終了
詳細を表示
ネットワーク接続テスト中
+ ビデオ拡張: (x%1$.2f) Shader SGSR1
+ ビデオ超解像
+ ハードウェアアクセラレーションを活用して画像の鮮明さを向上させる
\ No newline at end of file
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 45d4ff9021..4e96ef14a6 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -278,4 +278,7 @@
연결된 컨트롤러 또는 기기의 Android 버전에서 컨트롤러 센서가 지원되지 않는 경우 장치에 내장된 모션 센서를 사용합니다.
\n참고: 이 옵션을 활성화하면 사용 중인 컨트롤러가 호스트에서 PlayStation 컨트롤러로 나타날 수 있습니다.
컨트롤러의 모션 센서 에뮬레이션 지원
+ 비디오 향상: (x%1$.2f) Shader SGSR1
+ 비디오 초해상도
+ 하드웨어 가속을 사용하여 이미지 선명도 향상
\ No newline at end of file
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
index 730c4ee42f..a799b59495 100644
--- a/app/src/main/res/values-nb-rNO/strings.xml
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -207,4 +207,7 @@
Systemspesifikk fullskjerm
Gjennomsnittlig nettverksforsinkelse: %1$d ms (variasjon: %2$d ms)
Videostrøm: %1$s %2$.2f BPS
+ Video-forbedring: (x%1$.2f) Shader SGSR1
+ Video Superoppløsning
+ Bruk maskinvareakselerasjon for å forbedre bildeklarheten
\ No newline at end of file
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index a5ae5211b6..0c79fbe436 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -223,4 +223,7 @@
Toevoegen aan Kanaal
Gebruik HEVC altijd (mogelijkheid tot crashes)
+ Video-verbetering: (x%1$.2f) Shader SGSR1
+ Video Superresolutie
+ Gebruik hardwareversnelling om de helderheid van het beeld te verbeteren
\ No newline at end of file
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 7d99219bf6..1e31629821 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -272,4 +272,7 @@
Automatycznie (zalecane)
Preferowany AV1 (eksperymentalny)
Wyświetl instrukcje, jak skonfigurować komputer do gier do streamowania
+ Ulepszenie wideo: (x%1$.2f) Shader SGSR1
+ Super rozdzielczość wideo
+ Wykorzystaj akcelerację sprzętową, aby poprawić klarowność obrazu
\ No newline at end of file
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index e129e3001b..d9379eb4c3 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -254,4 +254,7 @@
Stick analógico esquerdo
Usar um stick analógico para fazer scroll
Selecciona um stick analógico para fazer scroll quando a emulação de rato está ativada
+ Melhoria de vídeo: (x%1$.2f) Shader SGSR1
+ Super-resolução de vídeo
+ Aproveite a aceleração de hardware para melhorar a clareza da imagem
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index c707a52c10..674511a3bc 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -254,4 +254,7 @@
Stick analógico esquerdo
Usar um stick analógico para fazer scroll
Selecciona um stick analógico para fazer scroll quando a emulação de rato está ativada
+ Melhoria de vídeo: (x%1$.2f) SGSR1
+ Super-resolução de vídeo
+ Aproveite a aceleração de hardware para melhorar a clareza da imagem
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index b145f4d712..fadf645da6 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -178,4 +178,7 @@
Sunet Surround 5.1
Sunet Surround 7.1
Folosește HEVC mereu (se poate bloca)
+ Îmbunătățire video: (x%1$.2f) Shader SGSR1
+ Super rezoluție video
+ Utilizați accelerarea hardware pentru a îmbunătăți claritatea imaginii
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 93a2736023..e5c128757b 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -254,4 +254,7 @@
Скорость вывода/отрисовки кадра
Сбалансированно с лимитом FPS
480p
+ Улучшение видео: (x%1$.2f) Shader SGSR1
+ Супер-разрешение видео
+ Использование аппаратного ускорения для улучшения четкости изображения
\ No newline at end of file
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 175af18c89..fbe9e177dd 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -272,4 +272,7 @@
Höger analogspak
%
Använd en analog spak för att bläddra
+ Videoförbättring: (x%1$.2f) Shader SGSR1
+ Video-superupplösning
+ Använd hårdvaruacceleration för att förbättra bildskärpan
\ No newline at end of file
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 1750e76f1b..45fb67ee41 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -173,4 +173,7 @@
Yerel Xbox oyun kumandası desteği mevcut olsa bile desteklenen tüm oyun kumandaları için Moonlight\'ın USB sürücüsünü kullan
Oyun kumandaları ve ekran kontrolleri için A/B ve X/Y yüz düğmelerini değiştirir
Bu fare hızlandırmanın uzak masaüstü kullanımı için daha doğal davranmasını sağlayabilir, ancak birçok oyunla uyumsuzdur.
+ Video İyileştirme: (x%1$.2f) Shader SGSR1
+ Video Süper Çözünürlük
+ Görüntü netliğini artırmak için donanım hızlandırmasından yararlanın
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 70ae19ec09..afe84e5f36 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -283,4 +283,7 @@
Тип геймпада може бути змінений через емуляцію датчика руху
Закриття сервісів NVIDIA GameStream
Затримка обробки хостом мін./макс./середня: %1$.1f/%2$.1f/%3$.1f мс
+ Покращення відео: (x%1$.2f) Shader SGSR1
+ Супер-роздільна здатність відео
+ Використовуйте апаратне прискорення для покращення чіткості зображення
\ No newline at end of file
diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
index a85727d7fc..bf80206684 100644
--- a/app/src/main/res/values-vi/strings.xml
+++ b/app/src/main/res/values-vi/strings.xml
@@ -233,4 +233,7 @@
Hỗ trợ bộ cân bằng của hệ thống
Cho phép hiệu ứng âm thanh hoạt động khi stream, nhưng có thể tăng độ trễ âm thanh
(Dọc)
+ Cải thiện video: (x%1$.2f) Shader SGSR1
+ Siêu độ phân giải video
+ Tận dụng tăng tốc phần cứng để cải thiện độ rõ nét của hình ảnh
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index c03cd575fa..917fbb45f1 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -284,4 +284,7 @@
在鼠标模式下选择用哪个摇杆控制鼠标移动
增强或减弱设备的震动强度
默认 (两个摇杆都控制鼠标)
+ 视频增强: (x%1$.2f) Shader SGSR1
+ 视频超分辨率
+ 利用硬件加速提高图像清晰度
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 7f3a3429d8..ebc0d5fee7 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -276,4 +276,7 @@
如果您連線的手把或 Android 版本不支援手把感測器,請使用裝置內建的動態感測器。
\n注意:啟用此選項可能會導致您的手把在主機被辨識為 PlayStation 控制器。
模擬手把動態感測器支援
+ 視訊增強: (x%1$.2f) Shader SGSR1
+ 視訊超解析度
+ 利用硬體加速提高圖像清晰度
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 9cc2215aaa..03e2870c06 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -107,6 +107,7 @@
Poor connection to PC
Video stream: %1$s %2$.2f FPS
Decoder: %1$s
+ Video Enhancement: (x%1$.2f) Shader SGSR1
Incoming frame rate from network: %1$.2f FPS
Rendering frame rate: %1$.2f FPS
Host processing latency min/max/average: %1$.1f/%2$.1f/%3$.1f ms
@@ -295,6 +296,9 @@
Balanced with FPS limit
Prefer smoothest video (may significantly increase latency)
+ Video Super Resolution
+ Leverage hardware acceleration to improve picture clarity
+
Use an analog stick to scroll
Select an analog stick to scroll when in mouse emulation mode
None (both sticks move the mouse)
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 5b09082b35..8b6ac7a94d 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -36,6 +36,11 @@
android:entryValues="@array/video_frame_pacing_values"
android:summary="@string/summary_frame_pacing"
android:defaultValue="latency" />
+