From ddc32eaea413861253c80fa16a14177bbb97edc3 Mon Sep 17 00:00:00 2001 From: izzy2lost Date: Fri, 6 Mar 2026 14:09:09 -0500 Subject: [PATCH] added opengl es option --- android/app/src/main/cpp/CMakeLists.txt | 25 +- android/app/src/main/cpp/xemu_android.cpp | 44 +- .../app/src/main/cpp/xemu_settings_android.cc | 18 +- .../com/izzy2lost/x1box/SettingsActivity.kt | 29 + .../src/main/res/layout/activity_settings.xml | 76 +++ android/app/src/main/res/values/strings.xml | 7 + hw/xbox/nv2a/nv2a.h | 2 - hw/xbox/nv2a/pgraph/gl/display.c | 316 ++++------- hw/xbox/nv2a/pgraph/gl/draw.c | 25 +- hw/xbox/nv2a/pgraph/gl/renderer.c | 13 + hw/xbox/nv2a/pgraph/gl/renderer.h | 1 + hw/xbox/nv2a/pgraph/gl/shaders.c | 36 +- hw/xbox/nv2a/pgraph/gl/surface.c | 498 ++++++++++++++++-- hw/xbox/nv2a/pgraph/gl/texture.c | 453 +++++++++++++++- hw/xbox/nv2a/pgraph/gl/vertex.c | 13 + hw/xbox/nv2a/pgraph/glsl/psh.c | 42 +- hw/xbox/nv2a/pgraph/glsl/vsh-ff.c | 24 +- hw/xbox/nv2a/pgraph/glsl/vsh-prog.c | 4 +- hw/xbox/nv2a/pgraph/glsl/vsh.c | 2 +- hw/xbox/nv2a/pgraph/pgraph.c | 18 +- hw/xbox/nv2a/pgraph/vertex.c | 8 + include/ui/xemu-display.h | 2 + ui/xemu.c | 296 ++--------- 23 files changed, 1343 insertions(+), 609 deletions(-) diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 910b5754e8..e31303b033 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -485,7 +485,6 @@ list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]gtk.*\\.c$") list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]sdl2.*\\.c$") list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]console-gl\\.c$") list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]console-vc\\.c$") -list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]shader\\.c$") list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]vnc.*\\.c$") list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]xemu-snapshots\\.c$") list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]xemu-thumbnail\\.cc$") @@ -659,6 +658,30 @@ endif() file(GLOB_RECURSE QAPI_GEN_SOURCES "${QAPI_GEN_DIR}/*.c") list(APPEND XEMU_CORE_SOURCES ${QAPI_GEN_SOURCES}) +# Generate GL shader headers used by ui/shader.c. +set(XEMU_SHADER_GEN_DIR "${CMAKE_BINARY_DIR}/ui/shader") +set(XEMU_SHADER_INPUTS + "${REPO_ROOT}/ui/shader/texture-blit.frag" + "${REPO_ROOT}/ui/shader/texture-blit.vert" + "${REPO_ROOT}/ui/shader/texture-blit-flip.vert" +) +file(MAKE_DIRECTORY "${XEMU_SHADER_GEN_DIR}") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${XEMU_SHADER_INPUTS}) +foreach(SHADER_INPUT ${XEMU_SHADER_INPUTS}) + get_filename_component(SHADER_NAME "${SHADER_INPUT}" NAME) + string(REPLACE "." "-" SHADER_OUTPUT_BASENAME "${SHADER_NAME}") + set(SHADER_OUTPUT "${XEMU_SHADER_GEN_DIR}/${SHADER_OUTPUT_BASENAME}.h") + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" "${REPO_ROOT}/scripts/shaderinclude.py" "${SHADER_INPUT}" + OUTPUT_FILE "${SHADER_OUTPUT}" + WORKING_DIRECTORY "${REPO_ROOT}" + RESULT_VARIABLE SHADER_GEN_RESULT + ) + if(NOT SHADER_GEN_RESULT EQUAL 0) + message(FATAL_ERROR "Shader header generation failed for ${SHADER_INPUT}") + endif() +endforeach() + # Generate block coroutine wrapper sources (block-gen.c). set(BLOCK_GEN_C "${CMAKE_BINARY_DIR}/block-gen.c") execute_process( diff --git a/android/app/src/main/cpp/xemu_android.cpp b/android/app/src/main/cpp/xemu_android.cpp index 58adf8446c..c2da5d723b 100644 --- a/android/app/src/main/cpp/xemu_android.cpp +++ b/android/app/src/main/cpp/xemu_android.cpp @@ -577,14 +577,17 @@ cleanup: } struct EmulatorSettings { - int surface_scale = 1; // 1, 2, or 3 - int frame_rate_limit = 60; // 30 or 60 - int system_memory_mib = 64; // 64 or 128 - std::string tcg_thread = "multi"; // "single" or "multi" + int surface_scale = 1; // 1, 2, or 3 + int frame_rate_limit = 60; // 30 or 60 + int system_memory_mib = 64; // 64 or 128 + std::string tcg_thread = "multi"; // "single" or "multi" + std::string renderer = "vulkan"; // "vulkan" or "opengl" + std::string filtering = "linear"; // "linear" or "nearest" bool use_dsp = false; bool hrtf = true; bool cache_shaders = true; bool hard_fpu = true; + bool vsync = false; bool skip_boot_anim = false; }; @@ -645,13 +648,13 @@ static bool WriteConfigToml(const std::string& config_path, general->insert_or_assign("show_welcome", false); general->insert_or_assign("skip_boot_anim", settings.skip_boot_anim); - display->insert_or_assign("renderer", "vulkan"); - if (!display->contains("filtering")) { - display->insert_or_assign("filtering", "nearest"); - } - if (!display_window->contains("vsync")) { - display_window->insert_or_assign("vsync", false); + { + const std::string r = (settings.renderer == "opengl") ? "opengl" : "vulkan"; + display->insert_or_assign("renderer", r); } + display->insert_or_assign( + "filtering", settings.filtering == "nearest" ? "nearest" : "linear"); + display_window->insert_or_assign("vsync", settings.vsync); // User-controlled settings (always written so they take effect immediately) { int scale = settings.surface_scale; @@ -678,9 +681,6 @@ static bool WriteConfigToml(const std::string& config_path, if (!audio->contains("volume_limit")) { audio->insert_or_assign("volume_limit", 1.0); } - if (!android->contains("force_cpu_blit")) { - android->insert_or_assign("force_cpu_blit", false); - } if (!android->contains("tcg_tuning")) { android->insert_or_assign("tcg_tuning", true); } @@ -830,6 +830,19 @@ static SetupFiles SyncSetupFiles() { emuSettings.tcg_thread = "single"; } } + { + std::string rendererPref = GetPrefString(env, activity, "setting_renderer"); + if (rendererPref == "opengl") { + emuSettings.renderer = "opengl"; + } + } + { + std::string filteringPref = GetPrefString(env, activity, "setting_filtering"); + if (filteringPref == "nearest") { + emuSettings.filtering = "nearest"; + } + } + emuSettings.vsync = GetPrefBool(env, activity, "setting_vsync", false); out.audio_driver = GetPrefString(env, activity, "setting_audio_driver"); int displayMode = GetPrefInt(env, activity, "setting_display_mode", 0); @@ -988,12 +1001,13 @@ extern "C" int SDL_main(int argc, char* argv[]) { } else if (!g_config.sys.files.eeprom_path) { xemu_settings_set_string(&g_config.sys.files.eeprom_path, ""); } - setenv("XEMU_ANDROID_FORCE_CPU_BLIT", "0", 1); g_config.general.show_welcome = false; g_config.perf.cache_shaders = true; LogInfoInt("Config final show_welcome=%d", g_config.general.show_welcome ? 1 : 0); LogInfoInt("Config final cache_shaders=%d", g_config.perf.cache_shaders ? 1 : 0); - LogInfoInt("Config final renderer=%d", (int)g_config.display.renderer); + LogInfoFmt("Config final renderer=%s", + (g_config.display.renderer == 0) ? "Vulkan" : + (g_config.display.renderer == 1) ? "OpenGL" : "Null"); LogInfoFmt("Config final bootrom=%s", g_config.sys.files.bootrom_path ? g_config.sys.files.bootrom_path : "(null)"); LogInfoFmt("Config final flashrom=%s", g_config.sys.files.flashrom_path ? g_config.sys.files.flashrom_path : "(null)"); LogInfoFmt("Config final hdd=%s", g_config.sys.files.hdd_path ? g_config.sys.files.hdd_path : "(null)"); diff --git a/android/app/src/main/cpp/xemu_settings_android.cc b/android/app/src/main/cpp/xemu_settings_android.cc index dce416a568..b242f46561 100644 --- a/android/app/src/main/cpp/xemu_settings_android.cc +++ b/android/app/src/main/cpp/xemu_settings_android.cc @@ -68,7 +68,7 @@ static void xemu_settings_apply_defaults(void) g_config.input.keyboard_controller_scancode_map.rtrigger = 18; g_config.display.renderer = CONFIG_DISPLAY_RENDERER_VULKAN; - g_config.display.filtering = CONFIG_DISPLAY_FILTERING_NEAREST; + g_config.display.filtering = CONFIG_DISPLAY_FILTERING_LINEAR; g_config.display.quality.surface_scale = 1; g_config.display.window.fullscreen_on_startup = false; g_config.display.window.fullscreen_exclusive = false; @@ -209,7 +209,6 @@ bool xemu_settings_load(void) xemu_settings_apply_defaults(); error_msg.clear(); - setenv("XEMU_ANDROID_FORCE_CPU_BLIT", "0", 1); setenv("XEMU_ANDROID_TCG_TUNING", "1", 1); setenv("XEMU_ANDROID_TCG_THREAD", "multi", 1); setenv("XEMU_ANDROID_TCG_TB_SIZE", "128", 1); @@ -247,13 +246,17 @@ bool xemu_settings_load(void) g_config.general.skip_boot_anim = *skip_boot_anim; } - // Display settings - force Vulkan on Android - g_config.display.renderer = CONFIG_DISPLAY_RENDERER_VULKAN; + // Display settings if (auto renderer = display["renderer"].value()) { CONFIG_DISPLAY_RENDERER parsed; - if (parse_renderer(*renderer, &parsed) && parsed != CONFIG_DISPLAY_RENDERER_VULKAN) { + if (parse_renderer(*renderer, &parsed)) { + g_config.display.renderer = parsed; + __android_log_print(ANDROID_LOG_INFO, "xemu-android", + "Config display.renderer=%s (%d)", + renderer->c_str(), (int)parsed); + } else { __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "Config display.renderer=%s requested, but Android forces Vulkan", + "Config display.renderer=%s unknown, using default", renderer->c_str()); } } @@ -311,9 +314,6 @@ bool xemu_settings_load(void) } // Android-specific settings - if (auto force_cpu_blit = android_cfg["force_cpu_blit"].value()) { - setenv("XEMU_ANDROID_FORCE_CPU_BLIT", *force_cpu_blit ? "1" : "0", 1); - } if (auto egl_offscreen = android_cfg["egl_offscreen"].value()) { if (!*egl_offscreen) { setenv("XEMU_ANDROID_EGL_OFFSCREEN", "0", 1); diff --git a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt index 3b91fee0da..c51eaa8669 100644 --- a/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt +++ b/android/app/src/main/java/com/izzy2lost/x1box/SettingsActivity.kt @@ -79,6 +79,8 @@ class SettingsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_settings) + val toggleGraphicsApi = findViewById(R.id.toggle_graphics_api) + val toggleFiltering = findViewById(R.id.toggle_filtering) val toggleScale = findViewById(R.id.toggle_resolution_scale) val btn1x = findViewById(R.id.btn_scale_1x) val btn2x = findViewById(R.id.btn_scale_2x) @@ -93,6 +95,7 @@ class SettingsActivity : AppCompatActivity() { val switchHrtf = findViewById(R.id.switch_hrtf) val switchShaders = findViewById(R.id.switch_cache_shaders) val switchFpu = findViewById(R.id.switch_hard_fpu) + val switchVsync = findViewById(R.id.switch_vsync) val switchSkipBootAnim = findViewById(R.id.switch_skip_boot_anim) val toggleAudioDriver = findViewById(R.id.toggle_audio_driver) val btnSave = findViewById(R.id.btn_settings_save) @@ -106,6 +109,20 @@ class SettingsActivity : AppCompatActivity() { dropdownEepromVideoStandard = findViewById(R.id.dropdown_eeprom_video_standard) // Load current values + val renderer = prefs.getString("setting_renderer", "vulkan") ?: "vulkan" + if (renderer == "opengl") { + toggleGraphicsApi.check(R.id.btn_renderer_opengl) + } else { + toggleGraphicsApi.check(R.id.btn_renderer_vulkan) + } + + val filtering = prefs.getString("setting_filtering", "linear") ?: "linear" + if (filtering == "nearest") { + toggleFiltering.check(R.id.btn_filtering_nearest) + } else { + toggleFiltering.check(R.id.btn_filtering_linear) + } + val scale = prefs.getInt("setting_surface_scale", 1) when (scale) { 2 -> toggleScale.check(R.id.btn_scale_2x) @@ -147,6 +164,7 @@ class SettingsActivity : AppCompatActivity() { switchHrtf.isChecked = prefs.getBoolean("setting_hrtf", true) switchShaders.isChecked = prefs.getBoolean("setting_cache_shaders", true) switchFpu.isChecked = prefs.getBoolean("setting_hard_fpu", true) + switchVsync.isChecked = prefs.getBoolean("setting_vsync", false) switchSkipBootAnim.isChecked = prefs.getBoolean("setting_skip_boot_anim", false) @@ -199,6 +217,14 @@ class SettingsActivity : AppCompatActivity() { R.id.btn_audio_disabled -> "dummy" else -> "openslES" } + val selectedRenderer = when (toggleGraphicsApi.checkedButtonId) { + R.id.btn_renderer_opengl -> "opengl" + else -> "vulkan" + } + val selectedFiltering = when (toggleFiltering.checkedButtonId) { + R.id.btn_filtering_nearest -> "nearest" + else -> "linear" + } val edit = prefs.edit() .putInt("setting_display_mode", selectedDisplayMode) @@ -210,8 +236,11 @@ class SettingsActivity : AppCompatActivity() { .putBoolean("setting_hrtf", switchHrtf.isChecked) .putBoolean("setting_cache_shaders", switchShaders.isChecked) .putBoolean("setting_hard_fpu", switchFpu.isChecked) + .putBoolean("setting_vsync", switchVsync.isChecked) .putBoolean("setting_skip_boot_anim", switchSkipBootAnim.isChecked) .putString("setting_audio_driver", selectedAudioDriver) + .putString("setting_filtering", selectedFiltering) + .putString("setting_renderer", selectedRenderer) when { clearVulkan -> edit diff --git a/android/app/src/main/res/layout/activity_settings.xml b/android/app/src/main/res/layout/activity_settings.xml index 330049fdf4..21ec539a0f 100644 --- a/android/app/src/main/res/layout/activity_settings.xml +++ b/android/app/src/main/res/layout/activity_settings.xml @@ -55,6 +55,82 @@ android:textAppearance="@style/TextAppearance.Material3.LabelLarge" android:textColor="@color/xemu_green" /> + + + + + + + + + + + + + + + + + + + + + + Save Display + Graphics API + Vulkan + OpenGL ES + Display Filtering + Linear + Nearest + VSync Custom Vulkan Driver System Default Browse… diff --git a/hw/xbox/nv2a/nv2a.h b/hw/xbox/nv2a/nv2a.h index 4157138677..25df54eb99 100644 --- a/hw/xbox/nv2a/nv2a.h +++ b/hw/xbox/nv2a/nv2a.h @@ -25,8 +25,6 @@ void nv2a_init(PCIBus *bus, int devfn, MemoryRegion *ram); void nv2a_context_init(void); #ifdef __ANDROID__ void nv2a_android_early_context_init(void); -bool nv2a_android_copy_readback(uint8_t **buffer, size_t *buffer_size, - int *width, int *height); #endif int nv2a_get_framebuffer_surface(void); void nv2a_release_framebuffer_surface(void); diff --git a/hw/xbox/nv2a/pgraph/gl/display.c b/hw/xbox/nv2a/pgraph/gl/display.c index 1987884b7a..3247e75a82 100644 --- a/hw/xbox/nv2a/pgraph/gl/display.c +++ b/hw/xbox/nv2a/pgraph/gl/display.c @@ -26,180 +26,110 @@ #include "renderer.h" #include - #ifdef __ANDROID__ #include -#include "qemu/thread.h" -#include -#include "system/memory.h" #endif #ifdef __ANDROID__ -static void check_gl_error_android(const char *context) +static void gl_log_errors(const char *ctx) { - GLenum err; - int count = 0; - while ((err = glGetError()) != GL_NO_ERROR) { - fprintf(stderr, "GL error 0x%X in %s\n", err, context); - if (++count >= 10) { - break; - } + GLenum gl_err; + + while ((gl_err = glGetError()) != GL_NO_ERROR) { + __android_log_print(ANDROID_LOG_WARN, "xemu-android", + "GL error 0x%X at %s", gl_err, ctx); } } -#define GL_ASSERT_NO_ERROR(context) check_gl_error_android(context) + +#define GL_ASSERT_NO_ERROR(ctx) gl_log_errors(ctx) #else #define GL_ASSERT_NO_ERROR(context) assert(glGetError() == GL_NO_ERROR) #endif #ifdef __ANDROID__ -static QemuMutex g_android_readback_mutex; -static bool g_android_readback_mutex_init = false; -static uint8_t *g_android_readback_buf = NULL; -static size_t g_android_readback_size = 0; -static int g_android_readback_w = 0; -static int g_android_readback_h = 0; -static bool g_android_readback_ready = false; +typedef struct AndroidDisplayGLState { + GLint framebuffer; + GLint program; + GLint vertex_array; + GLint array_buffer; + GLint active_texture; + GLint texture_2d_unit0; + GLint viewport[4]; + GLboolean color_mask[4]; + GLfloat clear_color[4]; + GLboolean scissor_test; + GLboolean blend; + GLboolean stencil_test; + GLboolean cull_face; + GLboolean depth_test; +} AndroidDisplayGLState; -static bool android_force_cpu_blit(void) +static void android_display_capture_gl_state(AndroidDisplayGLState *state) { - static int cached = -1; - if (cached < 0) { - const char *env = getenv("XEMU_ANDROID_FORCE_CPU_BLIT"); - if (!env) { - cached = 1; - } else if (!strcmp(env, "1") || !strcmp(env, "true") || - !strcmp(env, "TRUE")) { - cached = 1; - } else { - cached = 0; - } - } - return cached == 1; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &state->framebuffer); + glGetIntegerv(GL_CURRENT_PROGRAM, &state->program); + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &state->vertex_array); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &state->array_buffer); + glGetIntegerv(GL_ACTIVE_TEXTURE, &state->active_texture); + glActiveTexture(GL_TEXTURE0); + glGetIntegerv(GL_TEXTURE_BINDING_2D, &state->texture_2d_unit0); + glActiveTexture(state->active_texture); + glGetIntegerv(GL_VIEWPORT, state->viewport); + glGetBooleanv(GL_COLOR_WRITEMASK, state->color_mask); + glGetFloatv(GL_COLOR_CLEAR_VALUE, state->clear_color); + state->scissor_test = glIsEnabled(GL_SCISSOR_TEST); + state->blend = glIsEnabled(GL_BLEND); + state->stencil_test = glIsEnabled(GL_STENCIL_TEST); + state->cull_face = glIsEnabled(GL_CULL_FACE); + state->depth_test = glIsEnabled(GL_DEPTH_TEST); } -static bool android_readback_is_black(const uint8_t *buf, size_t size, - int width, int height) +static void android_display_restore_gl_state( + const AndroidDisplayGLState *state) { - if (!buf || size < 4 || width <= 0 || height <= 0) { - return false; - } - size_t tl_off = 0; - size_t mid_off = ((size_t)(height / 2) * (size_t)width + - (size_t)(width / 2)) * 4; - size_t br_off = ((size_t)(height - 1) * (size_t)width + - (size_t)(width - 1)) * 4; - uint32_t tl = 0, mid = 0, br = 0; - if (tl_off + 4 <= size) { - memcpy(&tl, buf + tl_off, sizeof(tl)); - } - if (mid_off + 4 <= size) { - memcpy(&mid, buf + mid_off, sizeof(mid)); - } - if (br_off + 4 <= size) { - memcpy(&br, buf + br_off, sizeof(br)); - } - bool black_tl = (tl == 0x00000000u || tl == 0xff000000u); - bool black_mid = (mid == 0x00000000u || mid == 0xff000000u); - bool black_br = (br == 0x00000000u || br == 0xff000000u); - return black_tl && black_mid && black_br; -} + glBindFramebuffer(GL_FRAMEBUFFER, state->framebuffer); + glUseProgram(state->program); + glBindVertexArray(state->vertex_array); + glBindBuffer(GL_ARRAY_BUFFER, state->array_buffer); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, state->texture_2d_unit0); + glActiveTexture(state->active_texture); + glViewport(state->viewport[0], state->viewport[1], + state->viewport[2], state->viewport[3]); + glColorMask(state->color_mask[0], state->color_mask[1], + state->color_mask[2], state->color_mask[3]); + glClearColor(state->clear_color[0], state->clear_color[1], + state->clear_color[2], state->clear_color[3]); -static void android_store_readback(NV2AState *d, SurfaceBinding *surface, - int width, int height) -{ - if (width <= 0 || height <= 0) { - return; + if (state->scissor_test) { + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); } - if (!g_android_readback_mutex_init) { - qemu_mutex_init(&g_android_readback_mutex); - g_android_readback_mutex_init = true; + if (state->blend) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); } - size_t needed = (size_t)width * (size_t)height * 4; - qemu_mutex_lock(&g_android_readback_mutex); - if (needed > g_android_readback_size) { - g_android_readback_buf = g_realloc(g_android_readback_buf, needed); - g_android_readback_size = needed; + if (state->stencil_test) { + glEnable(GL_STENCIL_TEST); + } else { + glDisable(GL_STENCIL_TEST); } - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, - g_android_readback_buf); - glPixelStorei(GL_PACK_ALIGNMENT, 4); - bool display_black = android_readback_is_black( - g_android_readback_buf, g_android_readback_size, width, height); - bool used_surface = false; - if (display_black && d && surface && surface->gl_buffer) { - PGRAPHState *pg = &d->pgraph; - PGRAPHGLState *r = pg->gl_renderer_state; - unsigned int surf_w = surface->width; - unsigned int surf_h = surface->height; - pgraph_apply_scaling_factor(pg, &surf_w, &surf_h); - if (surf_w > 0 && surf_h > 0) { - size_t surf_needed = (size_t)surf_w * (size_t)surf_h * 4; - if (surf_needed > g_android_readback_size) { - g_android_readback_buf = g_realloc(g_android_readback_buf, - surf_needed); - g_android_readback_size = surf_needed; - } - GLint prev_fbo = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, r->disp_rndr.fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, surface->gl_buffer, 0); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glReadPixels(0, 0, surf_w, surf_h, GL_RGBA, GL_UNSIGNED_BYTE, - g_android_readback_buf); - glPixelStorei(GL_PACK_ALIGNMENT, 4); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, - GL_TEXTURE_2D, r->gl_display_buffer, 0); - glBindFramebuffer(GL_FRAMEBUFFER, prev_fbo); - g_android_readback_w = (int)surf_w; - g_android_readback_h = (int)surf_h; - used_surface = true; - static unsigned int surf_log = 0; - if ((surf_log++ % 120) == 0) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "android readback: display black, using surface vram=0x%x size=%ux%u", - (unsigned)surface->vram_addr, - surf_w, surf_h); - } - } + if (state->cull_face) { + glEnable(GL_CULL_FACE); + } else { + glDisable(GL_CULL_FACE); } - if (!used_surface) { - g_android_readback_w = width; - g_android_readback_h = height; + if (state->depth_test) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); } - g_android_readback_ready = true; - qemu_mutex_unlock(&g_android_readback_mutex); } - -bool nv2a_android_copy_readback(uint8_t **buffer, size_t *buffer_size, - int *width, int *height) -{ - if (!android_force_cpu_blit()) { - return false; - } - if (!g_android_readback_mutex_init || !g_android_readback_ready) { - return false; - } - qemu_mutex_lock(&g_android_readback_mutex); - size_t needed = (size_t)g_android_readback_w * (size_t)g_android_readback_h * 4; - if (!g_android_readback_buf || needed == 0) { - qemu_mutex_unlock(&g_android_readback_mutex); - return false; - } - if (*buffer_size < needed) { - *buffer = g_realloc(*buffer, needed); - *buffer_size = needed; - } - memcpy(*buffer, g_android_readback_buf, needed); - *width = g_android_readback_w; - *height = g_android_readback_h; - qemu_mutex_unlock(&g_android_readback_mutex); - return true; -} - #endif + void pgraph_gl_init_display(NV2AState *d) { struct PGRAPHState *pg = &d->pgraph; @@ -556,16 +486,6 @@ static void render_display(NV2AState *d, SurfaceBinding *surface) glDrawBuffers(1, DrawBuffers); GLenum fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (fb_status != GL_FRAMEBUFFER_COMPLETE) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "xemu-android", - "render_display: framebuffer incomplete 0x%X (fmt=%d ifmt=%d type=%d size=%ux%u)", - fb_status, - r->gl_display_buffer_format, - r->gl_display_buffer_internal_format, - r->gl_display_buffer_type, - r->gl_display_buffer_width, - r->gl_display_buffer_height); -#else fprintf(stderr, "render_display: framebuffer incomplete 0x%X (fmt=%d ifmt=%d type=%d size=%ux%u)\n", fb_status, @@ -574,7 +494,6 @@ static void render_display(NV2AState *d, SurfaceBinding *surface) r->gl_display_buffer_type, r->gl_display_buffer_width, r->gl_display_buffer_height); -#endif glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -582,44 +501,13 @@ static void render_display(NV2AState *d, SurfaceBinding *surface) } glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); -#ifdef __ANDROID__ - bool clip_enable = false; - float clip_x = 0.0f; - float clip_y = 0.0f; - float clip_w = 0.0f; - float clip_h = 0.0f; - if (android_force_cpu_blit()) { - clip_x = (float)surface->shape.clip_x; - clip_y = (float)surface->shape.clip_y; - clip_w = (float)surface->shape.clip_width; - clip_h = (float)surface->shape.clip_height; - if (clip_w > 0.0f && clip_h > 0.0f) { - clip_enable = true; - static unsigned int clip_log = 0; - if ((clip_log++ % 120) == 0) { - GLint tex_w = 0; - GLint tex_h = 0; - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tex_w); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &tex_h); - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "render_display: clip rect x=%g y=%g w=%g h=%g tex=%dx%d disp=%ux%u", - clip_x, clip_y, clip_w, clip_h, - (int)tex_w, (int)tex_h, - width, height); - } - } - } -#endif glBindVertexArray(r->disp_rndr.vao); glBindBuffer(GL_ARRAY_BUFFER, r->disp_rndr.vbo); glUseProgram(r->disp_rndr.prog); glProgramUniform1i(r->disp_rndr.prog, r->disp_rndr.tex_loc, 0); glUniform2f(r->disp_rndr.display_size_loc, width, height); glUniform1f(r->disp_rndr.line_offset_loc, line_offset); -#ifdef __ANDROID__ - glUniform1i(r->disp_rndr.clip_enable_loc, clip_enable ? 1 : 0); - glUniform4f(r->disp_rndr.clip_rect_loc, clip_x, clip_y, clip_w, clip_h); -#endif + glUniform1i(r->disp_rndr.clip_enable_loc, 0); render_display_pvideo_overlay(d); glViewport(0, 0, width, height); @@ -629,28 +517,30 @@ static void render_display(NV2AState *d, SurfaceBinding *surface) glDisable(GL_STENCIL_TEST); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); +#ifndef __ANDROID__ /* glPolygonMode not available in GLES 3.x core */ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); -#ifdef __ANDROID__ - if (android_force_cpu_blit()) { - android_store_readback(d, surface, width, height); - } -#endif - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); } static void gl_fence(void) { +#ifdef __ANDROID__ + /* Shared-context sync objects are still producing invalid-operation + * failures on Adreno. Favor correctness over throughput here. */ + glFinish(); +#else GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); int result = glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, (GLuint64)(5000000000)); assert(result == GL_CONDITION_SATISFIED || result == GL_ALREADY_SIGNALED); glDeleteSync(fence); +#endif } void pgraph_gl_sync(NV2AState *d) @@ -677,29 +567,27 @@ void pgraph_gl_sync(NV2AState *d) /* Wait for queued commands to complete */ #ifdef __ANDROID__ - bool force_upload = !tcg_enabled(); - if (android_force_cpu_blit() && surface->draw_time == 0) { - force_upload = true; - static unsigned int no_draw_log = 0; - if ((no_draw_log++ % 120) == 0) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "pgraph_gl_sync: forcing upload (no draws yet) vram=0x%x", - (unsigned)surface->vram_addr); - } - } - pgraph_gl_upload_surface_data(d, surface, force_upload); -#else - pgraph_gl_upload_surface_data(d, surface, !tcg_enabled()); + GL_ASSERT_NO_ERROR("pgraph_gl_sync: pre-upload"); #endif + pgraph_gl_upload_surface_data(d, surface, !tcg_enabled()); + GL_ASSERT_NO_ERROR("pgraph_gl_sync: surface upload"); gl_fence(); - GL_ASSERT_NO_ERROR("pgraph_gl_sync: upload fence"); + GL_ASSERT_NO_ERROR("pgraph_gl_sync: upload finish"); /* Render framebuffer */ #ifdef __ANDROID__ - glo_set_current(g_nv2a_context_render); - render_display(d, surface); - gl_fence(); - GL_ASSERT_NO_ERROR("pgraph_gl_sync: render fence"); + { + AndroidDisplayGLState state; + + glo_set_current(g_nv2a_context_render); + GL_ASSERT_NO_ERROR("pgraph_gl_sync: pre-render"); + android_display_capture_gl_state(&state); + render_display(d, surface); + GL_ASSERT_NO_ERROR("pgraph_gl_sync: render display"); + android_display_restore_gl_state(&state); + gl_fence(); + GL_ASSERT_NO_ERROR("pgraph_gl_sync: render finish"); + } #else /* Render framebuffer in display context */ glo_set_current(g_nv2a_context_display); diff --git a/hw/xbox/nv2a/pgraph/gl/draw.c b/hw/xbox/nv2a/pgraph/gl/draw.c index fe71cd1bd0..c802ddaa3c 100644 --- a/hw/xbox/nv2a/pgraph/gl/draw.c +++ b/hw/xbox/nv2a/pgraph/gl/draw.c @@ -163,7 +163,6 @@ void pgraph_gl_draw_begin(NV2AState *d) assert(r->color_binding || r->zeta_binding); pgraph_gl_bind_textures(d); - pgraph_gl_bind_shaders(pg); glColorMask(mask_red, mask_green, mask_blue, mask_alpha); glDepthMask(!!(control_0 & NV_PGRAPH_CONTROL_0_ZWRITEENABLE)); @@ -215,8 +214,10 @@ void pgraph_gl_draw_begin(NV2AState *d) /* Polygon offset is handled in geometry and fragment shaders explicitly */ glDisable(GL_POLYGON_OFFSET_FILL); +#ifndef __ANDROID__ /* GL_POLYGON_OFFSET_LINE/POINT not valid in GLES 3.x */ glDisable(GL_POLYGON_OFFSET_LINE); glDisable(GL_POLYGON_OFFSET_POINT); +#endif /* Depth testing */ if (depth_test) { @@ -234,8 +235,10 @@ void pgraph_gl_draw_begin(NV2AState *d) glEnable(GL_DEPTH_CLAMP); #endif +#ifndef __ANDROID__ /* glProvokingVertex not available in GLES 3.x */ /* Set first vertex convention to match Vulkan default */ glProvokingVertex(GL_FIRST_VERTEX_CONVENTION); +#endif if (stencil_test) { glEnable(GL_STENCIL_TEST); @@ -281,7 +284,11 @@ void pgraph_gl_draw_begin(NV2AState *d) glDisable(GL_DITHER); } +#ifndef __ANDROID__ + /* GL_PROGRAM_POINT_SIZE is not a valid enum in GLES 3.x; + * point size from gl_PointSize is always enabled in GLES. */ glEnable(GL_PROGRAM_POINT_SIZE); +#endif bool anti_aliasing = GET_MASK(pgraph_reg_r(pg, NV_PGRAPH_ANTIALIASING), NV_PGRAPH_ANTIALIASING_ENABLE); @@ -328,6 +335,9 @@ void pgraph_gl_draw_begin(NV2AState *d) glScissor(xmin, ymin, scissor_width, scissor_height); /* Visibility testing */ + /* GL_SAMPLES_PASSED is desktop-only; GLES has GL_ANY_SAMPLES_PASSED but + * only returns boolean. Skip occlusion queries entirely on Android. */ +#ifndef __ANDROID__ if (pg->zpass_pixel_count_enable) { r->gl_zpass_pixel_count_query_count++; r->gl_zpass_pixel_count_queries = (GLuint*)g_realloc( @@ -340,6 +350,7 @@ void pgraph_gl_draw_begin(NV2AState *d) r->gl_zpass_pixel_count_query_count - 1] = gl_query; glBeginQuery(GL_SAMPLES_PASSED, gl_query); } +#endif } void pgraph_gl_draw_end(NV2AState *d) @@ -373,10 +384,12 @@ void pgraph_gl_draw_end(NV2AState *d) pgraph_gl_flush_draw(d); /* End of visibility testing */ +#ifndef __ANDROID__ if (pg->zpass_pixel_count_enable) { nv2a_profile_inc_counter(NV2A_PROF_QUERY); glEndQuery(GL_SAMPLES_PASSED); } +#endif pg->draw_time++; #ifdef __ANDROID__ @@ -400,7 +413,6 @@ void pgraph_gl_flush_draw(NV2AState *d) if (!(r->color_binding || r->zeta_binding)) { return; } - assert(r->shader_binding); PrimAssemblyState assembly = { .primitive_mode = pg->primitive_mode, @@ -426,6 +438,7 @@ void pgraph_gl_flush_draw(NV2AState *d) pg->draw_arrays_max_count - 1, false, 0, pg->draw_arrays_max_count - 1); + pgraph_gl_bind_shaders(pg); PrimRewrite prim_rw = pgraph_prim_rewrite_ranges( &r->prim_rewrite_buf, assembly, pg->draw_arrays_start, @@ -469,6 +482,7 @@ void pgraph_gl_flush_draw(NV2AState *d) pgraph_gl_bind_vertex_attributes( d, min_element, max_element, false, 0, draw_indices[draw_index_count - 1]); + pgraph_gl_bind_shaders(pg); if (prim_rw.num_indices > 0) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, r->gl_prim_rewrite_buffer); @@ -508,10 +522,12 @@ void pgraph_gl_flush_draw(NV2AState *d) nv2a_profile_inc_counter(NV2A_PROF_INLINE_BUFFERS); assert(pg->inline_array_length == 0); - if (pg->compressed_attrs) { + if (pg->compressed_attrs || pg->swizzle_attrs || pg->uniform_attrs) { pg->compressed_attrs = 0; - pgraph_gl_bind_shaders(pg); + pg->uniform_attrs = 0; + pg->swizzle_attrs = 0; } + pgraph_gl_bind_shaders(pg); for (int i = 0; i < NV2A_VERTEXSHADER_ATTRIBUTES; i++) { VertexAttribute *attr = &pg->vertex_attributes[i]; @@ -552,6 +568,7 @@ void pgraph_gl_flush_draw(NV2AState *d) nv2a_profile_inc_counter(NV2A_PROF_INLINE_ARRAYS); unsigned int index_count = pgraph_gl_bind_inline_array(d); + pgraph_gl_bind_shaders(pg); PrimRewrite prim_rw = pgraph_prim_rewrite_sequential( &r->prim_rewrite_buf, assembly, 0, index_count); diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.c b/hw/xbox/nv2a/pgraph/gl/renderer.c index 8cda79de36..cdc094a177 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.c +++ b/hw/xbox/nv2a/pgraph/gl/renderer.c @@ -126,7 +126,12 @@ static void pgraph_gl_init(NV2AState *d, Error **errp) assert(glo_check_extension("GL_ARB_ES2_compatibility")); #endif +#ifndef __ANDROID__ /* GL_SMOOTH_LINE_WIDTH_RANGE not valid in GLES 3.x */ glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, r->supported_smooth_line_width_range); +#else + r->supported_smooth_line_width_range[0] = 1.0f; + r->supported_smooth_line_width_range[1] = 1.0f; +#endif glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, r->supported_aliased_line_width_range); pgraph_gl_init_surfaces(pg); @@ -143,6 +148,14 @@ static void pgraph_gl_init(NV2AState *d, Error **errp) r->supported_extensions.texture_filter_anisotropic = glo_check_extension("GL_EXT_texture_filter_anisotropic"); + r->supported_extensions.max_texture_max_anisotropy = 1.0f; + if (r->supported_extensions.texture_filter_anisotropic) { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, + &r->supported_extensions.max_texture_max_anisotropy); + if (r->supported_extensions.max_texture_max_anisotropy < 1.0f) { + r->supported_extensions.max_texture_max_anisotropy = 1.0f; + } + } } static void pgraph_gl_finalize(NV2AState *d) diff --git a/hw/xbox/nv2a/pgraph/gl/renderer.h b/hw/xbox/nv2a/pgraph/gl/renderer.h index 9dd007e8be..a800181365 100644 --- a/hw/xbox/nv2a/pgraph/gl/renderer.h +++ b/hw/xbox/nv2a/pgraph/gl/renderer.h @@ -242,6 +242,7 @@ typedef struct PGRAPHGLState { struct supported_extensions { GLboolean texture_filter_anisotropic; + GLfloat max_texture_max_anisotropy; } supported_extensions; #ifdef __ANDROID__ diff --git a/hw/xbox/nv2a/pgraph/gl/shaders.c b/hw/xbox/nv2a/pgraph/gl/shaders.c index 3240f31744..015c82437b 100644 --- a/hw/xbox/nv2a/pgraph/gl/shaders.c +++ b/hw/xbox/nv2a/pgraph/gl/shaders.c @@ -29,6 +29,10 @@ #include "debug.h" #include "renderer.h" +#ifdef __ANDROID__ +#include +#endif + static GLenum get_gl_primitive_mode(enum ShaderPrimitiveMode primitive_mode) { switch (primitive_mode) { @@ -41,6 +45,31 @@ static GLenum get_gl_primitive_mode(enum ShaderPrimitiveMode primitive_mode) } } +static void log_shader_source_with_line_numbers(const char *name, + const char *code) +{ + int line_no = 1; + const char *line = code; + + while (line && *line) { + const char *line_end = strchr(line, '\n'); + size_t len = line_end ? (size_t)(line_end - line) : strlen(line); + +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "xemu-android", + "%s %4d | %.*s", name, line_no, (int)len, line); +#endif + fprintf(stderr, "%s %4d | %.*s\n", name, line_no, (int)len, line); + + if (!line_end) { + break; + } + + line = line_end + 1; + line_no++; + } +} + static GLuint create_gl_shader(GLenum gl_shader_type, const char *code, const char *name) @@ -64,7 +93,12 @@ static GLuint create_gl_shader(GLenum gl_shader_type, glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); log = g_malloc(log_length * sizeof(GLchar)); glGetShaderInfoLog(shader, log_length, NULL, log); - fprintf(stderr, "%s\n\n" "nv2a: %s compilation failed: %s\n", code, name, log); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "xemu-android", + "nv2a: %s compilation failed: %s", name, log); +#endif + fprintf(stderr, "nv2a: %s compilation failed: %s\n", name, log); + log_shader_source_with_line_numbers(name, code); g_free(log); NV2A_GL_DGROUP_END(); diff --git a/hw/xbox/nv2a/pgraph/gl/surface.c b/hw/xbox/nv2a/pgraph/gl/surface.c index 4aca9e551a..e99c43a45c 100644 --- a/hw/xbox/nv2a/pgraph/gl/surface.c +++ b/hw/xbox/nv2a/pgraph/gl/surface.c @@ -34,23 +34,321 @@ static void android_sanitize_surface_format(PGRAPHGLState *r, SurfaceFormatInfo *fmt) { - if (!r || !fmt) { - return; + /* Android keeps the guest surface format metadata intact and converts + * unsupported BGRA guest layouts at upload/readback time instead. */ + (void)r; + (void)fmt; +} + +static bool android_surface_uses_rgba8_transfer(const SurfaceBinding *surface) +{ + if (!surface || !surface->color) { + return false; } - if (fmt->gl_format == GL_BGRA) { - if (r->bgra_supported) { - // GLES BGRA path: force a byte-based type which is widely supported. - fmt->gl_type = GL_UNSIGNED_BYTE; - } else { - fmt->gl_format = GL_RGBA; - fmt->gl_type = GL_UNSIGNED_BYTE; + + switch (surface->shape.color_format) { + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: + case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: + return true; + default: + return false; + } +} + +static void android_surface_get_storage_format(const SurfaceBinding *surface, + GLint *internal_format, + GLenum *format, + GLenum *type) +{ + *internal_format = surface->fmt.gl_internal_format; + *format = surface->fmt.gl_format; + *type = surface->fmt.gl_type; + + if (android_surface_uses_rgba8_transfer(surface)) { + *internal_format = GL_RGBA8; + *format = GL_RGBA; + *type = GL_UNSIGNED_BYTE; + } +} + +static uint8_t android_expand_5_to_8(uint8_t value) +{ + return (value << 3) | (value >> 2); +} + +static void android_surface_guest_to_rgba8(const SurfaceBinding *surface, + const uint8_t *src, + unsigned int width, + unsigned int height, + unsigned int src_stride, + uint8_t *dst) +{ + unsigned int x, y; + + switch (surface->shape.color_format) { + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: + for (y = 0; y < height; y++) { + const uint8_t *src_row = src + y * src_stride; + uint8_t *dst_row = dst + y * width * 4; + for (x = 0; x < width; x++) { + uint16_t pixel = lduw_le_p(src_row + x * 2); + dst_row[x * 4 + 0] = android_expand_5_to_8((pixel >> 10) & 0x1F); + dst_row[x * 4 + 1] = android_expand_5_to_8((pixel >> 5) & 0x1F); + dst_row[x * 4 + 2] = android_expand_5_to_8(pixel & 0x1F); + dst_row[x * 4 + 3] = 0xFF; + } } - if (fmt->gl_internal_format == GL_RGB8) { - fmt->gl_internal_format = GL_RGBA8; + break; + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: + case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: + for (y = 0; y < height; y++) { + const uint8_t *src_row = src + y * src_stride; + uint8_t *dst_row = dst + y * width * 4; + for (x = 0; x < width; x++) { + const uint8_t *pixel = src_row + x * 4; + dst_row[x * 4 + 0] = pixel[2]; + dst_row[x * 4 + 1] = pixel[1]; + dst_row[x * 4 + 2] = pixel[0]; + dst_row[x * 4 + 3] = + (surface->shape.color_format == + NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8) + ? pixel[3] + : 0xFF; + } } - } else if (fmt->gl_format == GL_BGR) { - fmt->gl_format = GL_RGB; - fmt->gl_type = GL_UNSIGNED_BYTE; + break; + default: + g_assert_not_reached(); + } +} + +static bool android_surface_to_texture_needs_guest_reinterpretation( + const SurfaceBinding *surface, + const TextureShape *shape) +{ + switch (surface->shape.color_format) { + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: + return shape->color_format != + NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5 && + shape->color_format != + NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5; + case NV097_SET_SURFACE_FORMAT_COLOR_LE_R5G6B5: + return false; + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: + return shape->color_format != + NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8 && + shape->color_format != + NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8; + case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: + return shape->color_format != + NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8 && + shape->color_format != + NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8; + default: + return true; + } +} + +static void android_surface_guest_to_texture_rgba8( + const TextureShape *shape, + const uint8_t *src, + unsigned int width, + unsigned int height, + unsigned int src_stride, + uint8_t *dst) +{ + unsigned int x, y; + + for (y = 0; y < height; y++) { + const uint8_t *src_row = src + y * src_stride; + uint8_t *dst_row = dst + y * width * 4; + + for (x = 0; x < width; x++) { + uint8_t *out = dst_row + x * 4; + + switch (shape->color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5: { + uint16_t pixel = lduw_le_p(src_row + x * 2); + out[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); + out[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); + out[2] = android_expand_5_to_8(pixel & 0x1F); + out[3] = (pixel & 0x8000) ? 0xFF : 0x00; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5: { + uint16_t pixel = lduw_le_p(src_row + x * 2); + out[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); + out[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); + out[2] = android_expand_5_to_8(pixel & 0x1F); + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: { + uint16_t pixel = lduw_le_p(src_row + x * 2); + out[0] = android_expand_5_to_8((pixel >> 11) & 0x1F); + out[1] = (uint8_t)(((pixel >> 5) & 0x3F) * 255 / 63); + out[2] = android_expand_5_to_8(pixel & 0x1F); + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_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_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[2]; + out[1] = pixel[1]; + out[2] = pixel[0]; + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[0]; + out[1] = pixel[1]; + out[2] = pixel[2]; + out[3] = pixel[3]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[1]; + out[1] = pixel[2]; + out[2] = pixel[3]; + out[3] = pixel[0]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[3]; + out[1] = pixel[2]; + out[2] = pixel[1]; + out[3] = pixel[0]; + break; + } + default: + g_assert_not_reached(); + } + } + } +} + +static void android_surface_rgba8_to_guest(const SurfaceBinding *surface, + const uint8_t *src, + unsigned int src_stride, + unsigned int width, + unsigned int height, + uint8_t *dst, + unsigned int dst_stride) +{ + unsigned int x, y; + + switch (surface->shape.color_format) { + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: + for (y = 0; y < height; y++) { + const uint8_t *src_row = src + y * src_stride; + uint8_t *dst_row = dst + y * dst_stride; + for (x = 0; x < width; x++) { + const uint8_t *pixel = src_row + x * 4; + uint16_t packed = + 0x8000 | + ((uint16_t)(pixel[0] >> 3) << 10) | + ((uint16_t)(pixel[1] >> 3) << 5) | + (uint16_t)(pixel[2] >> 3); + stw_le_p(dst_row + x * 2, packed); + } + } + break; + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: + case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: + for (y = 0; y < height; y++) { + const uint8_t *src_row = src + y * src_stride; + uint8_t *dst_row = dst + y * dst_stride; + for (x = 0; x < width; x++) { + const uint8_t *pixel = src_row + x * 4; + uint8_t *out = dst_row + x * 4; + out[0] = pixel[2]; + out[1] = pixel[1]; + out[2] = pixel[0]; + out[3] = + (surface->shape.color_format == + NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8) + ? pixel[3] + : 0xFF; + } + } + break; + default: + g_assert_not_reached(); + } +} + +static bool android_surface_to_texture_rgba8_compatible( + const SurfaceBinding *surface, + const TextureShape *shape) +{ + if (!surface || !shape || !surface->color || shape->cubemap || + shape->levels > 1) { + return false; + } + + switch (surface->shape.color_format) { + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: + switch (shape->color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5: + return true; + default: + return false; + } + case NV097_SET_SURFACE_FORMAT_COLOR_LE_R5G6B5: + switch (shape->color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R5G6B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R5G6B5: + return true; + default: + return false; + } + case NV097_SET_SURFACE_FORMAT_COLOR_LE_X8R8G8B8_Z8R8G8B8: + switch (shape->color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: + return true; + default: + return false; + } + case NV097_SET_SURFACE_FORMAT_COLOR_LE_A8R8G8B8: + switch (shape->color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: + return true; + default: + return false; + } + default: + return false; } } #endif @@ -225,6 +523,12 @@ static bool surface_to_texture_can_fastpath(SurfaceBinding *surface, return false; } +#ifdef __ANDROID__ + if (android_surface_to_texture_rgba8_compatible(surface, shape)) { + return true; + } +#endif + switch (surface_fmt) { case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: switch (texture_fmt) { case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: return true; @@ -339,7 +643,9 @@ static bool render_surface_to(NV2AState *d, SurfaceBinding *surface, glDisable(GL_STENCIL_TEST); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); +#ifndef __ANDROID__ /* glPolygonMode not available in GLES 3.x core */ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); @@ -369,6 +675,30 @@ static void render_surface_to_texture_slow(NV2AState *d, glActiveTexture(GL_TEXTURE0 + texture_unit); glBindTexture(texture->gl_target, texture->gl_texture); +#ifdef __ANDROID__ + if (android_surface_to_texture_rgba8_compatible(surface, texture_shape) && + !android_surface_to_texture_needs_guest_reinterpretation(surface, + texture_shape) && + texture->gl_target == GL_TEXTURE_2D) { + unsigned int tex_width = texture_shape->width; + unsigned int tex_height = texture_shape->height; + + pgraph_apply_scaling_factor(pg, &tex_width, &tex_height); + + glTexImage2D(texture->gl_target, 0, GL_RGBA8, tex_width, tex_height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glBindTexture(texture->gl_target, 0); + + if (render_surface_to(d, surface, texture_unit, texture->gl_target, + texture->gl_texture, tex_width, tex_height)) { + glBindTexture(texture->gl_target, texture->gl_texture); + return; + } + + glBindTexture(texture->gl_target, texture->gl_texture); + } +#endif + unsigned int width = surface->width, height = surface->height; pgraph_apply_scaling_factor(pg, &width, &height); @@ -382,6 +712,24 @@ static void render_surface_to_texture_slow(NV2AState *d, height = texture_shape->height; pgraph_apply_scaling_factor(pg, &width, &height); +#ifdef __ANDROID__ + if (android_surface_to_texture_rgba8_compatible(surface, texture_shape)) { + uint8_t *upload_tmp = g_malloc(width * height * 4); + if (android_surface_to_texture_needs_guest_reinterpretation( + surface, texture_shape)) { + android_surface_guest_to_texture_rgba8( + texture_shape, buf, width, height, + width * surface->fmt.bytes_per_pixel, upload_tmp); + } else { + android_surface_guest_to_rgba8(surface, buf, width, height, + width * surface->fmt.bytes_per_pixel, + upload_tmp); + } + glTexImage2D(texture->gl_target, 0, GL_RGBA8, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, upload_tmp); + g_free(upload_tmp); + } else +#endif glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0, f->gl_format, f->gl_type, buf); g_free(buf); @@ -420,6 +768,14 @@ void pgraph_gl_render_surface_to_texture(NV2AState *d, SurfaceBinding *surface, glTexParameteri(texture->gl_target, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(texture->gl_target, GL_TEXTURE_MAX_LEVEL, 0); glTexParameteri(texture->gl_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +#ifdef __ANDROID__ + if (android_surface_to_texture_rgba8_compatible(surface, texture_shape) && + !android_surface_to_texture_needs_guest_reinterpretation(surface, + texture_shape)) { + glTexImage2D(texture->gl_target, 0, GL_RGBA8, width, height, 0, + GL_RGBA, GL_UNSIGNED_BYTE, NULL); + } else +#endif glTexImage2D(texture->gl_target, 0, f->gl_internal_format, width, height, 0, f->gl_format, f->gl_type, NULL); glBindTexture(texture->gl_target, 0); @@ -464,6 +820,12 @@ bool pgraph_gl_check_surface_to_texture_compatibility( return false; } +#ifdef __ANDROID__ + if (android_surface_to_texture_rgba8_compatible(surface, shape)) { + return true; + } +#endif + switch (surface_fmt) { case NV097_SET_SURFACE_FORMAT_COLOR_LE_X1R5G5B5_Z1R5G5B5: switch (texture_fmt) { case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: return true; @@ -871,6 +1233,50 @@ static void surface_download_to_buffer(NV2AState *d, SurfaceBinding *surface, } /* Read surface into memory */ +#ifdef __ANDROID__ + if (android_surface_uses_rgba8_transfer(surface)) { + const unsigned int factor = downscale ? pg->surface_scale_factor : 1; + const unsigned int read_width = surface->width * factor; + const unsigned int read_height = surface->height * factor; + const unsigned int rgba_stride = read_width * 4; + uint8_t *rgba_pixels = g_malloc(read_height * rgba_stride); + uint8_t *rgba_linear = rgba_pixels; + uint8_t *linear_guest = pixels; + + glo_readpixels(GL_RGBA, GL_UNSIGNED_BYTE, 4, rgba_stride, + read_width, read_height, flip, rgba_pixels); + + if (downscale) { + rgba_linear = g_malloc(surface->width * surface->height * 4); + for (unsigned int y = 0; y < surface->height; y++) { + surface_copy_shrink_row(rgba_linear + y * surface->width * 4, + rgba_pixels + y * rgba_stride * factor, + surface->width, 4, factor); + } + } + + if (swizzle) { + linear_guest = g_malloc(surface->size); + } + + android_surface_rgba8_to_guest(surface, rgba_linear, surface->width * 4, + surface->width, surface->height, + linear_guest, surface->pitch); + + if (swizzle) { + swizzle_rect(linear_guest, surface->width, surface->height, pixels, + surface->pitch, surface->fmt.bytes_per_pixel); + g_free(linear_guest); + } + + if (rgba_linear != rgba_pixels) { + g_free(rgba_linear); + } + g_free(rgba_pixels); + goto cleanup; + } +#endif + uint8_t *gl_read_buf = pixels; uint8_t *swizzle_buf = pixels; @@ -1117,38 +1523,31 @@ void pgraph_gl_upload_surface_data(NV2AState *d, SurfaceBinding *surface, } glBindTexture(GL_TEXTURE_2D, surface->gl_buffer); +#ifdef __ANDROID__ + { + const uint8_t *upload_buf = gl_read_buf; + uint8_t *upload_tmp = NULL; + GLint upload_ifmt; + GLenum upload_fmt; + GLenum upload_type; + + android_surface_get_storage_format(surface, &upload_ifmt, &upload_fmt, + &upload_type); + if (android_surface_uses_rgba8_transfer(surface)) { + upload_tmp = g_malloc(width * height * 4); + android_surface_guest_to_rgba8(surface, gl_read_buf, width, height, + width * surface->fmt.bytes_per_pixel, + upload_tmp); + upload_buf = upload_tmp; + } + glTexImage2D(GL_TEXTURE_2D, 0, upload_ifmt, width, height, 0, + upload_fmt, upload_type, upload_buf); + g_free(upload_tmp); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, surface->fmt.gl_internal_format, width, height, 0, surface->fmt.gl_format, surface->fmt.gl_type, gl_read_buf); -#ifdef __ANDROID__ - { - static unsigned int upload_fail_log = 0; - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - if (upload_fail_log++ < 20) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "surface upload glTexImage2D err=0x%X ifmt=%d fmt=%d type=%d size=%ux%u bpp=%u vram=0x%x", - err, - surface->fmt.gl_internal_format, - surface->fmt.gl_format, - surface->fmt.gl_type, - width, height, - surface->fmt.bytes_per_pixel, - (unsigned)surface->vram_addr); - } - if (surface->fmt.bytes_per_pixel == 4) { - // Fallback to a byte-based RGBA upload to avoid invalid enum combos. - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, gl_read_buf); - err = glGetError(); - if (err != GL_NO_ERROR && upload_fail_log++ < 20) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "surface upload fallback err=0x%X", - err); - } - } - } - } #endif glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); if (optimal_buf != buf) { @@ -1419,9 +1818,22 @@ static void update_surface_part(NV2AState *d, bool upload, bool color) unsigned int width = entry.width ? entry.width : 1; unsigned int height = entry.height ? entry.height : 1; pgraph_apply_scaling_factor(pg, &width, &height); +#ifdef __ANDROID__ + { + GLint storage_ifmt; + GLenum storage_fmt; + GLenum storage_type; + + android_surface_get_storage_format(&entry, &storage_ifmt, + &storage_fmt, &storage_type); + glTexImage2D(GL_TEXTURE_2D, 0, storage_ifmt, width, height, 0, + storage_fmt, storage_type, NULL); + } +#else glTexImage2D(GL_TEXTURE_2D, 0, entry.fmt.gl_internal_format, width, height, 0, entry.fmt.gl_format, entry.fmt.gl_type, NULL); +#endif found = surface_put(d, entry.vram_addr, &entry); /* FIXME: Refactor */ diff --git a/hw/xbox/nv2a/pgraph/gl/texture.c b/hw/xbox/nv2a/pgraph/gl/texture.c index 27bb310de8..531cb0591b 100644 --- a/hw/xbox/nv2a/pgraph/gl/texture.c +++ b/hw/xbox/nv2a/pgraph/gl/texture.c @@ -27,6 +27,275 @@ #include "debug.h" #include "renderer.h" +#ifdef __ANDROID__ +static uint8_t android_expand_4_to_8(uint8_t value) +{ + return (value << 4) | value; +} + +static uint8_t android_expand_5_to_8(uint8_t value) +{ + return (value << 3) | (value >> 2); +} + +static bool android_texture_needs_rgba8_upload(const TextureShape s) +{ + switch (s.color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_AY8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A4R4G4B4: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_AY8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A4R4G4B4: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: + return true; + default: + return false; + } +} + +static bool android_texture_rgba8_upload_applies_swizzle(const TextureShape s) +{ + switch (s.color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_AY8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_AY8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8B8: + return true; + default: + return false; + } +} + +static unsigned int android_texture_source_bpp(const TextureShape s, + unsigned int default_bpp, + bool has_converted_data) +{ + if (has_converted_data && + s.color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_I8_A8R8G8B8) { + return 4; + } + return default_bpp; +} + +static void android_texture_convert_to_rgba8(const TextureShape s, + const uint8_t *src, + unsigned int width, + unsigned int height, + unsigned int depth, + unsigned int row_pitch, + unsigned int slice_pitch, + uint8_t *dst) +{ + unsigned int x, y, z; + + for (z = 0; z < depth; z++) { + const uint8_t *src_slice = src + z * slice_pitch; + uint8_t *dst_slice = dst + z * width * height * 4; + + for (y = 0; y < height; y++) { + const uint8_t *src_row = src_slice + y * row_pitch; + uint8_t *dst_row = dst_slice + y * width * 4; + + for (x = 0; x < width; x++) { + uint8_t *out = dst_row + x * 4; + + switch (s.color_format) { + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_Y8: { + uint8_t value = src_row[x]; + out[0] = value; + out[1] = value; + out[2] = value; + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_AY8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_AY8: { + uint8_t value = src_row[x]; + out[0] = value; + out[1] = value; + out[2] = value; + out[3] = value; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A1R5G5B5: { + uint16_t pixel = lduw_le_p(src_row + x * 2); + out[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); + out[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); + out[2] = android_expand_5_to_8(pixel & 0x1F); + out[3] = (pixel & 0x8000) ? 0xFF : 0x00; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_X1R5G5B5: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X1R5G5B5: { + uint16_t pixel = lduw_le_p(src_row + x * 2); + out[0] = android_expand_5_to_8((pixel >> 10) & 0x1F); + out[1] = android_expand_5_to_8((pixel >> 5) & 0x1F); + out[2] = android_expand_5_to_8(pixel & 0x1F); + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A4R4G4B4: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A4R4G4B4: { + uint16_t pixel = lduw_le_p(src_row + x * 2); + out[0] = android_expand_4_to_8((pixel >> 8) & 0x0F); + out[1] = android_expand_4_to_8((pixel >> 4) & 0x0F); + out[2] = android_expand_4_to_8(pixel & 0x0F); + out[3] = android_expand_4_to_8((pixel >> 12) & 0x0F); + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8: { + uint8_t value = src_row[x]; + out[0] = 0xFF; + out[1] = 0xFF; + out[2] = 0xFF; + out[3] = value; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8Y8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8Y8: { + const uint8_t *pixel = src_row + x * 2; + out[0] = pixel[0]; + out[1] = pixel[0]; + out[2] = pixel[0]; + out[3] = pixel[1]; + break; + } + 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_SZ_X8R8G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_X8R8G8B8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[2]; + out[1] = pixel[1]; + out[2] = pixel[0]; + out[3] = 0xFF; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_B8G8R8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_B8G8R8A8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[1]; + out[1] = pixel[2]; + out[2] = pixel[3]; + out[3] = pixel[0]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_A8B8G8R8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_A8B8G8R8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[0]; + out[1] = pixel[1]; + out[2] = pixel[2]; + out[3] = pixel[3]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8G8B8A8: + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_R8G8B8A8: { + const uint8_t *pixel = src_row + x * 4; + out[0] = pixel[3]; + out[1] = pixel[2]; + out[2] = pixel[1]; + out[3] = pixel[0]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_LU_IMAGE_G8B8: + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_G8B8: { + const uint8_t *pixel = src_row + x * 2; + out[0] = pixel[0]; + out[1] = pixel[1]; + out[2] = pixel[0]; + out[3] = pixel[1]; + break; + } + case NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R8B8: { + const uint8_t *pixel = src_row + x * 2; + out[0] = pixel[1]; + out[1] = pixel[0]; + out[2] = pixel[0]; + out[3] = pixel[1]; + break; + } + default: + g_assert_not_reached(); + } + } + } + } +} + +static void android_prepare_tex_upload(const TextureShape s, + const uint8_t *src, + unsigned int width, + unsigned int height, + unsigned int depth, + unsigned int row_pitch, + unsigned int slice_pitch, + GLint *ifmt, + GLenum *fmt, + GLenum *type, + const uint8_t **upload_data, + uint8_t **upload_tmp) +{ + *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; + + if (!android_texture_needs_rgba8_upload(s)) { + return; + } + + *upload_tmp = g_malloc(width * height * depth * 4); + android_texture_convert_to_rgba8(s, src, width, height, depth, + row_pitch, slice_pitch, *upload_tmp); + *upload_data = *upload_tmp; + *ifmt = GL_RGBA8; + *fmt = GL_RGBA; + *type = GL_UNSIGNED_BYTE; +} +#endif + static TextureBinding* generate_texture(const TextureShape s, const uint8_t *texture_data, const uint8_t *palette_data); static void texture_binding_destroy(gpointer data); @@ -163,16 +432,28 @@ static void apply_texture_parameters(PGRAPHGLState *r, /* Texture wrapping */ assert(addru < ARRAY_SIZE(pgraph_texture_addr_gl_map)); if (addru != binding->addru) { + GLenum wrap_s = pgraph_texture_addr_gl_map[addru]; +#ifdef __ANDROID__ + if (wrap_s == GL_CLAMP_TO_BORDER) { + wrap_s = GL_CLAMP_TO_EDGE; + } +#endif glTexParameteri(binding->gl_target, GL_TEXTURE_WRAP_S, - pgraph_texture_addr_gl_map[addru]); + wrap_s); binding->addru = addru; } bool needs_border_color = binding->addru == NV_PGRAPH_TEXADDRESS0_ADDRU_BORDER; if (dimensionality > 1) { if (addrv != binding->addrv) { assert(addrv < ARRAY_SIZE(pgraph_texture_addr_gl_map)); + GLenum wrap_t = pgraph_texture_addr_gl_map[addrv]; +#ifdef __ANDROID__ + if (wrap_t == GL_CLAMP_TO_BORDER) { + wrap_t = GL_CLAMP_TO_EDGE; + } +#endif glTexParameteri(binding->gl_target, GL_TEXTURE_WRAP_T, - pgraph_texture_addr_gl_map[addrv]); + wrap_t); binding->addrv = addrv; } needs_border_color = needs_border_color || binding->addrv == NV_PGRAPH_TEXADDRESS0_ADDRU_BORDER; @@ -180,18 +461,33 @@ static void apply_texture_parameters(PGRAPHGLState *r, if (dimensionality > 2) { if (addrp != binding->addrp) { assert(addrp < ARRAY_SIZE(pgraph_texture_addr_gl_map)); + GLenum wrap_r = pgraph_texture_addr_gl_map[addrp]; +#ifdef __ANDROID__ + if (wrap_r == GL_CLAMP_TO_BORDER) { + wrap_r = GL_CLAMP_TO_EDGE; + } +#endif glTexParameteri(binding->gl_target, GL_TEXTURE_WRAP_R, - pgraph_texture_addr_gl_map[addrp]); + wrap_r); binding->addrp = addrp; } needs_border_color = needs_border_color || binding->addrp == NV_PGRAPH_TEXADDRESS0_ADDRU_BORDER; } if (r->supported_extensions.texture_filter_anisotropic) { + GLfloat clamped_anisotropy = MIN( + (GLfloat)max_anisotropy, + r->supported_extensions.max_texture_max_anisotropy); + if (clamped_anisotropy < 1.0f) { + clamped_anisotropy = 1.0f; + } glTexParameterf(binding->gl_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, - max_anisotropy); + clamped_anisotropy); } +#ifdef __ANDROID__ + needs_border_color = false; +#endif if (!is_bordered && needs_border_color) { if (!binding->border_color_set || binding->border_color != border_color) { /* FIXME: Color channels might be wrong order */ @@ -434,6 +730,13 @@ static void upload_gl_texture(GLenum gl_target, { ColorFormatInfo f = kelvin_color_format_gl_map[s.color_format]; nv2a_profile_inc_counter(NV2A_PROF_TEX_UPLOAD); +#ifdef __ANDROID__ + GLint prev_unpack_alignment; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &prev_unpack_alignment); + if (prev_unpack_alignment != 1) { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + } +#endif unsigned int adjusted_width = s.width; unsigned int adjusted_height = s.height; @@ -458,18 +761,45 @@ static void upload_gl_texture(GLenum gl_target, uint8_t *converted = pgraph_convert_texture_data( s, texture_data, palette_data, adjusted_width, adjusted_height, 1, adjusted_pitch, 0, NULL); - glPixelStorei(GL_UNPACK_ROW_LENGTH, - converted ? 0 : adjusted_pitch / f.bytes_per_pixel); - glTexImage2D(GL_TEXTURE_2D, 0, f.gl_internal_format, - adjusted_width, adjusted_height, 0, - f.gl_format, f.gl_type, - converted ? converted : texture_data); + { + 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, + converted != NULL); +#else + f.bytes_per_pixel; +#endif + unsigned int row_pitch = + converted ? adjusted_width * source_bpp : adjusted_pitch; + 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, adjusted_width, + adjusted_height, 1, row_pitch, + row_pitch * adjusted_height, + &tex_ifmt, &tex_fmt, &tex_type, + &upload_data, &upload_tmp); +#endif + glPixelStorei(GL_UNPACK_ROW_LENGTH, + upload_data == texture_data ? + adjusted_pitch / f.bytes_per_pixel : 0); + glTexImage2D(GL_TEXTURE_2D, 0, tex_ifmt, + adjusted_width, adjusted_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); } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); break; } /* fallthru */ @@ -513,8 +843,10 @@ static void upload_gl_texture(GLenum gl_target, } } - glTexImage2D(gl_target, level, GL_RGBA, tex_width, tex_height, 0, - GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, converted); + /* s3tc_decompress_2d outputs RGBA8 data; use GL_UNSIGNED_BYTE + * which is valid on both desktop GL and GLES. */ + 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) { glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); @@ -533,25 +865,51 @@ static void upload_gl_texture(GLenum gl_target, uint8_t *converted = pgraph_convert_texture_data( s, unswizzled, palette_data, width, height, 1, pitch, 0, NULL); - uint8_t *pixel_data = converted ? converted : unswizzled; + const uint8_t *pixel_data = converted ? converted : unswizzled; unsigned int tex_width = width; unsigned int tex_height = height; + unsigned int source_bpp = +#ifdef __ANDROID__ + android_texture_source_bpp(s, f.bytes_per_pixel, + converted != NULL); +#else + f.bytes_per_pixel; +#endif + unsigned int row_pitch = width * source_bpp; if (s.cubemap && adjusted_width != s.width) { // 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. - glPixelStorei(GL_UNPACK_ROW_LENGTH, adjusted_width); tex_width = s.width; tex_height = s.height; - pixel_data += 4 * f.bytes_per_pixel + 4 * pitch; + pixel_data += 4 * source_bpp + 4 * row_pitch; } - glTexImage2D(gl_target, level, f.gl_internal_format, tex_width, - tex_height, 0, f.gl_format, f.gl_type, - pixel_data); - if (s.cubemap && s.border) { + { + 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, + tex_height, 1, row_pitch, + row_pitch * tex_height, + &tex_ifmt, &tex_fmt, &tex_type, + &upload_data, &upload_tmp); +#endif + glPixelStorei(GL_UNPACK_ROW_LENGTH, + upload_data == pixel_data && + row_pitch != tex_width * source_bpp ? + row_pitch / source_bpp : 0); + glTexImage2D(gl_target, level, tex_ifmt, tex_width, + 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); @@ -597,9 +955,10 @@ static void upload_gl_texture(GLenum gl_target, gl_internal_format_to_s3tc_enum(f.gl_internal_format), texture_data, width, height, depth); - glTexImage3D(gl_target, level, GL_RGBA8, + /* s3tc_decompress_3d outputs RGBA8; use GL_UNSIGNED_BYTE. */ + glTexImage3D(gl_target, level, GL_RGBA8, width, height, depth, 0, - GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + GL_RGBA, GL_UNSIGNED_BYTE, converted); g_free(converted); @@ -620,10 +979,37 @@ static void upload_gl_texture(GLenum gl_target, s, unswizzled, palette_data, width, height, depth, row_pitch, slice_pitch, NULL); - glTexImage3D(gl_target, level, f.gl_internal_format, - width, height, depth, 0, - f.gl_format, f.gl_type, - converted ? converted : unswizzled); + { + 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, + converted != NULL); +#else + f.bytes_per_pixel; +#endif + unsigned int upload_row_pitch = width * source_bpp; + unsigned int upload_slice_pitch = upload_row_pitch * height; + 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, width, height, + depth, upload_row_pitch, + upload_slice_pitch, &tex_ifmt, + &tex_fmt, &tex_type, + &upload_data, &upload_tmp); +#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) { g_free(converted); @@ -643,6 +1029,9 @@ static void upload_gl_texture(GLenum gl_target, assert(false); break; } +#ifdef __ANDROID__ + glPixelStorei(GL_UNPACK_ALIGNMENT, prev_unpack_alignment); +#endif } static TextureBinding* generate_texture(const TextureShape s, @@ -746,8 +1135,14 @@ static TextureBinding* generate_texture(const TextureShape s, s.levels - 1); } - if (f.gl_swizzle_mask[0] != 0 || f.gl_swizzle_mask[1] != 0 - || f.gl_swizzle_mask[2] != 0 || f.gl_swizzle_mask[3] != 0) { + bool apply_swizzle = f.gl_swizzle_mask[0] != 0 || f.gl_swizzle_mask[1] != 0 + || f.gl_swizzle_mask[2] != 0 || f.gl_swizzle_mask[3] != 0; +#ifdef __ANDROID__ + if (apply_swizzle && android_texture_rgba8_upload_applies_swizzle(s)) { + apply_swizzle = false; + } +#endif + if (apply_swizzle) { glTexParameteriv(gl_target, GL_TEXTURE_SWIZZLE_RGBA, (const GLint *)f.gl_swizzle_mask); } diff --git a/hw/xbox/nv2a/pgraph/gl/vertex.c b/hw/xbox/nv2a/pgraph/gl/vertex.c index 8563a50fb1..91919dc179 100644 --- a/hw/xbox/nv2a/pgraph/gl/vertex.c +++ b/hw/xbox/nv2a/pgraph/gl/vertex.c @@ -80,6 +80,8 @@ void pgraph_gl_bind_vertex_attributes(NV2AState *d, unsigned int min_element, } pg->compressed_attrs = 0; + pg->uniform_attrs = 0; + pg->swizzle_attrs = 0; for (int i = 0; i < NV2A_VERTEXSHADER_ATTRIBUTES; i++) { VertexAttribute *attr = &pg->vertex_attributes[i]; @@ -97,13 +99,21 @@ void pgraph_gl_bind_vertex_attributes(NV2AState *d, unsigned int min_element, GLenum gl_type; GLboolean gl_normalize; bool needs_conversion = false; + bool d3d_swizzle = false; switch (attr->format) { case NV097_SET_VERTEX_DATA_ARRAY_FORMAT_TYPE_UB_D3D: gl_type = GL_UNSIGNED_BYTE; gl_normalize = GL_TRUE; +#ifdef __ANDROID__ + gl_count = attr->count; + if (attr->count == 4) { + d3d_swizzle = true; + } +#else // http://www.opengl.org/registry/specs/ARB/vertex_array_bgra.txt gl_count = GL_BGRA; +#endif break; case NV097_SET_VERTEX_DATA_ARRAY_FORMAT_TYPE_UB_OGL: gl_type = GL_UNSIGNED_BYTE; @@ -188,6 +198,9 @@ void pgraph_gl_bind_vertex_attributes(NV2AState *d, unsigned int min_element, } glEnableVertexAttribArray(i); + if (d3d_swizzle) { + pg->swizzle_attrs |= (1 << i); + } last_entry += stride * provoking_element_index; pgraph_update_inline_value(attr, last_entry); } diff --git a/hw/xbox/nv2a/pgraph/glsl/psh.c b/hw/xbox/nv2a/pgraph/glsl/psh.c index e941d67ca4..951149feac 100644 --- a/hw/xbox/nv2a/pgraph/glsl/psh.c +++ b/hw/xbox/nv2a/pgraph/glsl/psh.c @@ -179,19 +179,10 @@ void pgraph_glsl_set_psh_state(PGRAPHState *pg, PshState *state) } } - /* Keep track of whether texture data has been loaded as signed - * normalized integers or not. This dictates whether or not we will need - * to re-map in fragment shader for certain texture modes (e.g. - * bumpenvmap). - * - * FIXME: When signed texture data is loaded as unsigned and remapped in - * fragment shader, there may be interpolation artifacts. Fix this to - * support signed textures more appropriately. - */ -#if 0 // FIXME - psh->snorm_tex[i] = (f.gl_internal_format == GL_RGB8_SNORM) - || (f.gl_internal_format == GL_RG8_SNORM); -#endif + /* Keep track of textures that are uploaded as signed normalized data. + * Those must not be remapped a second time in the fragment shader. */ + state->snorm_tex[i] = + color_format == NV097_SET_TEXTURE_FORMAT_COLOR_SZ_R6G5B5; state->shadow_map[i] = f.depth; uint32_t filter = pgraph_reg_r(pg, NV_PGRAPH_TEXFILTER0 + i * 4); @@ -767,7 +758,7 @@ static void apply_border_adjustment(const struct PixelShader *ps, MString *vars, mstring_append_fmt( vars, "vec3 t%dLogicalSize = vec3(%f, %f, %f);\n" - "%s.xyz = (%s.xyz * t%dLogicalSize + vec3(4, 4, 4)) * vec3(%f, %f, %f);\n", + "%s.xyz = (%s.xyz * t%dLogicalSize + vec3(4.0, 4.0, 4.0)) * vec3(%f, %f, %f);\n", i, ps->state->border_logical_size[i][0], ps->state->border_logical_size[i][1], ps->state->border_logical_size[i][2], var_name, var_name, i, ps->state->border_inv_real_size[i][0], ps->state->border_inv_real_size[i][1], ps->state->border_inv_real_size[i][2]); } @@ -783,7 +774,7 @@ static void apply_convolution_filter(const struct PixelShader *ps, MString *vars mstring_append_fmt(vars, "vec4 t%d = vec4(0.0);\n" "for (int i = 0; i < 9; i++) {\n" - " vec3 texCoordDelta = vec3(convolution3x3[i], 0);\n" + " vec3 texCoordDelta = vec3(convolution3x3[i], 0.0);\n" " texCoordDelta.xy /= vec2(textureSize(texSamp%d, 0));\n" " t%d += textureProj(texSamp%d, %s(pT%d.xyw) + texCoordDelta) * gaussian3x3[i];\n" "}\n", tex, tex, tex, tex, tex_remap, tex); @@ -855,22 +846,23 @@ static MString* psh_convert(struct PixelShader *ps) mstring_append_fmt(preflight, "float sign1(float x) {\n" - " x *= 255.0;\n" - " return (x-128.0)/127.0;\n" + " float xf = float(x) * 255.0;\n" + " return (xf - 128.0) / 127.0;\n" "}\n" "float sign2(float x) {\n" - " x *= 255.0;\n" - " if (x >= 128.0) return (x-255.5)/127.5;\n" - " else return (x+0.5)/127.5;\n" + " float xf = float(x) * 255.0;\n" + " if (xf >= 128.0) return (xf - 255.5) / 127.5;\n" + " else return (xf + 0.5) / 127.5;\n" "}\n" "float sign3(float x) {\n" - " x *= 255.0;\n" - " if (x >= 128.0) return (x-256.0)/127.0;\n" - " else return (x)/127.0;\n" + " float xf = float(x) * 255.0;\n" + " if (xf >= 128.0) return (xf - 256.0) / 127.0;\n" + " else return xf / 127.0;\n" "}\n" "float sign3_to_0_to_1(float x) {\n" - " if (x >= 0.0) return x/2.0;\n" - " else return 1.0+x/2.0;\n" + " float xf = float(x);\n" + " if (xf >= 0.0) return xf / 2.0;\n" + " else return 1.0 + xf / 2.0;\n" "}\n" "vec3 dotmap_zero_to_one(vec4 col) {\n" " return col.rgb;\n" diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c index 37507cae24..5e5c42ab11 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-ff.c @@ -262,6 +262,8 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz static char alpha_source_specular[] = "specular.a"; static char alpha_source_material[] = "material_alpha"; const char *alpha_source = alpha_source_diffuse; + const char *ambient_source_rgb = "diffuse.rgb"; + const char *emission_source_rgb = "diffuse.rgb"; if (state->fixed_function.diffuse_src == MATERIAL_COLOR_SRC_MATERIAL) { alpha_source = alpha_source_material; } else if (state->fixed_function.diffuse_src == MATERIAL_COLOR_SRC_SPECULAR) { @@ -269,21 +271,23 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz } if (state->fixed_function.ambient_src == MATERIAL_COLOR_SRC_MATERIAL) { - mstring_append_fmt(body, "oD0 = vec4(sceneAmbientColor, %s);\n", alpha_source); + ambient_source_rgb = "materialEmissionColor.rgb"; } else if (state->fixed_function.ambient_src == MATERIAL_COLOR_SRC_DIFFUSE) { - mstring_append_fmt(body, "oD0 = vec4(diffuse.rgb, %s);\n", alpha_source); + ambient_source_rgb = "diffuse.rgb"; } else if (state->fixed_function.ambient_src == MATERIAL_COLOR_SRC_SPECULAR) { - mstring_append_fmt(body, "oD0 = vec4(specular.rgb, %s);\n", alpha_source); + ambient_source_rgb = "specular.rgb"; } - mstring_append(body, "oD0.rgb *= materialEmissionColor.rgb;\n"); if (state->fixed_function.emission_src == MATERIAL_COLOR_SRC_MATERIAL) { - mstring_append(body, "oD0.rgb += sceneAmbientColor;\n"); + emission_source_rgb = "materialEmissionColor.rgb"; } else if (state->fixed_function.emission_src == MATERIAL_COLOR_SRC_DIFFUSE) { - mstring_append(body, "oD0.rgb += diffuse.rgb;\n"); + emission_source_rgb = "diffuse.rgb"; } else if (state->fixed_function.emission_src == MATERIAL_COLOR_SRC_SPECULAR) { - mstring_append(body, "oD0.rgb += specular.rgb;\n"); + emission_source_rgb = "specular.rgb"; } + mstring_append_fmt(body, "oD0 = vec4(sceneAmbientColor * %s, %s);\n", + ambient_source_rgb, alpha_source); + mstring_append_fmt(body, "oD0.rgb += %s;\n", emission_source_rgb); mstring_append(body, "oD1 = vec4(0.0, 0.0, 0.0, specular.a);\n"); @@ -349,7 +353,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz /* https://docs.microsoft.com/en-us/windows/win32/direct3d9/attenuation-and-spotlight-factor#spotlight-factor */ mstring_append_fmt(body, " vec4 spotDir = lightSpotDirection(%d);\n" - " float invScale = 1/length(spotDir.xyz);\n" + " float invScale = 1.0 / length(spotDir.xyz);\n" " float cosHalfPhi = -invScale*spotDir.w;\n" " float cosHalfTheta = invScale + cosHalfPhi;\n" " float spotDirDotVP = dot(spotDir.xyz, VP);\n" @@ -485,7 +489,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " oPos.xy = roundScreenCoords(oPos.xy);\n" " vec4 vtxPos = vec4(oPos.xy, oPos.z / oPos.w, oPos.w);\n" " oPos.z = oPos.z / clipRange.y;\n" - " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" + " oPos.xy = (2.0 * oPos.xy - surfaceSize) / surfaceSize;\n" " oPos.xy *= oPos.w;\n" ); @@ -495,7 +499,7 @@ GLSL_DEFINE(materialEmissionColor, GLSL_LTCTXA(NV_IGRAPH_XF_LTCTXA_CM_COL) ".xyz " float d_e = length(position * modelViewMat0);\n" " float ptMinSize = min(pointParams[7], 63.875);\n" " float ptMaxSize = min(pointParams[3] + ptMinSize, 63.875);\n" - " oPts.x = 1/sqrt(pointParams[0] + pointParams[1] * d_e + pointParams[2] * d_e * d_e) + pointParams[6];\n"); + " oPts.x = 1.0 / sqrt(pointParams[0] + pointParams[1] * d_e + pointParams[2] * d_e * d_e) + pointParams[6];\n"); mstring_append_fmt(body, " oPts.x = clamp(oPts.x * pointParams[3] + pointParams[7], ptMinSize, ptMaxSize) * float(%d);\n", state->surface_scale_factor); diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c index 4a1d57b1f1..e496f83e88 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh-prog.c @@ -696,7 +696,7 @@ static const char* vsh_header = "vec4 _LOG(float src)\n" "{\n" " float tmp = abs(src);\n" - " if (tmp == 0.0) { return vec4(-INFINITY, 1.0f, -INFINITY, 1.0f); }\n" + " if (tmp == 0.0) { return vec4(-INFINITY, 1.0, -INFINITY, 1.0); }\n" " vec4 result;\n" " result.x = floor(log2(tmp));\n" " result.y = tmp / exp2(floor(log2(tmp)));\n" @@ -757,7 +757,7 @@ void pgraph_glsl_gen_vsh_prog(uint16_t version, const uint32_t *tokens, " oPos.xy = roundScreenCoords(oPos.xy);\n" " oPos.w = clampAwayZeroInf(oPos.w);\n" " vec4 vtxPos = oPos;\n" - " oPos.xy = (2.0f * oPos.xy - surfaceSize) / surfaceSize;\n" + " oPos.xy = (2.0 * oPos.xy - surfaceSize) / surfaceSize;\n" " oPos.z = oPos.z / clipRange.y;\n" /* Undo perspective divide by w. diff --git a/hw/xbox/nv2a/pgraph/glsl/vsh.c b/hw/xbox/nv2a/pgraph/glsl/vsh.c index 8c9efb87e0..467a3873f9 100644 --- a/hw/xbox/nv2a/pgraph/glsl/vsh.c +++ b/hw/xbox/nv2a/pgraph/glsl/vsh.c @@ -229,7 +229,7 @@ MString *pgraph_glsl_gen_vsh(const VshState *state, GenVshGlslOptions opts) // fractional part and to convert floating-point coordinates by // by truncating (not flooring). "vec2 roundScreenCoords(vec2 pos) {\n" - " return trunc(pos * 16.0f) / 16.0f;\n" + " return trunc(pos * 16.0) / 16.0;\n" "}\n"); pgraph_glsl_get_vtx_header(header, opts.vulkan, state->smooth_shading, diff --git a/hw/xbox/nv2a/pgraph/pgraph.c b/hw/xbox/nv2a/pgraph/pgraph.c index 03aeba30d4..442d9d270b 100644 --- a/hw/xbox/nv2a/pgraph/pgraph.c +++ b/hw/xbox/nv2a/pgraph/pgraph.c @@ -2487,10 +2487,8 @@ DEF_METHOD_INC(NV097, SET_TEXCOORD3_2F) do { \ VertexAttribute *attribute = &pg->vertex_attributes[(attr_index)]; \ pgraph_allocate_inline_buffer_vertices(pg, (attr_index)); \ - attribute->inline_value[0] = (parameter & 0xFF) / 255.0f; \ - attribute->inline_value[1] = ((parameter >> 8) & 0xFF) / 255.0f; \ - attribute->inline_value[2] = ((parameter >> 16) & 0xFF) / 255.0f; \ - attribute->inline_value[3] = ((parameter >> 24) & 0xFF) / 255.0f; \ + pgraph_argb_pack32_to_rgba_float(parameter, \ + attribute->inline_value); \ } while (0) DEF_METHOD_INC(NV097, SET_DIFFUSE_COLOR4UB) @@ -2917,10 +2915,14 @@ DEF_METHOD_INC(NV097, SET_VERTEX_DATA4UB) int slot = (method - NV097_SET_VERTEX_DATA4UB) / 4; VertexAttribute *attribute = &pg->vertex_attributes[slot]; pgraph_allocate_inline_buffer_vertices(pg, slot); - attribute->inline_value[0] = (parameter & 0xFF) / 255.0; - attribute->inline_value[1] = ((parameter >> 8) & 0xFF) / 255.0; - attribute->inline_value[2] = ((parameter >> 16) & 0xFF) / 255.0; - attribute->inline_value[3] = ((parameter >> 24) & 0xFF) / 255.0; + if (slot == NV2A_VERTEX_ATTR_DIFFUSE || slot == NV2A_VERTEX_ATTR_SPECULAR) { + pgraph_argb_pack32_to_rgba_float(parameter, attribute->inline_value); + } else { + attribute->inline_value[0] = (parameter & 0xFF) / 255.0; + attribute->inline_value[1] = ((parameter >> 8) & 0xFF) / 255.0; + attribute->inline_value[2] = ((parameter >> 16) & 0xFF) / 255.0; + attribute->inline_value[3] = ((parameter >> 24) & 0xFF) / 255.0; + } if (slot == 0) { pgraph_finish_inline_buffer_vertex(pg); } diff --git a/hw/xbox/nv2a/pgraph/vertex.c b/hw/xbox/nv2a/pgraph/vertex.c index 31076896e7..309ac2af37 100644 --- a/hw/xbox/nv2a/pgraph/vertex.c +++ b/hw/xbox/nv2a/pgraph/vertex.c @@ -31,6 +31,14 @@ void pgraph_update_inline_value(VertexAttribute *attr, const uint8_t *data) switch (attr->format) { case NV097_SET_VERTEX_DATA_ARRAY_FORMAT_TYPE_UB_D3D: + if (attr->count == 4) { + attr->inline_value[0] = (float)data[2] / 255.0f; + attr->inline_value[1] = (float)data[1] / 255.0f; + attr->inline_value[2] = (float)data[0] / 255.0f; + attr->inline_value[3] = (float)data[3] / 255.0f; + break; + } + /* Fall back to direct copy for unexpected non-BGRA cases. */ case NV097_SET_VERTEX_DATA_ARRAY_FORMAT_TYPE_UB_OGL: for (uint32_t i = 0; i < attr->count; ++i) { attr->inline_value[i] = (float)data[i] / 255.0f; diff --git a/include/ui/xemu-display.h b/include/ui/xemu-display.h index d47d1fe57d..587a0761da 100644 --- a/include/ui/xemu-display.h +++ b/include/ui/xemu-display.h @@ -8,6 +8,7 @@ #include #include "ui/kbd-state.h" +#include "ui/shader.h" // FIXME: Cleanup struct sdl2_console { @@ -26,6 +27,7 @@ struct sdl2_console { int idle_counter; int ignore_hotkeys; SDL_GLContext winctx; + QemuGLShader *gls; QKbdState *kbd; bool y0_top; bool scanout_mode; diff --git a/ui/xemu.c b/ui/xemu.c index fcb14dea98..a60dbbb6d6 100644 --- a/ui/xemu.c +++ b/ui/xemu.c @@ -128,23 +128,12 @@ static void toggle_full_screen(struct sdl2_console *scon); #ifdef __ANDROID__ static bool g_android_gl_bgra_supported = true; -static bool g_android_use_hud = false; static bool g_android_paused = false; static bool g_android_should_quit = false; static uint64_t g_android_frame_counter = 0; static int g_android_target_fps = 60; static int64_t g_android_frame_interval_ns = 16666666; -static GLuint g_android_blit_prog; -static GLuint g_android_blit_vao; -static GLuint g_android_blit_vbo; -static GLint g_android_blit_tex_loc = -1; -static GLint g_android_blit_flip_loc = -1; static int g_android_display_mode = 0; /* 0=stretch, 1=4:3, 2=16:9 */ -static GLuint g_android_cpu_tex = 0; -static int g_android_cpu_tex_w = 0; -static int g_android_cpu_tex_h = 0; -static uint8_t *g_android_cpu_buf = NULL; -static size_t g_android_cpu_buf_size = 0; static bool sdl2_is_render_thread(void) { @@ -181,35 +170,16 @@ static void android_log_gl_error(const char *stage) (void)logged; } -static GLuint android_gl_compile_shader(GLenum type, const char *src) -{ - GLuint shader = glCreateShader(type); - if (!shader) { - __android_log_print(ANDROID_LOG_ERROR, "xemu-android", - "android_gl_compile_shader: glCreateShader failed"); - return 0; - } - glShaderSource(shader, 1, &src, NULL); - glCompileShader(shader); - GLint status = 0; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) { - char log[1024] = {0}; - GLsizei len = 0; - glGetShaderInfoLog(shader, (GLsizei)sizeof(log) - 1, &len, log); - __android_log_print(ANDROID_LOG_ERROR, "xemu-android", - "shader compile failed: %s", log); - glDeleteShader(shader); - return 0; - } - return shader; -} - void xemu_android_set_display_mode_setting(int mode) { g_android_display_mode = mode; } +int xemu_android_get_display_mode_setting(void) +{ + return g_android_display_mode; +} + static void xemu_android_refresh_frame_limit_from_env(void) { int fps = 60; @@ -233,98 +203,48 @@ static void xemu_android_refresh_frame_limit_from_env(void) g_android_target_fps); } -static bool android_blit_init(void) +static bool sdl2_gl_ensure_shader(struct sdl2_console *scon) { - if (g_android_blit_prog) { + if (scon->gls) { return true; } - const char *vs = "#version 300 es\n" - "precision highp float;\n" - "layout(location=0) in vec2 a_pos;\n" - "layout(location=1) in vec2 a_uv;\n" - "uniform bool u_flip;\n" - "out vec2 v_uv;\n" - "void main() {\n" - " v_uv = a_uv;\n" - " if (u_flip) { v_uv.y = 1.0 - v_uv.y; }\n" - " gl_Position = vec4(a_pos, 0.0, 1.0);\n" - "}\n"; - const char *fs = "#version 300 es\n" - "precision highp float;\n" - "in vec2 v_uv;\n" - "uniform sampler2D u_tex;\n" - "out vec4 out_Color;\n" - "void main() {\n" - " vec4 c = texture(u_tex, v_uv);\n" - " out_Color = vec4(c.rgb, 1.0);\n" - "}\n"; - GLuint vshader = android_gl_compile_shader(GL_VERTEX_SHADER, vs); - GLuint fshader = android_gl_compile_shader(GL_FRAGMENT_SHADER, fs); - if (!vshader || !fshader) { - return false; - } - - GLuint prog = glCreateProgram(); - glAttachShader(prog, vshader); - glAttachShader(prog, fshader); - glLinkProgram(prog); - glDeleteShader(vshader); - glDeleteShader(fshader); - - GLint linked = 0; - glGetProgramiv(prog, GL_LINK_STATUS, &linked); - if (!linked) { - char log[1024] = {0}; - GLsizei len = 0; - glGetProgramInfoLog(prog, (GLsizei)sizeof(log) - 1, &len, log); - __android_log_print(ANDROID_LOG_ERROR, "xemu-android", - "shader link failed: %s", log); - glDeleteProgram(prog); - return false; - } - - g_android_blit_prog = prog; - g_android_blit_tex_loc = glGetUniformLocation(prog, "u_tex"); - g_android_blit_flip_loc = glGetUniformLocation(prog, "u_flip"); - - const float verts[] = { - -1.0f, -1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, - -1.0f, 1.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, - }; - glGenVertexArrays(1, &g_android_blit_vao); - glBindVertexArray(g_android_blit_vao); - glGenBuffers(1, &g_android_blit_vbo); - glBindBuffer(GL_ARRAY_BUFFER, g_android_blit_vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), - (void *)(2 * sizeof(float))); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - return true; + scon->gls = qemu_gl_init_shader(); + return scon->gls != NULL; } -static void android_blit_frame(GLuint tex, bool flip) +static void sdl2_gl_render_texture(struct sdl2_console *scon, + GLuint tex, bool flip) { - if (!android_blit_init()) { - return; - } - android_log_gl_error("blit-start"); int w = 0; int h = 0; - SDL_GL_GetDrawableSize(m_window, &w, &h); - if (w <= 0) w = 1; - if (h <= 0) h = 1; + int vx = 0; + int vy = 0; + int vw; + int vh; - int vx = 0, vy = 0, vw = w, vh = h; + if (!sdl2_gl_ensure_shader(scon)) { + __android_log_print(ANDROID_LOG_ERROR, "xemu-android", + "failed to initialize shared GL shader"); + return; + } + + android_log_gl_error("blit-start"); + + SDL_GL_GetDrawableSize(scon->real_window, &w, &h); + if (w <= 0) { + w = 1; + } + if (h <= 0) { + h = 1; + } + + vw = w; + vh = h; if (g_android_display_mode != 0) { - float target = (g_android_display_mode == 1) ? (4.0f / 3.0f) : (16.0f / 9.0f); + float target = (g_android_display_mode == 1) + ? (4.0f / 3.0f) + : (16.0f / 9.0f); float screen = (float)w / (float)h; if (screen > target) { vw = (int)(h * target); @@ -342,16 +262,15 @@ static void android_blit_frame(GLuint tex, bool flip) glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glDisable(GL_SCISSOR_TEST); - glUseProgram(g_android_blit_prog); - glUniform1i(g_android_blit_tex_loc, 0); - glUniform1i(g_android_blit_flip_loc, flip ? 1 : 0); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); - glBindVertexArray(g_android_blit_vao); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); + /* Android previously used a local presenter shader whose flip boolean had + * the opposite meaning of qemu_gl_run_texture_blit(). Invert here so the + * NV2A/display texture origin matches the pre-refactor Android path. */ + qemu_gl_run_texture_blit(scon->gls, !flip); android_log_gl_error("blit-draw"); } + #endif int xemu_is_fullscreen(void) @@ -812,24 +731,12 @@ void sdl2_poll_events(struct sdl2_console *scon) } int kbd = 0, mouse = 0; -#ifdef __ANDROID__ - if (g_android_use_hud) { - xemu_hud_should_capture_kbd_mouse(&kbd, &mouse); - } -#else xemu_hud_should_capture_kbd_mouse(&kbd, &mouse); -#endif while (SDL_PollEvent(ev)) { // HUD must process events first so that if a controller is detached, // a latent rebind request can cancel before the state is freed -#ifdef __ANDROID__ - if (g_android_use_hud) { - xemu_hud_process_sdl_events(ev); - } -#else xemu_hud_process_sdl_events(ev); -#endif xemu_input_process_sdl_events(ev); switch (ev->type) { @@ -1404,9 +1311,7 @@ void xemu_android_display_loop(void) #ifdef __ANDROID__ xemu_android_refresh_frame_limit_from_env(); SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0); - if (g_android_use_hud) { - xemu_hud_init(m_window, m_context); - } + xemu_hud_init(m_window, m_context); #endif tcg_register_init_ctx(); qemu_set_current_aio_context(qemu_get_aio_context()); @@ -1641,34 +1546,6 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) update_fps(); g_android_frame_counter++; -#ifdef __ANDROID__ - static int force_cpu_blit_mode = -2; /* -2=uninit, -1=auto, 0=off, 1=on */ - if (force_cpu_blit_mode == -2) { - const char *env = SDL_getenv("XEMU_ANDROID_FORCE_CPU_BLIT"); - if (!env) { - force_cpu_blit_mode = -1; - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "refresh: CPU blit mode auto"); - } else if (strcmp(env, "1") == 0 || strcmp(env, "true") == 0 || - strcmp(env, "TRUE") == 0) { - force_cpu_blit_mode = 1; - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "refresh: forcing CPU blit path"); - } else { - force_cpu_blit_mode = 0; - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "refresh: CPU blit disabled via env"); - } - } - bool force_cpu_blit = false; - if (force_cpu_blit_mode == -1) { - /* Vulkan path should prefer direct texture presentation on Android. */ - force_cpu_blit = (g_config.display.renderer != CONFIG_DISPLAY_RENDERER_VULKAN); - } else { - force_cpu_blit = (force_cpu_blit_mode == 1); - } -#endif - /* XXX: Note that this bypasses the usual VGA path in order to quickly * get the surface. This is simple and fast, at the cost of accuracy. * Ideally, this should go through the VGA code and opportunistically pull @@ -1679,80 +1556,16 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) * the guest code isn't using HW accelerated rendering, but just blitting * to the framebuffer, fall back to the VGA path. */ - GLuint tex = 0; + GLuint tex = nv2a_get_framebuffer_surface(); #ifdef __ANDROID__ - if (force_cpu_blit) { - /* Trigger a render/sync so the readback buffer gets updated. */ - (void)nv2a_get_framebuffer_surface(); - nv2a_release_framebuffer_surface(); - - int rb_w = 0; - int rb_h = 0; - if (nv2a_android_copy_readback(&g_android_cpu_buf, - &g_android_cpu_buf_size, - &rb_w, &rb_h)) { - if ((g_android_frame_counter % 120) == 0) { - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "refresh: using readback %dx%d", rb_w, rb_h); - if (g_android_cpu_buf && rb_w > 0 && rb_h > 0) { - uint32_t tl = 0, mid = 0, br = 0; - size_t tl_off = 0; - size_t mid_off = ((size_t)(rb_h / 2) * (size_t)rb_w + - (size_t)(rb_w / 2)) * 4; - size_t br_off = ((size_t)(rb_h - 1) * (size_t)rb_w + - (size_t)(rb_w - 1)) * 4; - if (mid_off + 4 <= g_android_cpu_buf_size) { - memcpy(&mid, g_android_cpu_buf + mid_off, sizeof(mid)); - } - if (br_off + 4 <= g_android_cpu_buf_size) { - memcpy(&br, g_android_cpu_buf + br_off, sizeof(br)); - } - if (tl_off + 4 <= g_android_cpu_buf_size) { - memcpy(&tl, g_android_cpu_buf + tl_off, sizeof(tl)); - } - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "readback sample tl=%08x mid=%08x br=%08x", - tl, mid, br); - } - } - if (!g_android_cpu_tex) { - glGenTextures(1, &g_android_cpu_tex); - } - glBindTexture(GL_TEXTURE_2D, g_android_cpu_tex); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - if (rb_w != g_android_cpu_tex_w || rb_h != g_android_cpu_tex_h) { - g_android_cpu_tex_w = rb_w; - g_android_cpu_tex_h = rb_h; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rb_w, rb_h, 0, - GL_RGBA, GL_UNSIGNED_BYTE, g_android_cpu_buf); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rb_w, rb_h, - GL_RGBA, GL_UNSIGNED_BYTE, g_android_cpu_buf); - } - tex = g_android_cpu_tex; - flip_required = true; - } else if ((g_android_frame_counter % 120) == 0) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "refresh: no readback available yet"); - } + if (tex != 0 && glIsTexture(tex) == GL_FALSE) { if ((g_android_frame_counter % 120) == 0) { - __android_log_print(ANDROID_LOG_INFO, "xemu-android", - "refresh: force CPU blit path"); - } - } else { - tex = nv2a_get_framebuffer_surface(); - if (tex != 0 && glIsTexture(tex) == GL_FALSE) { - if ((g_android_frame_counter % 120) == 0) { - __android_log_print(ANDROID_LOG_WARN, "xemu-android", - "refresh: nv2a tex %u not valid in display context", - (unsigned)tex); - } - tex = 0; + __android_log_print(ANDROID_LOG_WARN, "xemu-android", + "refresh: nv2a tex %u not valid in display context", + (unsigned)tex); } + tex = 0; } -#else - tex = nv2a_get_framebuffer_surface(); #endif #ifdef __ANDROID__ android_log_gl_error("refresh-get-fb"); @@ -1806,20 +1619,13 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl) glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); #ifdef __ANDROID__ - android_blit_frame(tex, flip_required); - /* - * Android uses a HUD stub, but snapshot JNI requests are dispatched - * from xemu_hud_render(). Keep this hook active each frame so save/load - * requests run on the SDL/QEMU render thread. - */ + sdl2_gl_render_texture(scon, tex, flip_required); +#endif xemu_snapshots_set_framebuffer_texture(tex, flip_required); xemu_hud_set_framebuffer_texture(tex, flip_required); xemu_hud_render(); +#ifdef __ANDROID__ android_log_gl_error("refresh-blit"); -#else - xemu_snapshots_set_framebuffer_texture(tex, flip_required); - xemu_hud_set_framebuffer_texture(tex, flip_required); - xemu_hud_render(); #endif // Release BQL before swapping (which may sleep if swap interval is not immediate)