Feat[renderer]: Add LTW renderer (#6477)

* Feat[launcher]: start introducing the Tinywrapper renderer

* Feat[launcher]: add renderer, add compatibility checks

* Chore[ltw]: update to latest

* Style[code]: code cosmetic changes

* Legal[ltw]: make LTW optional

* Fix[ltw]: fix actions, add forgotten file

* Workflow[ltw]: disallow forks, allow building on error
This commit is contained in:
Maksim Belov 2025-01-14 00:23:49 +03:00 committed by GitHub
parent ed89b44d3b
commit b28fc4a1f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 140 additions and 485 deletions

View File

@ -26,6 +26,18 @@ jobs:
distribution: 'temurin'
java-version: '8'
- name: Get LTW
uses: dawidd6/action-download-artifact@v2
continue-on-error: true
with:
github_token: ${{secrets.LTW_CLONER_SECRET}}
repo: PojavLauncherTeam/BigTinyWrapper
workflow: android.yml
workflow_conclusion: success
name: output-aar
path: app_pojavlauncher/libs
allow_forks: false
- name: Get JRE 8
uses: dawidd6/action-download-artifact@v2
with:

View File

@ -227,5 +227,5 @@ dependencies {
// implementation 'net.sourceforge.streamsupport:streamsupport-cfuture:1.7.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}

View File

@ -22,6 +22,11 @@ import android.database.Cursor;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.net.Uri;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.GLES30;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
@ -68,6 +73,7 @@ import net.kdt.pojavlaunch.utils.DownloadUtils;
import net.kdt.pojavlaunch.utils.FileUtils;
import net.kdt.pojavlaunch.utils.JREUtils;
import net.kdt.pojavlaunch.utils.JSONUtils;
import net.kdt.pojavlaunch.utils.MCOptionUtils;
import net.kdt.pojavlaunch.utils.OldVersionsUtils;
import net.kdt.pojavlaunch.value.DependentLibrary;
import net.kdt.pojavlaunch.value.MinecraftAccount;
@ -178,6 +184,97 @@ public final class Tools {
NATIVE_LIB_DIR = ctx.getApplicationInfo().nativeLibraryDir;
}
/**
* Optimization mods based on Sodium can mitigate the render distance issue. Check if Sodium
* or its derivative is currently installed to skip the render distance check.
* @param gameDir current game directory
* @return whether sodium or a sodium-based mod is installed
*/
private static boolean hasSodium(File gameDir) {
File modsDir = new File(gameDir, "mods");
File[] mods = modsDir.listFiles(file -> file.isFile() && file.getName().endsWith(".jar"));
if(mods == null) return false;
for(File file : mods) {
String name = file.getName();
if(name.contains("sodium") ||
name.contains("embeddium") ||
name.contains("rubidium")) return true;
}
return false;
}
/**
* Initialize OpenGL and do checks to see if the GPU of the device is affected by the render
* distance issue.
* Currently only checks whether the user has an Adreno GPU capable of OpenGL ES 3
* and surfaceless rendering installed.
* This issue is caused by a very severe limit on the amount of GL buffer names that could be allocated
* by the Adreno properietary GLES driver.
* @return whether the GPU is affected by the Large Thin Wrapper render distance issue on vanilla
*/
private static boolean affectedByRenderDistanceIssue() {
EGLDisplay eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
if(eglDisplay == EGL14.EGL_NO_DISPLAY || !EGL14.eglInitialize(eglDisplay, null, 0, null, 0)) return false;
int[] egl_attributes = new int[] {
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 24,
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_NONE
};
EGLConfig[] config = new EGLConfig[1];
int[] num_configs = new int[]{0};
if(!EGL14.eglChooseConfig(eglDisplay, egl_attributes, 0, config, 0, 1, num_configs, 0) || num_configs[0] == 0) {
EGL14.eglTerminate(eglDisplay);
Log.e("CheckVendor", "Failed to choose an EGL config");
return false;
}
int[] egl_context_attributes = new int[] { EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE };
EGLContext context = EGL14.eglCreateContext(eglDisplay, config[0], EGL14.EGL_NO_CONTEXT, egl_context_attributes, 0);
if(context == EGL14.EGL_NO_CONTEXT) {
Log.e("CheckVendor", "Failed to create a context");
EGL14.eglTerminate(eglDisplay);
return false;
}
if(!EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, context)) {
Log.e("CheckVendor", "Failed to make context current");
EGL14.eglDestroyContext(eglDisplay, context);
EGL14.eglTerminate(eglDisplay);
}
boolean is_adreno = GLES30.glGetString(GLES30.GL_VENDOR).equals("Qualcomm") &&
GLES30.glGetString(GLES30.GL_RENDERER).contains("Adreno");
Log.e("CheckVendor", "Running Adreno graphics: "+is_adreno);
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
EGL14.eglDestroyContext(eglDisplay, context);
EGL14.eglTerminate(eglDisplay);
return is_adreno;
}
private static boolean checkRenderDistance(File gamedir) {
if(!"opengles3_ltw".equals(Tools.LOCAL_RENDERER)) return false;
if(!affectedByRenderDistanceIssue()) return false;
if(hasSodium(gamedir)) return false;
int renderDistance;
try {
MCOptionUtils.load();
String renderDistanceString = MCOptionUtils.get("renderDistance");
renderDistance = Integer.parseInt(renderDistanceString);
}catch (Exception e) {
Log.e("Tools", "Failed to check render distance", e);
renderDistance = 12; // Assume Minecraft's default render distance
}
// 7 is the render distance "magic number" above which MC creates too many buffers
// for Adreno's OpenGL ES implementation
return renderDistance > 7;
}
public static void launchMinecraft(final AppCompatActivity activity, MinecraftAccount minecraftAccount,
MinecraftProfile minecraftProfile, String versionId, int versionJavaRequirement) throws Throwable {
int freeDeviceMemory = getFreeDeviceMemory(activity);
@ -203,10 +300,27 @@ public final class Tools {
// to start after the activity is shown again
}
}
Runtime runtime = MultiRTUtils.forceReread(Tools.pickRuntime(minecraftProfile, versionJavaRequirement));
JMinecraftVersionList.Version versionInfo = Tools.getVersionInfo(versionId);
LauncherProfiles.load();
File gamedir = Tools.getGameDirPath(minecraftProfile);
if(checkRenderDistance(gamedir)) {
LifecycleAwareAlertDialog.DialogCreator dialogCreator = ((alertDialog, dialogBuilder) ->
dialogBuilder.setMessage(activity.getString(R.string.ltw_render_distance_warning_msg))
.setPositiveButton(android.R.string.ok, (d, w)->{}));
if(LifecycleAwareAlertDialog.haltOnDialog(activity.getLifecycle(), activity, dialogCreator)) {
return;
}
// If the code goes here, it means that the user clicked "OK". Fix the render distance.
try {
MCOptionUtils.set("renderDistance", "7");
MCOptionUtils.save();
}catch (Exception e) {
Log.e("Tools", "Failed to fix render distance setting", e);
}
}
Runtime runtime = MultiRTUtils.forceReread(Tools.pickRuntime(minecraftProfile, versionJavaRequirement));
JMinecraftVersionList.Version versionInfo = Tools.getVersionInfo(versionId);
// Pre-process specific files
@ -1271,12 +1385,16 @@ public final class Tools {
boolean deviceHasVulkan = checkVulkanSupport(context.getPackageManager());
// Currently, only 32-bit x86 does not have the Zink binary
boolean deviceHasZinkBinary = !(Architecture.is32BitsDevice() && Architecture.isx86Device());
boolean deviceHasOpenGLES3 = JREUtils.getDetectedVersion() >= 3;
// LTW is an optional proprietary dependency
boolean appHasLtw = new File(Tools.NATIVE_LIB_DIR, "libltw.so").exists();
List<String> rendererIds = new ArrayList<>(defaultRenderers.length);
List<String> rendererNames = new ArrayList<>(defaultRendererNames.length);
for(int i = 0; i < defaultRenderers.length; i++) {
String rendererId = defaultRenderers[i];
if(rendererId.contains("vulkan") && !deviceHasVulkan) continue;
if(rendererId.contains("zink") && !deviceHasZinkBinary) continue;
if(rendererId.contains("ltw") && (!deviceHasOpenGLES3 || !appHasLtw)) continue;
rendererIds.add(rendererId);
rendererNames.add(defaultRendererNames[i]);
}

View File

@ -220,9 +220,9 @@ public class JREUtils {
if(LOCAL_RENDERER != null) {
envMap.put("POJAV_RENDERER", LOCAL_RENDERER);
if(LOCAL_RENDERER.equals("opengles3_desktopgl_angle_vulkan")) {
if(LOCAL_RENDERER.equals("opengles3_ltw")) {
envMap.put("LIBGL_ES", "3");
envMap.put("POJAVEXEC_EGL","libEGL_angle.so"); // Use ANGLE EGL
envMap.put("POJAVEXEC_EGL","libltw.so"); // Use ANGLE EGL
}
}
if(LauncherPreferences.PREF_BIG_CORE_AFFINITY) envMap.put("POJAV_BIG_CORE_AFFINITY", "1");
@ -464,7 +464,7 @@ public class JREUtils {
case "opengles3":
renderLibrary = "libgl4es_114.so"; break;
case "vulkan_zink": renderLibrary = "libOSMesa.so"; break;
case "opengles3_desktopgl_angle_vulkan" : renderLibrary = "libtinywrapper.so"; break;
case "opengles3_ltw" : renderLibrary = "libltw.so"; break;
default:
Log.w("RENDER_LIBRARY", "No renderer selected, defaulting to opengles2");
renderLibrary = "libgl4es_114.so";

View File

@ -8,18 +8,6 @@ HERE_PATH := $(LOCAL_PATH)
LOCAL_PATH := $(HERE_PATH)
include $(CLEAR_VARS)
LOCAL_MODULE := angle_gles2
LOCAL_SRC_FILES := tinywrapper/angle-gles/$(TARGET_ARCH_ABI)/libGLESv2_angle.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := tinywrapper
LOCAL_SHARED_LIBRARIES := angle_gles2
LOCAL_SRC_FILES := tinywrapper/main.c tinywrapper/string_utils.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)/tinywrapper
include $(BUILD_SHARED_LIBRARY)
$(call import-module,prefab/bytehook)
LOCAL_PATH := $(HERE_PATH)

View File

@ -1,201 +0,0 @@
//#import <Foundation/Foundation.h>
#include <stdio.h>
#include <dlfcn.h>
#include "GL/gl.h"
#include "GLES3/gl32.h"
#include "string_utils.h"
#define LOOKUP_FUNC(func) \
if (!gles_##func) { \
gles_##func = dlsym(RTLD_NEXT, #func); \
} if (!gles_##func) { \
gles_##func = dlsym(RTLD_DEFAULT, #func); \
}
int proxy_width, proxy_height, proxy_intformat, maxTextureSize;
void glBindFragDataLocationEXT(GLuint program, GLuint colorNumber, const char * name);
void(*gles_glGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params);
void(*gles_glShaderSource)(GLuint shader, GLsizei count, const GLchar * const *string, const GLint *length);
void(*gles_glTexImage2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *data);
void glBindFragDataLocation(GLuint program, GLuint colorNumber, const char * name) {
glBindFragDataLocationEXT(program, colorNumber, name);
}
void glClearDepth(GLdouble depth) {
glClearDepthf(depth);
}
void *glMapBuffer(GLenum target, GLenum access) {
// Use: GL_EXT_map_buffer_range
GLenum access_range;
GLint length;
switch (target) {
// GL 4.2
case GL_ATOMIC_COUNTER_BUFFER:
// GL 4.3
case GL_DISPATCH_INDIRECT_BUFFER:
case GL_SHADER_STORAGE_BUFFER :
// GL 4.4
case GL_QUERY_BUFFER:
printf("ERROR: glMapBuffer unsupported target=0x%x", target);
break; // not supported for now
case GL_DRAW_INDIRECT_BUFFER:
case GL_TEXTURE_BUFFER:
printf("ERROR: glMapBuffer unimplemented target=0x%x", target);
break;
}
switch (access) {
case GL_READ_ONLY:
access_range = GL_MAP_READ_BIT;
break;
case GL_WRITE_ONLY:
access_range = GL_MAP_WRITE_BIT;
break;
case GL_READ_WRITE:
access_range = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT;
break;
}
glGetBufferParameteriv(target, GL_BUFFER_SIZE, &length);
return glMapBufferRange(target, 0, length, access_range);
}
void glShaderSource(GLuint shader, GLsizei count, const GLchar * const *string, const GLint *length) {
LOOKUP_FUNC(glShaderSource)
// DBG(printf("glShaderSource(%d, %d, %p, %p)\n", shader, count, string, length);)
char *source = NULL;
char *converted;
// get the size of the shader sources and than concatenate in a single string
int l = 0;
for (int i=0; i<count; i++) l+=(length && length[i] >= 0)?length[i]:strlen(string[i]);
if (source) free(source);
source = calloc(1, l+1);
if(length) {
for (int i=0; i<count; i++) {
if(length[i] >= 0)
strncat(source, string[i], length[i]);
else
strcat(source, string[i]);
}
} else {
for (int i=0; i<count; i++)
strcat(source, string[i]);
}
char *source2 = strchr(source, '#');
if (!source2) {
source2 = source;
}
// are there #version?
if (!strncmp(source2, "#version ", 9)) {
converted = strdup(source2);
if (converted[9] == '1') {
if (converted[10] - '0' < 2) {
// 100, 110 -> 120
converted[10] = '2';
} else if (converted[10] - '0' < 6) {
// 130, 140, 150 -> 330
converted[9] = converted[10] = '3';
}
}
// remove "core", is it safe?
if (!strncmp(&converted[13], "core", 4)) {
strncpy(&converted[13], "\n//c", 4);
}
} else {
converted = calloc(1, strlen(source) + 13);
strcpy(converted, "#version 120\n");
strcpy(&converted[13], strdup(source));
}
int convertedLen = strlen(converted);
#ifdef __APPLE__
// patch OptiFine 1.17.x
if (FindString(converted, "\nuniform mat4 textureMatrix = mat4(1.0);")) {
InplaceReplace(converted, &convertedLen, "\nuniform mat4 textureMatrix = mat4(1.0);", "\n#define textureMatrix mat4(1.0)");
}
#endif
// some needed exts
const char* extensions =
"#extension GL_EXT_blend_func_extended : enable\n"
// For OptiFine (see patch above)
"#extension GL_EXT_shader_non_constant_global_initializers : enable\n";
converted = InplaceInsert(GetLine(converted, 1), extensions, converted, &convertedLen);
gles_glShaderSource(shader, 1, (const GLchar * const*)((converted)?(&converted):(&source)), NULL);
free(source);
free(converted);
}
int isProxyTexture(GLenum target) {
switch (target) {
case GL_PROXY_TEXTURE_1D:
case GL_PROXY_TEXTURE_2D:
case GL_PROXY_TEXTURE_3D:
case GL_PROXY_TEXTURE_RECTANGLE_ARB:
return 1;
}
return 0;
}
static int inline nlevel(int size, int level) {
if(size) {
size>>=level;
if(!size) size=1;
}
return size;
}
void glGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) {
LOOKUP_FUNC(glGetTexLevelParameteriv)
// NSLog("glGetTexLevelParameteriv(%x, %d, %x, %p)", target, level, pname, params);
if (isProxyTexture(target)) {
switch (pname) {
case GL_TEXTURE_WIDTH:
(*params) = nlevel(proxy_width,level);
break;
case GL_TEXTURE_HEIGHT:
(*params) = nlevel(proxy_height,level);
break;
case GL_TEXTURE_INTERNAL_FORMAT:
(*params) = proxy_intformat;
break;
}
} else {
gles_glGetTexLevelParameteriv(target, level, pname, params);
}
}
void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *data) {
LOOKUP_FUNC(glTexImage2D)
if (isProxyTexture(target)) {
if (!maxTextureSize) {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
// maxTextureSize = 16384;
// NSLog(@"Maximum texture size: %d", maxTextureSize);
}
proxy_width = ((width<<level)>maxTextureSize)?0:width;
proxy_height = ((height<<level)>maxTextureSize)?0:height;
proxy_intformat = internalformat;
// swizzle_internalformat((GLenum *) &internalformat, format, type);
} else {
gles_glTexImage2D(target, level, internalformat, width, height, border, format, type, data);
}
}

View File

@ -1,234 +0,0 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "string_utils.h"
const char* AllSeparators = " \t\n\r.,;()[]{}-<>+*/%&\\\"'^$=!:?";
char* ResizeIfNeeded(char* pBuffer, int *size, int addsize);
char* InplaceReplace(char* pBuffer, int* size, const char* S, const char* D)
{
int lS = strlen(S), lD = strlen(D);
pBuffer = ResizeIfNeeded(pBuffer, size, (lD-lS)*CountString(pBuffer, S));
char* p = pBuffer;
while((p = strstr(p, S)))
{
// found an occurence of S
// check if good to replace, strchr also found '\0' :)
if(strchr(AllSeparators, p[lS])!=NULL && (p==pBuffer || strchr(AllSeparators, p[-1])!=NULL)) {
// move out rest of string
memmove(p+lD, p+lS, strlen(p)-lS+1);
// replace
memcpy(p, D, strlen(D));
// next
p+=lD;
} else p+=lS;
}
return pBuffer;
}
char* InplaceInsert(char* pBuffer, const char* S, char* master, int* size)
{
char* m = ResizeIfNeeded(master, size, strlen(S));
if(m!=master) {
pBuffer += (m-master);
master = m;
}
char* p = pBuffer;
int lS = strlen(S), ll = strlen(pBuffer);
memmove(p+lS, p, ll+1);
memcpy(p, S, lS);
return master;
}
char* GetLine(char* pBuffer, int num)
{
char *p = pBuffer;
while(num-- && (p=strstr(p, "\n"))) p+=strlen("\n");
return (p)?p:pBuffer;
}
int CountLine(const char* pBuffer)
{
int n=0;
const char* p = pBuffer;
while((p=strstr(p, "\n"))) {
p+=strlen("\n");
n++;
}
return n;
}
int GetLineFor(const char* pBuffer, const char* S)
{
int n=0;
const char* p = pBuffer;
const char* end = FindString(pBuffer, S);
if(!end)
return 0;
while((p=strstr(p, "\n"))) {
p+=strlen("\n");
n++;
if(p>=end)
return n;
}
return n;
}
int CountString(const char* pBuffer, const char* S)
{
const char* p = pBuffer;
int lS = strlen(S);
int n = 0;
while((p = strstr(p, S)))
{
// found an occurence of S
// check if good to count, strchr also found '\0' :)
if(strchr(AllSeparators, p[lS])!=NULL && (p==pBuffer || strchr(AllSeparators, p[-1])!=NULL))
n++;
p+=lS;
}
return n;
}
const char* FindString(const char* pBuffer, const char* S)
{
const char* p = pBuffer;
int lS = strlen(S);
while((p = strstr(p, S)))
{
// found an occurence of S
// check if good to count, strchr also found '\0' :)
if(strchr(AllSeparators, p[lS])!=NULL && (p==pBuffer || strchr(AllSeparators, p[-1])!=NULL))
return p;
p+=lS;
}
return NULL;
}
char* FindStringNC(char* pBuffer, const char* S)
{
char* p = pBuffer;
int lS = strlen(S);
while((p = strstr(p, S)))
{
// found an occurence of S
// check if good to count, strchr also found '\0' :)
if(strchr(AllSeparators, p[lS])!=NULL && (p==pBuffer || strchr(AllSeparators, p[-1])!=NULL))
return p;
p+=lS;
}
return NULL;
}
char* ResizeIfNeeded(char* pBuffer, int *size, int addsize) {
char* p = pBuffer;
int newsize = strlen(pBuffer)+addsize+1;
if (newsize>*size) {
newsize += 100;
p = (char*)realloc(pBuffer, newsize);
*size=newsize;
}
return p;
}
char* Append(char* pBuffer, int* size, const char* S) {
char* p =pBuffer;
p = ResizeIfNeeded(pBuffer, size, strlen(S));
strcat(p, S);
return p;
}
int isBlank(char c) {
switch(c) {
case ' ':
case '\t':
case '\n':
case '\r':
case ':':
case ',':
case ';':
case '/':
return 1;
default:
return 0;
}
}
char* StrNext(char *pBuffer, const char* S) {
if(!pBuffer) return NULL;
char *p = strstr(pBuffer, S);
return (p)?p:(p+strlen(S));
}
char* NextStr(char* pBuffer) {
if(!pBuffer) return NULL;
while(isBlank(*pBuffer))
++pBuffer;
return pBuffer;
}
char* NextBlank(char* pBuffer) {
if(!pBuffer) return NULL;
while(!isBlank(*pBuffer))
++pBuffer;
return pBuffer;
}
char* NextLine(char* pBuffer) {
if(!pBuffer) return NULL;
while(*pBuffer && *pBuffer!='\n')
++pBuffer;
return pBuffer;
}
const char* GetNextStr(char* pBuffer) {
static char buff[100] = {0};
buff[0] = '\0';
if(!pBuffer) return NULL;
char* p1 = NextStr(pBuffer);
if(!p1) return buff;
char* p2 = NextBlank(p1);
if(!p2) return buff;
int i=0;
while(p1!=p2 && i<99)
buff[i++] = *(p1++);
buff[i] = '\0';
return buff;
}
int CountStringSimple(char* pBuffer, const char* S)
{
char* p = pBuffer;
int lS = strlen(S);
int n = 0;
while((p = strstr(p, S)))
{
// found an occurence of S
n++;
p+=lS;
}
return n;
}
char* InplaceReplaceSimple(char* pBuffer, int* size, const char* S, const char* D)
{
int lS = strlen(S), lD = strlen(D);
pBuffer = ResizeIfNeeded(pBuffer, size, (lD-lS)*CountStringSimple(pBuffer, S));
char* p = pBuffer;
while((p = strstr(p, S)))
{
// found an occurence of S
// move out rest of string
memmove(p+lD, p+lS, strlen(p)-lS+1);
// replace
memcpy(p, D, strlen(D));
// next
p+=lD;
}
return pBuffer;
}

View File

@ -1,29 +0,0 @@
#ifndef _GL4ES_STRING_UTILS_H_
#define _GL4ES_STRING_UTILS_H_
extern const char* AllSeparators;
const char* FindString(const char* pBuffer, const char* S);
char* FindStringNC(char* pBuffer, const char* S);
int CountString(const char* pBuffer, const char* S);
char* ResizeIfNeeded(char* pBuffer, int *size, int addsize);
char* InplaceReplace(char* pBuffer, int* size, const char* S, const char* D);
char* Append(char* pBuffer, int* size, const char* S);
char* InplaceInsert(char* pBuffer, const char* S, char* master, int* size);
char* GetLine(char* pBuffer, int num);
int CountLine(const char* pBuffer);
int GetLineFor(const char* pBuffer, const char* S); // get the line number for 1st occurent of S in pBuffer
char* StrNext(char *pBuffer, const char* S); // mostly as strstr, but go after the substring if found
//"blank" (space, tab, cr, lf,":", ",", ";", ".", "/")
char* NextStr(char* pBuffer); // go to next non "blank"
char* NextBlank(char* pBuffer); // go to next "blank"
char* NextLine(char* pBuffer); // go to next new line (crlf not included)
const char* GetNextStr(char* pBuffer); // get a (static) copy of next str (until next separator), can be a simple number or separator also
// those function don't try to be smart with separators...
int CountStringSimple(char* pBuffer, const char* S);
char* InplaceReplaceSimple(char* pBuffer, int* size, const char* S, const char* D);
#endif // _GL4ES_STRING_UTILS_H_

View File

@ -3,7 +3,7 @@
<string-array name="renderer">
<item name="1">@string/mcl_setting_renderer_gles2_4</item>
<item name="2">@string/mcl_setting_renderer_vulkan_zink</item>
<item name="3">@string/mcl_setting_renderer_angle</item>
<item name="3">@string/mcl_setting_renderer_ltw</item>
</string-array>
<string-array name="menu_customcontrol">
@ -36,7 +36,7 @@
<string-array name="renderer_values">
<item>opengles2</item> <!-- gl4es_extra 1.1.4 with OpenGL ES 2/"3" -->
<item>vulkan_zink</item> <!-- virglrenderer with OpenGL ES 3 -->
<item>opengles3_desktopgl_angle_vulkan</item>
<item>opengles3_ltw</item>
</string-array>
<string-array name="download_source_names">
<item>@string/global_default</item>

View File

@ -66,7 +66,7 @@
<string name="mcl_setting_category_renderer">Renderer</string>
<string name="mcl_setting_renderer_gles2_4">Holy GL4ES - (all versions, fast)</string>
<string name="mcl_setting_renderer_vulkan_zink">Zink (Vulkan) - (all versions, mid)</string>
<string name="mcl_setting_renderer_angle">ANGLE (Vulkan) - (1.17+ only, mid)</string>
<string name="mcl_setting_renderer_ltw">LTW (OpenGL ES 3) - 1.17+ only</string>
<string name="mcl_setting_veroption_release">Release</string>
<string name="mcl_setting_veroption_snapshot">Snapshot</string>
<string name="mcl_setting_veroption_oldalpha">Old-alpha</string>
@ -418,6 +418,7 @@
<string name="preference_remap_controller_title">Change controller key bindings</string>
<string name="preference_remap_controller_description">Allows you to modify the keyboard keys bound to each controller button</string>
<string name="mcl_button_discord">Discord</string>
<string name="ltw_render_distance_warning_msg">Your GPU is not capable of rendering above 7 render distance without Sodium or other similar mods. The render distance will be automatically reduced when you click "OK".</string>
<string name="mcl_button_open_directory">Open game directory</string>
<string name="discord_invite" translatable="false">https://discord.com/invite/aenk3EUvER</string>
<string name="local_login_bad_username_title">Unsuitable username</string>