Update everything to enable second screen on android under GL and Vulkan. Still some issues!

This commit is contained in:
David Griswold 2025-03-02 17:15:18 -03:00 committed by OpenSauce04
parent c4667b1cf0
commit 12afea9015
14 changed files with 113 additions and 33 deletions

View File

@ -125,6 +125,9 @@ object NativeLibrary {
external fun surfaceDestroyed()
external fun doFrame()
//Second window
external fun enableSecondWindow(secondary_surface: Surface)
external fun disableSecondWindow()
/**
* Unpauses emulation from a paused state.
*/

View File

@ -1,11 +1,10 @@
import android.app.Presentation
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.os.Bundle
import android.view.Display
import android.view.SurfaceHolder
import android.view.SurfaceView
import org.citra.citra_emu.NativeLibrary
class SecondScreenPresentation(
context: Context,
@ -20,7 +19,7 @@ class SecondScreenPresentation(
surfaceView = SurfaceView(context)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
NativeLibrary.enableSecondWindow(holder.surface)
}
override fun surfaceChanged(
@ -29,9 +28,12 @@ class SecondScreenPresentation(
width: Int,
height: Int
) {
NativeLibrary.enableSecondWindow(holder.surface)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {}
override fun surfaceDestroyed(holder: SurfaceHolder) {
NativeLibrary.disableSecondWindow();
}
})
setContentView(surfaceView) // Set SurfaceView as content

View File

@ -1329,7 +1329,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
private fun runWithValidSurface() {
NativeLibrary.surfaceChanged(surface!!, surface2!!)
NativeLibrary.surfaceChanged(surface!!)
when (state) {
State.STOPPED -> {
Thread({

View File

@ -49,16 +49,17 @@ void EmuWindow_Android::OnFramebufferSizeChanged() {
const int bigger{window_width > window_height ? window_width : window_height};
const int smaller{window_width < window_height ? window_width : window_height};
if (is_portrait_mode) {
if (is_portrait_mode && !is_secondary) {
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
} else {
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
}
}
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface) : host_window{surface} {
EmuWindow_Android::EmuWindow_Android(ANativeWindow *surface, bool is_secondary) : EmuWindow{
is_secondary}, host_window(surface) {
LOG_DEBUG(Frontend, "Initializing EmuWindow_Android");
if (is_secondary) LOG_DEBUG(Frontend, "Initializing secondary window Android");
if (!surface) {
LOG_CRITICAL(Frontend, "surface is nullptr");
return;

View File

@ -5,6 +5,7 @@
#pragma once
#include <vector>
#include <EGL/egl.h>
#include "core/frontend/emu_window.h"
namespace Core {
@ -13,7 +14,7 @@ class System;
class EmuWindow_Android : public Frontend::EmuWindow {
public:
EmuWindow_Android(ANativeWindow* surface);
EmuWindow_Android(ANativeWindow* surface, bool is_secondary = false);
~EmuWindow_Android();
/// Called by the onSurfaceChanges() method to change the surface
@ -30,7 +31,10 @@ public:
void DoneCurrent() override;
virtual void TryPresenting() {}
// EGL Context must be shared
// could probably use the existing
// SharedContext for this instead, this is maybe temporary
virtual EGLContext* GetEGLContext() {return NULL;}
virtual void StopPresenting() {}
protected:

View File

@ -72,8 +72,8 @@ private:
EGLContext egl_context{};
};
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface)
: EmuWindow_Android{surface}, system{system_} {
EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativeWindow* surface, bool is_secondary, EGLContext* sharedContext)
: EmuWindow_Android{surface,is_secondary}, system{system_} {
if (egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); egl_display == EGL_NO_DISPLAY) {
LOG_CRITICAL(Frontend, "eglGetDisplay() failed");
return;
@ -96,8 +96,9 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ
if (eglQuerySurface(egl_display, egl_surface, EGL_HEIGHT, &window_height) != EGL_TRUE) {
return;
}
if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
if (sharedContext) {
egl_context = *sharedContext;
}else if (egl_context = eglCreateContext(egl_display, egl_config, 0, egl_context_attribs.data());
egl_context == EGL_NO_CONTEXT) {
LOG_CRITICAL(Frontend, "eglCreateContext() failed");
return;
@ -127,6 +128,10 @@ EmuWindow_Android_OpenGL::EmuWindow_Android_OpenGL(Core::System& system_, ANativ
OnFramebufferSizeChanged();
}
EGLContext* EmuWindow_Android_OpenGL::GetEGLContext() {
return &egl_context;
}
bool EmuWindow_Android_OpenGL::CreateWindowSurface() {
if (!host_window) {
return true;
@ -204,14 +209,15 @@ void EmuWindow_Android_OpenGL::TryPresenting() {
return;
}
if (presenting_state == PresentingState::Initial) [[unlikely]] {
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
presenting_state = PresentingState::Running;
}
if (presenting_state != PresentingState::Running) [[unlikely]] {
return;
}
// if (presenting_state != PresentingState::Running) [[unlikely]] {
// return;
// }
eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
eglSwapInterval(egl_display, Settings::values.use_vsync_new ? 1 : 0);
system.GPU().Renderer().TryPresent(0);
system.GPU().Renderer().TryPresent(100,is_secondary);
eglSwapBuffers(egl_display, egl_surface);
}

View File

@ -19,13 +19,13 @@ struct ANativeWindow;
class EmuWindow_Android_OpenGL : public EmuWindow_Android {
public:
EmuWindow_Android_OpenGL(Core::System& system, ANativeWindow* surface);
EmuWindow_Android_OpenGL(Core::System& system, ANativeWindow* surface, bool is_secondary, EGLContext* sharedContext = NULL);
~EmuWindow_Android_OpenGL() override = default;
void TryPresenting() override;
void StopPresenting() override;
void PollEvents() override;
EGLContext* GetEGLContext() override;
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
private:

View File

@ -24,8 +24,8 @@ private:
};
EmuWindow_Android_Vulkan::EmuWindow_Android_Vulkan(
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_)
: EmuWindow_Android{surface}, driver_library{driver_library_} {
ANativeWindow* surface, std::shared_ptr<Common::DynamicLibrary> driver_library_, bool is_secondary)
: EmuWindow_Android{surface,is_secondary}, driver_library{driver_library_} {
CreateWindowSurface();
if (core_context = CreateSharedContext(); !core_context) {

View File

@ -11,7 +11,7 @@ struct ANativeWindow;
class EmuWindow_Android_Vulkan : public EmuWindow_Android {
public:
EmuWindow_Android_Vulkan(ANativeWindow* surface,
std::shared_ptr<Common::DynamicLibrary> driver_library);
std::shared_ptr<Common::DynamicLibrary> driver_library, bool is_secondary);
~EmuWindow_Android_Vulkan() override = default;
void PollEvents() override {}

View File

@ -65,9 +65,12 @@
namespace {
ANativeWindow* s_surf;
ANativeWindow* s_secondary_surface;
bool secondary_enabled = false;
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
std::unique_ptr<EmuWindow_Android> window;
std::unique_ptr<EmuWindow_Android> second_window;
std::atomic<bool> stop_run{true};
std::atomic<bool> pause_emulation{false};
@ -118,8 +121,10 @@ static void TryShutdown() {
}
window->DoneCurrent();
if (second_window) second_window->DoneCurrent();
Core::System::GetInstance().Shutdown();
window.reset();
if (second_window) second_window.reset();
InputManager::Shutdown();
MicroProfileShutdown();
}
@ -148,12 +153,17 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
switch (graphics_api) {
#ifdef ENABLE_OPENGL
case Settings::GraphicsAPI::OpenGL:
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf);
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf,false);
if (secondary_enabled) {
EGLContext* c = window->GetEGLContext();
second_window = std::make_unique<EmuWindow_Android_OpenGL>(system,s_secondary_surface, true, c);
}
break;
#endif
#ifdef ENABLE_VULKAN
case Settings::GraphicsAPI::Vulkan:
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library,false);
if (secondary_enabled) second_window = std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface, vulkan_library, true);
break;
#endif
default:
@ -161,9 +171,14 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
"Unknown or unsupported graphics API {}, falling back to available default",
graphics_api);
#ifdef ENABLE_OPENGL
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf);
window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_surf, false);
if (secondary_enabled) {
EGLContext *c = window->GetEGLContext();
second_window = std::make_unique<EmuWindow_Android_OpenGL>(system, s_secondary_surface,true, c);
}
#elif ENABLE_VULKAN
window = std::make_unique<EmuWindow_Android_Vulkan>(s_surf, vulkan_library);
if (secondary_enabled) second_window = std::make_unique<EmuWindow_Android_Vulkan>(s_secondary_surface, vulkan_library, true);
#else
// TODO: Add a null renderer backend for this, perhaps.
#error "At least one renderer must be enabled."
@ -202,7 +217,8 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
InputManager::Init();
window->MakeCurrent();
const Core::System::ResultStatus load_result{system.Load(*window, filepath)};
//if (second_window) second_window->MakeCurrent();
const Core::System::ResultStatus load_result{system.Load(*window, filepath,second_window.get())};
if (load_result != Core::System::ResultStatus::Success) {
return load_result;
}
@ -249,6 +265,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
std::unique_lock pause_lock{paused_mutex};
running_cv.wait(pause_lock, [] { return !pause_emulation || stop_run; });
window->PollEvents();
//if (second_window) second_window->PollEvents();
}
}
@ -306,6 +323,23 @@ void Java_org_citra_citra_1emu_NativeLibrary_surfaceChanged(JNIEnv* env,
LOG_INFO(Frontend, "Surface changed");
}
void Java_org_citra_citra_1emu_NativeLibrary_enableSecondWindow(JNIEnv* env,
[[maybe_unused]] jobject obj,jobject surf) {
s_secondary_surface = ANativeWindow_fromSurface(env, surf);
secondary_enabled = true;
LOG_INFO(Frontend, "Secondary Surface Enabled");
}
void Java_org_citra_citra_1emu_NativeLibrary_disableSecondWindow(JNIEnv* env,
[[maybe_unused]] jobject obj) {
secondary_enabled = false;
// how do I delete the window? TODO
LOG_INFO(Frontend, "Secondary Surface Disabled");
}
void Java_org_citra_citra_1emu_NativeLibrary_surfaceDestroyed([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj) {
if (s_surf != nullptr) {
@ -325,6 +359,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en
if (window) {
window->TryPresenting();
}
if (second_window) {
second_window->TryPresenting();
}
}
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(
@ -481,6 +518,7 @@ void Java_org_citra_citra_1emu_NativeLibrary_stopEmulation([[maybe_unused]] JNIE
stop_run = true;
pause_emulation = false;
window->StopPresenting();
if (second_window) second_window->StopPresenting();
running_cv.notify_all();
}
@ -745,4 +783,4 @@ void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIE
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
}
} // extern "C"
}

View File

@ -251,6 +251,13 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
break;
}
}
#ifdef ANDROID
// if is_secondary is set on android, MUST be a second window
if (is_secondary) {
layout = Layout::SeparateWindowsLayout(width, height, is_secondary,
Settings::values.upright_screen.GetValue());
}
#endif
UpdateMinimumWindowSize(min_size);
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {

View File

@ -35,7 +35,7 @@ void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {
window.UpdateCurrentFramebufferLayout(layout.width, layout.height, is_portrait_mode);
};
update_layout(render_window);
if (secondary_window) {
if (secondary_window != nullptr) {
update_layout(*secondary_window);
}
}

View File

@ -96,7 +96,7 @@ void RendererOpenGL::SwapBuffers() {
PrepareRendertarget();
RenderScreenshot();
const auto& main_layout = render_window.GetFramebufferLayout();
const auto &main_layout = render_window.GetFramebufferLayout();
RenderToMailbox(main_layout, render_window.mailbox, false);
#ifndef ANDROID
@ -106,6 +106,15 @@ void RendererOpenGL::SwapBuffers() {
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
secondary_window->PollEvents();
}
#endif
#ifdef ANDROID
// on android, if secondary_window is defined at all
// it means we have a second display
if (secondary_window) {
const auto &secondary_layout = secondary_window->GetFramebufferLayout();
RenderToMailbox(secondary_layout, secondary_window->mailbox, false);
secondary_window->PollEvents();
}
#endif
if (frame_dumper.IsDumping()) {
try {
@ -553,7 +562,7 @@ void RendererOpenGL::DrawSingleScreen(const ScreenInfo& screen_info, float x, fl
state.texture_units[0].texture_2d = 0;
state.texture_units[0].sampler = 0;
state.Apply();
}
}
/**
* Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD

View File

@ -829,6 +829,16 @@ void RendererVulkan::SwapBuffers() {
RenderToWindow(*second_window, secondary_layout, false);
secondary_window->PollEvents();
}
#endif
#ifdef ANDROID
if (secondary_window) {
const auto &secondary_layout = secondary_window->GetFramebufferLayout();
if (!second_window) {
second_window = std::make_unique<PresentWindow>(*secondary_window, instance, scheduler);
}
RenderToWindow(*second_window, secondary_layout, false);
secondary_window->PollEvents();
}
#endif
rasterizer.TickFrame();
EndFrame();