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" /> +