Optimize Android build configurations and improve texture handling with NEON support

This commit is contained in:
izzy2lost
2026-03-20 18:15:07 -04:00
parent 8a92b3edff
commit 71a11cfcad
7 changed files with 178 additions and 67 deletions
+7 -6
View File
@@ -42,12 +42,13 @@ android {
arguments += listOf(
"-DXEMU_ANDROID_BUILD_ID=3",
"-DXEMU_ENABLE_XISO_CONVERTER=ON",
"-DCMAKE_C_FLAGS_DEBUG=-O2 -g0",
"-DCMAKE_CXX_FLAGS_DEBUG=-O2 -g0",
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=-O3 -g0",
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=-O3 -g0",
"-DCMAKE_C_FLAGS_RELEASE=-O3 -g0",
"-DCMAKE_CXX_FLAGS_RELEASE=-O3 -g0"
"-DCMAKE_C_FLAGS_DEBUG=-O2 -g0 -march=armv8-a+simd -fvisibility=hidden -ffunction-sections -fdata-sections",
"-DCMAKE_CXX_FLAGS_DEBUG=-O2 -g0 -march=armv8-a+simd -fvisibility=hidden -ffunction-sections -fdata-sections",
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=-O3 -g0 -march=armv8-a+simd -fvisibility=hidden -ffunction-sections -fdata-sections",
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=-O3 -g0 -march=armv8-a+simd -fvisibility=hidden -ffunction-sections -fdata-sections",
"-DCMAKE_C_FLAGS_RELEASE=-O3 -g0 -march=armv8-a+simd -fvisibility=hidden -ffunction-sections -fdata-sections",
"-DCMAKE_CXX_FLAGS_RELEASE=-O3 -g0 -march=armv8-a+simd -fvisibility=hidden -ffunction-sections -fdata-sections",
"-DCMAKE_SHARED_LINKER_FLAGS=-Wl,--gc-sections"
)
cppFlags += listOf("-std=c++17", "-fexceptions", "-frtti")
}
+22 -10
View File
@@ -23,6 +23,7 @@
#include <string>
#include <vector>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
@@ -144,18 +145,29 @@ static void AppendNativeDebugLog(const char* level, const char* message) {
SDL_LockMutex(g_native_debug_log_mutex);
/* Use POSIX write() with O_APPEND instead of std::ofstream to avoid
* repeated file open/close overhead and C++ stream construction. */
struct stat st {};
const bool should_truncate =
stat(g_native_debug_log_path.c_str(), &st) == 0 &&
st.st_size > kMaxDebugLogBytes;
if (stat(g_native_debug_log_path.c_str(), &st) == 0 &&
st.st_size > kMaxDebugLogBytes) {
/* Truncate oversized log file */
int tfd = open(g_native_debug_log_path.c_str(), O_WRONLY | O_TRUNC);
if (tfd >= 0) close(tfd);
}
std::ofstream out(
g_native_debug_log_path,
should_truncate ? (std::ios::out | std::ios::trunc)
: (std::ios::out | std::ios::app));
if (out.is_open()) {
out << CurrentNativeLogTimestamp() << ' ' << level << '/' << kLogTag
<< ": " << message << '\n';
int fd = open(g_native_debug_log_path.c_str(),
O_WRONLY | O_CREAT | O_APPEND, 0644);
if (fd >= 0) {
char buf[2048];
std::string ts = CurrentNativeLogTimestamp();
int len = snprintf(buf, sizeof(buf), "%s %s/%s: %s\n",
ts.c_str(), level, kLogTag, message);
if (len > 0) {
size_t bytes_to_write =
(size_t)((len < (int)sizeof(buf) - 1) ? len : (int)sizeof(buf) - 1);
(void)write(fd, buf, bytes_to_write);
}
close(fd);
}
SDL_UnlockMutex(g_native_debug_log_mutex);
+7 -2
View File
@@ -59,6 +59,7 @@ typedef struct XemuFatxCache {
uint32_t entry_size;
bool dirty;
void *data;
size_t data_capacity;
} XemuFatxCache;
typedef struct XemuFatxFs {
@@ -375,11 +376,15 @@ static bool xemu_fatx_populate_fat_cache(XemuFatxFs *fs, uint32_t index)
cache->dirty = false;
}
g_free(cache->data);
cache->position = index;
cache->entries = MIN(XEMU_FATX_FAT_CACHE_NUM_ENTRIES, fs->fat_entry_count - index);
cache->entry_size = fs->fat_type == XEMU_FATX_FAT_TYPE_16 ? 2 : 4;
cache->data = g_malloc(cache->entries * cache->entry_size);
size_t needed = (size_t)cache->entries * cache->entry_size;
if (needed > cache->data_capacity) {
g_free(cache->data);
cache->data = g_malloc(needed);
cache->data_capacity = needed;
}
xemu_fatx_dev_seek(fs, fs->fat_offset +
(uint64_t)cache->position * cache->entry_size);
+2
View File
@@ -203,6 +203,8 @@ typedef struct PGRAPHGLState {
size_t android_conv_buf_size;
uint8_t *android_s2t_conv_buf; /* render-to-texture conversion */
size_t android_s2t_conv_buf_size;
uint8_t *android_tex_conv_buf; /* texture upload conversion */
size_t android_tex_conv_buf_size;
#endif
TextureBinding *texture_binding[NV2A_MAX_TEXTURES];
+121 -47
View File
@@ -29,6 +29,9 @@
#ifdef __ANDROID__
#include <android/log.h>
#ifdef __aarch64__
#include <arm_neon.h>
#endif
static void android_log_gl_errors(const char *ctx)
{
@@ -231,48 +234,116 @@ static void android_texture_convert_to_rgba8(const TextureShape s,
}
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8:
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8:
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: {
const uint8_t *pixel = src_row + x * 4;
out[0] = pixel[2];
out[1] = pixel[1];
out[2] = pixel[0];
out[3] = pixel[3];
break;
}
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8:
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8:
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: {
bool preserve_alpha =
(s.color_format != NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8 &&
s.color_format != NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8);
#ifdef __aarch64__
/* NEON BGRA→RGBA shuffle: process 4 pixels (16 bytes) at a time */
static const uint8_t perm_arr[16] =
{2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15};
uint8x16_t vperm = vld1q_u8(perm_arr);
static const uint8_t alpha_mask_arr[16] =
{0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF, 0,0,0,0xFF};
uint8x16_t valpha_mask = preserve_alpha
? vdupq_n_u8(0)
: vld1q_u8(alpha_mask_arr);
unsigned int remaining = width - x;
const uint8_t *sp = src_row + x * 4;
uint8_t *dp = out;
while (remaining >= 4) {
uint8x16_t v = vqtbl1q_u8(vld1q_u8(sp), vperm);
vst1q_u8(dp, vorrq_u8(v, valpha_mask));
sp += 16; dp += 16; remaining -= 4;
}
while (remaining-- > 0) {
dp[0] = sp[2]; dp[1] = sp[1];
dp[2] = sp[0]; dp[3] = preserve_alpha ? sp[3] : 0xFF;
sp += 4; dp += 4;
}
x = width; /* skip remaining scalar iterations */
#else
const uint8_t *pixel = src_row + x * 4;
out[0] = pixel[2];
out[1] = pixel[1];
out[2] = pixel[0];
out[3] = 0xFF;
out[3] = preserve_alpha ? pixel[3] : 0xFF;
#endif
break;
}
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8:
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: {
#ifdef __aarch64__
/* NEON B8G8R8A8→RGBA8: rotate bytes within each pixel */
static const uint8_t perm_arr[16] =
{1,2,3,0, 5,6,7,4, 9,10,11,8, 13,14,15,12};
uint8x16_t vperm = vld1q_u8(perm_arr);
unsigned int remaining = width - x;
const uint8_t *sp = src_row + x * 4;
uint8_t *dp = out;
while (remaining >= 4) {
vst1q_u8(dp, vqtbl1q_u8(vld1q_u8(sp), vperm));
sp += 16; dp += 16; remaining -= 4;
}
while (remaining-- > 0) {
dp[0] = sp[1]; dp[1] = sp[2];
dp[2] = sp[3]; dp[3] = sp[0];
sp += 4; dp += 4;
}
x = width;
#else
const uint8_t *pixel = src_row + x * 4;
out[0] = pixel[1];
out[1] = pixel[2];
out[2] = pixel[3];
out[3] = pixel[0];
#endif
break;
}
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8:
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: {
#ifdef __aarch64__
/* A8B8G8R8 is already RGBA8 — bulk memcpy */
memcpy(out, src_row + x * 4, (width - x) * 4);
x = width;
#else
const uint8_t *pixel = src_row + x * 4;
out[0] = pixel[0];
out[1] = pixel[1];
out[2] = pixel[2];
out[3] = pixel[3];
#endif
break;
}
case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8:
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: {
#ifdef __aarch64__
/* NEON R8G8B8A8→RGBA8: full byte reverse per pixel */
static const uint8_t perm_arr[16] =
{3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12};
uint8x16_t vperm = vld1q_u8(perm_arr);
unsigned int remaining = width - x;
const uint8_t *sp = src_row + x * 4;
uint8_t *dp = out;
while (remaining >= 4) {
vst1q_u8(dp, vqtbl1q_u8(vld1q_u8(sp), vperm));
sp += 16; dp += 16; remaining -= 4;
}
while (remaining-- > 0) {
dp[0] = sp[3]; dp[1] = sp[2];
dp[2] = sp[1]; dp[3] = sp[0];
sp += 4; dp += 4;
}
x = width;
#else
const uint8_t *pixel = src_row + x * 4;
out[0] = pixel[3];
out[1] = pixel[2];
out[2] = pixel[1];
out[3] = pixel[0];
#endif
break;
}
case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_G8B8:
@@ -300,7 +371,8 @@ static void android_texture_convert_to_rgba8(const TextureShape s,
}
}
static void android_prepare_tex_upload(const TextureShape s,
static void android_prepare_tex_upload(PGRAPHGLState *r,
const TextureShape s,
const uint8_t *src,
unsigned int width,
unsigned int height,
@@ -310,11 +382,9 @@ static void android_prepare_tex_upload(const TextureShape s,
GLint *ifmt,
GLenum *fmt,
GLenum *type,
const uint8_t **upload_data,
uint8_t **upload_tmp)
const uint8_t **upload_data)
{
*upload_data = src;
*upload_tmp = NULL;
*ifmt = kelvin_color_format_gl_map[s.color_format].gl_internal_format;
*fmt = kelvin_color_format_gl_map[s.color_format].gl_format;
*type = kelvin_color_format_gl_map[s.color_format].gl_type;
@@ -323,10 +393,16 @@ static void android_prepare_tex_upload(const TextureShape s,
return;
}
*upload_tmp = g_malloc(width * height * depth * 4);
size_t needed = (size_t)width * height * depth * 4;
if (needed > r->android_tex_conv_buf_size) {
g_free(r->android_tex_conv_buf);
r->android_tex_conv_buf = g_malloc(needed);
r->android_tex_conv_buf_size = needed;
}
android_texture_convert_to_rgba8(s, src, width, height, depth,
row_pitch, slice_pitch, *upload_tmp);
*upload_data = *upload_tmp;
row_pitch, slice_pitch,
r->android_tex_conv_buf);
*upload_data = r->android_tex_conv_buf;
*ifmt = GL_RGBA8;
*fmt = GL_RGBA;
*type = GL_UNSIGNED_BYTE;
@@ -383,7 +459,7 @@ static void pgraph_gl_log_invalid_texture_range(NV2AState *d,
#endif
}
static TextureBinding* generate_texture(const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data);
static TextureBinding* generate_texture(PGRAPHGLState *r, const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data);
static void texture_binding_destroy(gpointer data);
struct pgraph_texture_possibly_dirty_struct {
@@ -810,7 +886,7 @@ void pgraph_gl_bind_textures(NV2AState *d)
if (key_out->binding == NULL) {
// Must create the texture
key_out->binding = generate_texture(state, texture_data, palette_data);
key_out->binding = generate_texture(r, state, texture_data, palette_data);
key_out->binding->data_hash = tex_data_hash;
key_out->binding->scale = 1;
#ifdef __ANDROID__
@@ -894,11 +970,13 @@ gl_internal_format_to_s3tc_enum(GLint gl_internal_format)
}
}
static void upload_gl_texture(GLenum gl_target,
static void upload_gl_texture(PGRAPHGLState *r,
GLenum gl_target,
const TextureShape s,
const uint8_t *texture_data,
const uint8_t *palette_data)
{
(void)r; /* used only in Android-specific paths */
ColorFormatInfo f = kelvin_color_format_gl_map[s.color_format];
nv2a_profile_inc_counter(NV2A_PROF_TEX_UPLOAD);
#ifdef __ANDROID__
@@ -935,7 +1013,6 @@ static void upload_gl_texture(GLenum gl_target,
{
const uint8_t *pixel_data = converted ? converted : texture_data;
const uint8_t *upload_data = pixel_data;
uint8_t *upload_tmp = NULL;
unsigned int source_bpp =
#ifdef __ANDROID__
android_texture_source_bpp(s, f.bytes_per_pixel,
@@ -949,11 +1026,11 @@ static void upload_gl_texture(GLenum gl_target,
GLint tex_ifmt = f.gl_internal_format;
GLenum tex_type = f.gl_type;
#ifdef __ANDROID__
android_prepare_tex_upload(s, pixel_data, adjusted_width,
android_prepare_tex_upload(r, s, pixel_data, adjusted_width,
adjusted_height, 1, row_pitch,
row_pitch * adjusted_height,
&tex_ifmt, &tex_fmt, &tex_type,
&upload_data, &upload_tmp);
&upload_data);
#endif
glPixelStorei(GL_UNPACK_ROW_LENGTH,
upload_data == texture_data ?
@@ -963,9 +1040,6 @@ static void upload_gl_texture(GLenum gl_target,
tex_fmt, tex_type,
upload_data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
#ifdef __ANDROID__
g_free(upload_tmp);
#endif
}
if (converted) {
@@ -1000,8 +1074,10 @@ static void upload_gl_texture(GLenum gl_target,
texture_data, width, height);
unsigned int tex_width = width;
unsigned int tex_height = height;
bool need_cubemap_border_strip =
s.cubemap && adjusted_width != s.width;
if (s.cubemap && adjusted_width != s.width) {
if (need_cubemap_border_strip) {
// FIXME: Consider preserving the border.
// There does not seem to be a way to reference the border
// texels in a cubemap, so they are discarded.
@@ -1019,7 +1095,7 @@ static void upload_gl_texture(GLenum gl_target,
glTexImage2D(gl_target, level, GL_RGBA8, tex_width, tex_height,
0, GL_RGBA, GL_UNSIGNED_BYTE, converted);
g_free(converted);
if (s.cubemap && adjusted_width != s.width) {
if (need_cubemap_border_strip) {
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
if (physical_width == width) {
@@ -1059,16 +1135,15 @@ static void upload_gl_texture(GLenum gl_target,
{
const uint8_t *upload_data = pixel_data;
uint8_t *upload_tmp = NULL;
GLenum tex_fmt = f.gl_format;
GLint tex_ifmt = f.gl_internal_format;
GLenum tex_type = f.gl_type;
#ifdef __ANDROID__
android_prepare_tex_upload(s, pixel_data, tex_width,
android_prepare_tex_upload(r, s, pixel_data, tex_width,
tex_height, 1, row_pitch,
row_pitch * tex_height,
&tex_ifmt, &tex_fmt, &tex_type,
&upload_data, &upload_tmp);
&upload_data);
#endif
glPixelStorei(GL_UNPACK_ROW_LENGTH,
upload_data == pixel_data &&
@@ -1078,9 +1153,6 @@ static void upload_gl_texture(GLenum gl_target,
tex_height, 0, tex_fmt, tex_type,
upload_data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
#ifdef __ANDROID__
g_free(upload_tmp);
#endif
}
if (converted) {
g_free(converted);
@@ -1153,7 +1225,6 @@ static void upload_gl_texture(GLenum gl_target,
{
const uint8_t *pixel_data = converted ? converted : unswizzled;
const uint8_t *upload_data = pixel_data;
uint8_t *upload_tmp = NULL;
unsigned int source_bpp =
#ifdef __ANDROID__
android_texture_source_bpp(s, f.bytes_per_pixel,
@@ -1167,19 +1238,16 @@ static void upload_gl_texture(GLenum gl_target,
GLint tex_ifmt = f.gl_internal_format;
GLenum tex_type = f.gl_type;
#ifdef __ANDROID__
android_prepare_tex_upload(s, pixel_data, width, height,
android_prepare_tex_upload(r, s, pixel_data, width, height,
depth, upload_row_pitch,
upload_slice_pitch, &tex_ifmt,
&tex_fmt, &tex_type,
&upload_data, &upload_tmp);
&upload_data);
#endif
glTexImage3D(gl_target, level, tex_ifmt,
width, height, depth, 0,
tex_fmt, tex_type,
upload_data);
#ifdef __ANDROID__
g_free(upload_tmp);
#endif
}
if (converted) {
@@ -1205,7 +1273,8 @@ static void upload_gl_texture(GLenum gl_target,
#endif
}
static TextureBinding* generate_texture(const TextureShape s,
static TextureBinding* generate_texture(PGRAPHGLState *r,
const TextureShape s,
const uint8_t *texture_data,
const uint8_t *palette_data)
{
@@ -1282,20 +1351,20 @@ static TextureBinding* generate_texture(const TextureShape s,
length = (length + NV2A_CUBEMAP_FACE_ALIGNMENT - 1) & ~(NV2A_CUBEMAP_FACE_ALIGNMENT - 1);
upload_gl_texture(GL_TEXTURE_CUBE_MAP_POSITIVE_X,
upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
s, texture_data + 0 * length, palette_data);
upload_gl_texture(GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
s, texture_data + 1 * length, palette_data);
upload_gl_texture(GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
s, texture_data + 2 * length, palette_data);
upload_gl_texture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
s, texture_data + 3 * length, palette_data);
upload_gl_texture(GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
s, texture_data + 4 * length, palette_data);
upload_gl_texture(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
upload_gl_texture(r, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
s, texture_data + 5 * length, palette_data);
} else {
upload_gl_texture(gl_target, s, texture_data, palette_data);
upload_gl_texture(r, gl_target, s, texture_data, palette_data);
}
/* Linear textures don't support mipmapping */
@@ -1415,4 +1484,9 @@ void pgraph_gl_finalize_textures(PGRAPHState *pg)
free(r->texture_cache_entries);
r->texture_cache_entries = NULL;
#ifdef __ANDROID__
g_free(r->android_tex_conv_buf);
r->android_tex_conv_buf = NULL;
r->android_tex_conv_buf_size = 0;
#endif
}
+10
View File
@@ -23,6 +23,11 @@ NV2AStats g_nv2a_stats;
void nv2a_profile_increment(void)
{
#ifdef __ANDROID__
/* Skip profiling on Android — stats are never displayed and the
* qemu_clock_get_us() syscalls add unnecessary overhead per frame. */
return;
#else
int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
const int64_t fps_update_interval = 250000;
g_nv2a_stats.last_flip_time = now;
@@ -37,10 +42,14 @@ void nv2a_profile_increment(void)
ts = now;
frame_count = 0;
}
#endif
}
void nv2a_profile_flip_stall(void)
{
#ifdef __ANDROID__
return;
#else
int64_t now = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
int64_t render_time = (now-g_nv2a_stats.last_flip_time)/1000;
@@ -51,6 +60,7 @@ void nv2a_profile_flip_stall(void)
(g_nv2a_stats.frame_ptr + 1) % NV2A_PROF_NUM_FRAMES;
g_nv2a_stats.frame_count++;
memset(&g_nv2a_stats.frame_working, 0, sizeof(g_nv2a_stats.frame_working));
#endif
}
const char *nv2a_profile_get_counter_name(unsigned int cnt)
+9 -2
View File
@@ -1776,7 +1776,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
#endif
#ifdef __ANDROID__
const int64_t sleep_threshold = 500000; // 0.5ms — Android CFS scheduler jitter is ~0.2ms
const int64_t sleep_threshold = 200000; // 0.2ms — modern Android CFS jitter is <0.1ms
#elif !defined(_WIN32)
const int64_t sleep_threshold = 2000000;
#else
@@ -1951,7 +1951,14 @@ static void *call_qemu_main(void *opaque)
/* Note: only supports millisecond resolution on Windows */
static void sleep_ns(int64_t ns)
{
#ifndef _WIN32
#ifdef __ANDROID__
/* Use clock_nanosleep with relative time for better precision on Android.
* CLOCK_MONOTONIC avoids wall-clock adjustments during sleep. */
struct timespec sleep_delay;
sleep_delay.tv_sec = ns / 1000000000LL;
sleep_delay.tv_nsec = ns % 1000000000LL;
clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep_delay, NULL);
#elif !defined(_WIN32)
struct timespec sleep_delay, rem_delay;
sleep_delay.tv_sec = ns / 1000000000LL;
sleep_delay.tv_nsec = ns % 1000000000LL;