New setup wizard - groundwork for angle on Mali devices

This commit is contained in:
izzy2lost
2025-12-08 13:40:22 -05:00
parent 1492d74efe
commit 2ccaa85b08
17 changed files with 774 additions and 296 deletions
+1
View File
@@ -115,6 +115,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.documentfile:documentfile:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.viewpager2:viewpager2:1.1.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.activity:activity:1.12.1'
implementation 'androidx.core:core-ktx:1.15.0'
+35 -2
View File
@@ -41,6 +41,9 @@ namespace AndroidDeviceDetection
std::string board = GetSystemProperty("ro.product.board");
std::string platform = GetSystemProperty("ro.board.platform");
Console.WriteLn("Device Detection: hardware='%s', board='%s', platform='%s'",
hardware.c_str(), board.c_str(), platform.c_str());
// Convert to lowercase for comparison
auto toLower = [](std::string str) {
for (char& c : str) c = std::tolower(c);
@@ -65,6 +68,9 @@ namespace AndroidDeviceDetection
std::string board = GetSystemProperty("ro.product.board");
std::string platform = GetSystemProperty("ro.board.platform");
Console.WriteLn("Device Detection: hardware='%s', board='%s', platform='%s'",
hardware.c_str(), board.c_str(), platform.c_str());
auto toLower = [](std::string str) {
for (char& c : str) c = std::tolower(c);
return str;
@@ -99,13 +105,40 @@ namespace AndroidDeviceDetection
// Check for other vendors via hardware string
std::string hardware = GetSystemProperty("ro.hardware");
if (hardware.find("exynos") != std::string::npos)
std::string manufacturer = GetSystemProperty("ro.product.manufacturer");
auto toLower = [](std::string str) {
for (char& c : str) c = std::tolower(c);
return str;
};
hardware = toLower(hardware);
manufacturer = toLower(manufacturer);
// Samsung Exynos devices (Mali GPU)
if (hardware.find("exynos") != std::string::npos ||
hardware.find("universal") != std::string::npos ||
(manufacturer.find("samsung") != std::string::npos && hardware.find("samsungexynos") != std::string::npos))
{
Console.WriteLn("Detected Samsung Exynos (Mali GPU)");
return GPUVendor::ARM;
}
Console.WriteLn("Unknown GPU vendor, hardware: %s", hardware.c_str());
// Kirin devices (Mali GPU)
if (hardware.find("kirin") != std::string::npos || hardware.find("hi") == 0)
{
Console.WriteLn("Detected HiSilicon Kirin (Mali GPU)");
return GPUVendor::ARM;
}
// Rockchip devices (Mali GPU)
if (hardware.find("rk") == 0 || hardware.find("rockchip") != std::string::npos)
{
Console.WriteLn("Detected Rockchip (Mali GPU)");
return GPUVendor::ARM;
}
Console.WriteLn("Unknown GPU vendor, hardware: %s, manufacturer: %s", hardware.c_str(), manufacturer.c_str());
return GPUVendor::Unknown;
}
}
@@ -4,6 +4,27 @@
#include "GS/Renderers/OpenGL/GLContextEGL.h"
#include "common/Console.h"
#ifdef __ANDROID__
#include "AndroidDeviceDetection.h"
#endif
#include <EGL/eglext.h>
// NDK headers may lack ANGLE platform defines; provide fallbacks so we can request ANGLE.
#ifndef EGL_PLATFORM_ANGLE_ANGLE
#define EGL_PLATFORM_ANGLE_ANGLE 0x3202
#endif
#ifndef EGL_PLATFORM_ANGLE_TYPE_ANGLE
#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203
#endif
#ifndef EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE
#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x320F
#endif
#ifndef EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE 0x3209
#endif
#ifndef EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE
#define EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE 0x320A
#endif
#include <algorithm>
#include <cstring>
@@ -43,12 +64,36 @@ bool GLContextEGL::Initialize(const Version* versions_to_try, size_t num_version
return false;
int egl_major, egl_minor;
retry_initialize:
if (!eglInitialize(m_display, &egl_major, &egl_minor))
{
#ifdef __ANDROID__
if (m_tried_angle_display && m_used_angle_display)
{
const EGLint error = eglGetError();
Console.Warning("ANGLE: eglInitialize() failed with error 0x%x, retrying with native Mali driver", error);
m_used_angle_display = false;
m_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(m_wi.display_connection));
if (m_display != EGL_NO_DISPLAY)
goto retry_initialize;
}
#endif
Console.Error("eglInitialize() failed: %d", eglGetError());
return false;
}
Console.WriteLn("EGL Version: %d.%d", egl_major, egl_minor);
#ifdef __ANDROID__
if (m_used_angle_display)
{
Console.WriteLn("ANGLE: Successfully initialized ANGLE EGL display");
Console.WriteLn("ANGLE: OpenGL renderer will use ANGLE (GL ES -> Vulkan translation)");
}
else if (m_tried_angle_display)
{
Console.WriteLn("EGL: Using native Mali OpenGL ES driver (ANGLE not available)");
}
#endif
const char* extensions = eglQueryString(m_display, EGL_EXTENSIONS);
if (extensions)
@@ -67,7 +112,62 @@ bool GLContextEGL::Initialize(const Version* versions_to_try, size_t num_version
bool GLContextEGL::SetDisplay()
{
m_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(m_wi.display_connection));
#ifdef __ANDROID__
const AndroidDeviceDetection::GPUVendor vendor = AndroidDeviceDetection::DetectGPUVendor();
const bool prefer_angle = (vendor == AndroidDeviceDetection::GPUVendor::ARM);
m_tried_angle_display = prefer_angle;
Console.WriteLn("EGL: GPU Vendor detected: %d (ARM=%d), prefer_angle=%d",
static_cast<int>(vendor), static_cast<int>(AndroidDeviceDetection::GPUVendor::ARM), prefer_angle);
if (prefer_angle)
{
Console.WriteLn("ANGLE: Mali GPU detected, attempting to use ANGLE with Vulkan backend");
auto get_platform_display = reinterpret_cast<PFNEGLGETPLATFORMDISPLAYEXTPROC>(
eglGetProcAddress("eglGetPlatformDisplayEXT"));
if (get_platform_display)
{
Console.WriteLn("ANGLE: eglGetPlatformDisplayEXT found, requesting ANGLE display");
const EGLint attribs[] = {
EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE,
EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
EGL_NONE,
};
m_display = get_platform_display(EGL_PLATFORM_ANGLE_ANGLE, EGL_DEFAULT_DISPLAY, attribs);
if (m_display != EGL_NO_DISPLAY)
{
Console.WriteLn("ANGLE: Successfully created ANGLE display with Vulkan backend for Mali GPU");
Console.WriteLn("ANGLE: OpenGL ES calls will be translated to Vulkan");
m_used_angle_display = true;
}
else
{
const EGLint error = eglGetError();
Console.Warning("ANGLE: eglGetPlatformDisplayEXT failed with error 0x%x, falling back to default EGL", error);
}
}
else
{
Console.Warning("ANGLE: eglGetPlatformDisplayEXT not available (ANGLE libraries may not be loaded)");
Console.Warning("ANGLE: Falling back to native Mali OpenGL ES driver");
}
}
else
{
Console.WriteLn("EGL: Non-Mali GPU detected, using native OpenGL ES driver");
}
#endif
if (!m_display)
{
Console.WriteLn("EGL: Using default eglGetDisplay()");
m_display = eglGetDisplay(static_cast<EGLNativeDisplayType>(m_wi.display_connection));
}
if (!m_display)
{
Console.Error("eglGetDisplay() failed: %d", eglGetError());
@@ -48,4 +48,8 @@ protected:
EGLConfig m_config = {};
bool m_supports_surfaceless = false;
#ifdef __ANDROID__
bool m_tried_angle_display = false;
bool m_used_angle_display = false;
#endif
};
@@ -648,6 +648,10 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo)
const std::string vendor_lower = to_lower(vendor_str);
const std::string renderer_lower = to_lower(renderer_str);
// Detect Mali GPU
bool vendor_id_arm_mali = (vendor_lower.find("arm") != std::string::npos ||
renderer_lower.find("mali") != std::string::npos);
if (vendor_lower.find("advanced micro devices") != std::string::npos ||
vendor_lower.find("ati technologies inc.") != std::string::npos ||
vendor_lower.find("ati") != std::string::npos)
@@ -665,6 +669,10 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo)
Console.WriteLn(Color_StrongBlue, "GL: Intel GPU detected.");
//vendor_id_intel = true;
}
else if (vendor_id_arm_mali)
{
Console.WriteLn(Color_StrongYellow, "GL: ARM Mali GPU detected - applying workarounds");
}
GLint major_gl = 0;
GLint minor_gl = 0;
glGetIntegerv(GL_MAJOR_VERSION, &major_gl);
@@ -746,12 +754,31 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo)
} else {
buggy_pbo = !GLAD_GL_EXT_buffer_storage;
}
// Mali GPU workarounds - force PBO off and disable problematic features
if (vendor_id_arm_mali)
{
Console.WriteLn(Color_StrongYellow, "GL: Applying Mali GPU workarounds:");
Console.WriteLn(" - Disabling framebuffer fetch");
Console.WriteLn(" - Enabling texture barriers");
Console.WriteLn(" - Disabling vertex shader expansion");
Console.WriteLn(" - Disabling PBO for texture uploads");
Console.WriteLn(" - Disabling PBO for texture downloads");
Console.WriteLn(" - Disabling point expand");
buggy_pbo = true;
m_disable_download_pbo = true;
Console.WriteLn("GL: Mali workarounds applied. Textures will use direct upload path.");
}
if (buggy_pbo)
Console.Warning("GL: Not using PBOs for texture uploads because buffer_storage is unavailable.");
// Give the user the option to disable PBO usage for downloads.
// Most drivers seem to be faster with PBO.
m_disable_download_pbo = Host::GetBoolSettingValue("EmuCore/GS", "DisableGLDownloadPBO", false);
if (!vendor_id_arm_mali)
m_disable_download_pbo = Host::GetBoolSettingValue("EmuCore/GS", "DisableGLDownloadPBO", false);
if (m_disable_download_pbo)
Console.Warning("GL: Not using PBOs for texture downloads, this may reduce performance.");
@@ -760,6 +787,14 @@ bool GSDeviceOGL::CheckFeatures(bool& buggy_pbo)
m_features.primitive_id = true;
m_features.framebuffer_fetch = GLAD_GL_EXT_shader_framebuffer_fetch;
// Disable framebuffer fetch on Mali - causes 2D graphics issues
if (vendor_id_arm_mali && m_features.framebuffer_fetch)
{
Console.WriteLn("GL: Disabling framebuffer fetch on Mali GPU (causes rendering issues)");
m_features.framebuffer_fetch = false;
}
if (m_features.framebuffer_fetch && GSConfig.DisableFramebufferFetch)
{
Host::AddOSDMessage(
@@ -9,7 +9,20 @@ import java.io.File;
import java.lang.ref.WeakReference;
public class NativeApp {
private static boolean angleLoaded = false;
static {
try {
// Try shipping ANGLE (EGL/GLES-over-Vulkan). If missing, we'll fall back to system drivers.
System.loadLibrary("EGL_angle");
System.loadLibrary("GLESv2_angle");
angleLoaded = true;
android.util.Log.i("NativeApp", "ANGLE libraries loaded successfully (EGL_angle, GLESv2_angle)");
} catch (UnsatisfiedLinkError e) {
// Optional: not all builds will bundle ANGLE.
android.util.Log.w("NativeApp", "ANGLE libraries not found, will use native OpenGL ES driver: " + e.getMessage());
}
try {
System.loadLibrary("emucore");
hasNoNativeBinary = false;
@@ -17,6 +30,10 @@ public class NativeApp {
hasNoNativeBinary = true;
}
}
public static boolean isAngleLoaded() {
return angleLoaded;
}
public static boolean hasNoNativeBinary;
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<gradient
android:angle="135"
android:startColor="@color/md_theme_primaryContainer"
android:centerColor="@color/md_theme_tertiary"
android:endColor="@color/md_theme_tertiaryContainer"
android:type="linear" />
<corners android:bottomLeftRadius="24dp" android:bottomRightRadius="24dp" />
</shape>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="16dp" />
</shape>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,333L760,333L760,200Q760,200 760,200Q760,200 760,200L200,200Q200,200 200,200Q200,200 200,200L200,333ZM200,547L760,547L760,413L200,413L200,547ZM200,760L760,760Q760,760 760,760Q760,760 760,760L760,627L200,627L200,760Q200,760 200,760Q200,760 200,760ZM240,306L240,226L320,226L320,306L240,306ZM240,520L240,440L320,440L320,520L240,520ZM240,734L240,654L320,654L320,734L240,734Z"/>
</vector>
+10
View File
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M360,600L360,360L600,360L600,600L360,600ZM440,520L520,520L520,440L440,440L440,520ZM360,840L360,760L280,760Q247,760 223.5,736.5Q200,713 200,680L200,600L120,600L120,520L200,520L200,440L120,440L120,360L200,360L200,280Q200,247 223.5,223.5Q247,200 280,200L360,200L360,120L440,120L440,200L520,200L520,120L600,120L600,200L680,200Q713,200 736.5,223.5Q760,247 760,280L760,360L840,360L840,440L760,440L760,520L840,520L840,600L760,600L760,680Q760,713 736.5,736.5Q713,760 680,760L600,760L600,840L520,840L520,760L440,760L440,840L360,840ZM680,680Q680,680 680,680Q680,680 680,680L680,280Q680,280 680,280Q680,280 680,280L280,280Q280,280 280,280Q280,280 280,280L280,680Q280,680 280,680Q280,680 280,680L680,680ZM480,480L480,480L480,480L480,480Z"/>
</vector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="16dp" android:height="16dp" />
<solid android:color="@color/md_theme_tertiary" />
</shape>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="16dp" android:height="16dp" />
<solid android:color="@color/md_theme_outlineVariant" />
</shape>
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/md_theme_surface"
android:fitsSystemWindows="false">
<FrameLayout
android:id="@+id/header_container"
android:layout_width="0dp"
android:layout_height="180dp"
android:background="@drawable/bg_setup_intro_header"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingTop="48dp"
android:paddingBottom="20dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/tv_step"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Step 1 of 3"
android:textColor="@color/md_theme_onPrimary"
android:textSize="12sp"
android:alpha="0.85"
android:paddingBottom="4dp" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Welcome to PSX2"
android:textColor="@color/md_theme_onPrimary"
android:textSize="24sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tv_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Three quick steps to start playing"
android:textColor="@color/md_theme_onPrimary"
android:textSize="14sp"
android:alpha="0.9"
android:paddingTop="6dp" />
</LinearLayout>
</FrameLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/setup_pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingTop="20dp"
android:paddingBottom="16dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
app:layout_constraintTop_toBottomOf="@id/header_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/swipe_hint" />
<TextView
android:id="@+id/swipe_hint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="← Swipe to navigate →"
android:textSize="13sp"
android:textColor="@color/md_theme_tertiary"
android:textStyle="bold"
android:alpha="0.85"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@id/indicator_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:id="@+id/indicator_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@id/btn_next"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_next"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Next"
android:textAllCaps="false"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="32dp"
android:backgroundTint="@color/md_theme_tertiaryContainer"
android:textColor="@color/md_theme_onTertiaryContainer"
app:iconGravity="textStart"
app:iconTint="@color/md_theme_onTertiaryContainer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:background="@color/md_theme_surfaceContainer">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/iv_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginBottom="16dp"
android:tint="@color/md_theme_tertiary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_step_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingHorizontal="10dp"
android:paddingVertical="4dp"
android:text="STEP"
android:textAllCaps="true"
android:textSize="12sp"
android:textColor="@color/md_theme_onSurface"
android:background="@color/md_theme_surfaceContainerHigh"
app:layout_constraintTop_toBottomOf="@id/iv_icon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="Title"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/md_theme_onSurface"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/tv_step_chip"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Description"
android:textSize="14sp"
android:textColor="@color/md_theme_onSurfaceVariant"
app:layout_constraintTop_toBottomOf="@id/tv_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:id="@+id/action_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/tv_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_action"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Do it"
android:textAllCaps="false"
android:backgroundTint="@color/md_theme_tertiaryContainer"
android:textColor="@color/md_theme_onTertiaryContainer"
app:iconTint="@color/md_theme_tertiary" />
<TextView
android:id="@+id/tv_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:paddingHorizontal="12dp"
android:paddingVertical="8dp"
android:text="Pending"
android:textSize="12sp"
android:textStyle="bold"
android:textColor="@color/md_theme_onSurface"
android:background="@drawable/bg_status_badge"
android:backgroundTint="@color/md_theme_outlineVariant"
android:layout_gravity="center_vertical" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>