mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	Android: Get it running again
Currently settings are not changable.
This commit is contained in:
		
							parent
							
								
									729e1b3392
								
							
						
					
					
						commit
						a451e7f177
					
				|  | @ -1,10 +1,8 @@ | |||
| set(SRCS | ||||
|   android_host_interface.cpp | ||||
|   android_host_interface.h | ||||
|   android_gles_host_display.cpp | ||||
|   android_gles_host_display.h | ||||
|   main.cpp | ||||
| ) | ||||
| 
 | ||||
| add_library(duckstation-native SHARED ${SRCS}) | ||||
| target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui EGL::EGL) | ||||
| target_link_libraries(duckstation-native PRIVATE android frontend-common core common glad imgui) | ||||
|  |  | |||
|  | @ -1,399 +0,0 @@ | |||
| #include "android_gles_host_display.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/log.h" | ||||
| #include <EGL/eglext.h> | ||||
| #include <array> | ||||
| #include <imgui.h> | ||||
| #include <imgui_impl_opengl3.h> | ||||
| #include <tuple> | ||||
| Log_SetChannel(AndroidGLESHostDisplay); | ||||
| 
 | ||||
| class AndroidGLESHostDisplayTexture : public HostDisplayTexture | ||||
| { | ||||
| public: | ||||
|   AndroidGLESHostDisplayTexture(GLuint id, u32 width, u32 height) : m_id(id), m_width(width), m_height(height) {} | ||||
|   ~AndroidGLESHostDisplayTexture() override { glDeleteTextures(1, &m_id); } | ||||
| 
 | ||||
|   void* GetHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(m_id)); } | ||||
|   u32 GetWidth() const override { return m_width; } | ||||
|   u32 GetHeight() const override { return m_height; } | ||||
| 
 | ||||
|   GLuint GetGLID() const { return m_id; } | ||||
| 
 | ||||
|   static std::unique_ptr<AndroidGLESHostDisplayTexture> Create(u32 width, u32 height, const void* initial_data, | ||||
|                                                                u32 initial_data_stride) | ||||
|   { | ||||
|     GLuint id; | ||||
|     glGenTextures(1, &id); | ||||
| 
 | ||||
|     GLint old_texture_binding = 0; | ||||
|     glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); | ||||
| 
 | ||||
|     // TODO: Set pack width
 | ||||
|     Assert(!initial_data || initial_data_stride == (width * sizeof(u32))); | ||||
| 
 | ||||
|     glBindTexture(GL_TEXTURE_2D, id); | ||||
|     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, initial_data); | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||
|     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||
| 
 | ||||
|     glBindTexture(GL_TEXTURE_2D, id); | ||||
|     return std::make_unique<AndroidGLESHostDisplayTexture>(id, width, height); | ||||
|   } | ||||
| 
 | ||||
| private: | ||||
|   GLuint m_id; | ||||
|   u32 m_width; | ||||
|   u32 m_height; | ||||
| }; | ||||
| 
 | ||||
| AndroidGLESHostDisplay::AndroidGLESHostDisplay(ANativeWindow* window) | ||||
|   : m_window(window), m_window_width(ANativeWindow_getWidth(window)), m_window_height(ANativeWindow_getHeight(window)) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| AndroidGLESHostDisplay::~AndroidGLESHostDisplay() | ||||
| { | ||||
|   if (m_egl_context != EGL_NO_CONTEXT) | ||||
|   { | ||||
|     m_display_program.Destroy(); | ||||
|     ImGui_ImplOpenGL3_Shutdown(); | ||||
|     eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
|     eglDestroyContext(m_egl_display, m_egl_context); | ||||
|   } | ||||
| 
 | ||||
|   if (m_egl_surface != EGL_NO_SURFACE) | ||||
|     eglDestroySurface(m_egl_display, m_egl_surface); | ||||
| } | ||||
| 
 | ||||
| HostDisplay::RenderAPI AndroidGLESHostDisplay::GetRenderAPI() const | ||||
| { | ||||
|   return HostDisplay::RenderAPI::OpenGLES; | ||||
| } | ||||
| 
 | ||||
| void* AndroidGLESHostDisplay::GetRenderDevice() const | ||||
| { | ||||
|   return nullptr; | ||||
| } | ||||
| 
 | ||||
| void* AndroidGLESHostDisplay::GetRenderContext() const | ||||
| { | ||||
|   return m_egl_context; | ||||
| } | ||||
| 
 | ||||
| void* AndroidGLESHostDisplay::GetRenderWindow() const | ||||
| { | ||||
|   return m_window; | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::ChangeRenderWindow(void* new_window) | ||||
| { | ||||
|   eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); | ||||
| 
 | ||||
|   DestroySurface(); | ||||
| 
 | ||||
|   m_window = static_cast<ANativeWindow*>(new_window); | ||||
| 
 | ||||
|   if (!CreateSurface()) | ||||
|     Panic("Failed to recreate surface after window change"); | ||||
| 
 | ||||
|   if (!eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context)) | ||||
|     Panic("Failed to make context current after window change"); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<HostDisplayTexture> AndroidGLESHostDisplay::CreateTexture(u32 width, u32 height, const void* data, | ||||
|                                                                           u32 data_stride, bool dynamic) | ||||
| { | ||||
|   return AndroidGLESHostDisplayTexture::Create(width, height, data, data_stride); | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, | ||||
|                                            const void* data, u32 data_stride) | ||||
| { | ||||
|   AndroidGLESHostDisplayTexture* tex = static_cast<AndroidGLESHostDisplayTexture*>(texture); | ||||
|   Assert(data_stride == (width * sizeof(u32))); | ||||
| 
 | ||||
|   GLint old_texture_binding = 0; | ||||
|   glGetIntegerv(GL_TEXTURE_BINDING_2D, &old_texture_binding); | ||||
| 
 | ||||
|   glBindTexture(GL_TEXTURE_2D, tex->GetGLID()); | ||||
|   glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data); | ||||
| 
 | ||||
|   glBindTexture(GL_TEXTURE_2D, old_texture_binding); | ||||
| } | ||||
| 
 | ||||
| bool AndroidGLESHostDisplay::DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, | ||||
|                                              void* out_data, u32 out_data_stride) | ||||
| { | ||||
|   GLint old_alignment = 0, old_row_length = 0; | ||||
|   glGetIntegerv(GL_PACK_ALIGNMENT, &old_alignment); | ||||
|   glGetIntegerv(GL_PACK_ROW_LENGTH, &old_row_length); | ||||
|   glPixelStorei(GL_PACK_ALIGNMENT, sizeof(u32)); | ||||
|   glPixelStorei(GL_PACK_ROW_LENGTH, out_data_stride / sizeof(u32)); | ||||
| 
 | ||||
|   const GLuint texture = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture_handle)); | ||||
|   GL::Texture::GetTextureSubImage(texture, 0, x, y, 0, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, | ||||
|                                   height * out_data_stride, out_data); | ||||
| 
 | ||||
|   glPixelStorei(GL_PACK_ALIGNMENT, old_alignment); | ||||
|   glPixelStorei(GL_PACK_ROW_LENGTH, old_row_length); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::SetVSync(bool enabled) | ||||
| { | ||||
|   eglSwapInterval(m_egl_display, enabled ? 1 : 0); | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::WindowResized(s32 new_window_width, s32 new_window_height) | ||||
| { | ||||
|   HostDisplay::WindowResized(new_window_width, new_window_height); | ||||
|   m_window_width = ANativeWindow_getWidth(m_window); | ||||
|   m_window_height = ANativeWindow_getHeight(m_window); | ||||
|   ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width); | ||||
|   ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height); | ||||
|   Log_InfoPrintf("WindowResized %dx%d", m_window_width, m_window_height); | ||||
| } | ||||
| 
 | ||||
| const char* AndroidGLESHostDisplay::GetGLSLVersionString() const | ||||
| { | ||||
|   return "#version 100"; | ||||
| } | ||||
| 
 | ||||
| std::string AndroidGLESHostDisplay::GetGLSLVersionHeader() const | ||||
| { | ||||
|   return R"( | ||||
| #version 100 | ||||
| 
 | ||||
| precision highp float; | ||||
| precision highp int; | ||||
| )"; | ||||
| } | ||||
| 
 | ||||
| bool AndroidGLESHostDisplay::CreateGLContext() | ||||
| { | ||||
|   m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | ||||
|   if (!m_egl_display) | ||||
|   { | ||||
|     Log_ErrorPrint("eglGetDisplay() failed"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   EGLint egl_major_version, egl_minor_version; | ||||
|   if (!eglInitialize(m_egl_display, &egl_major_version, &egl_minor_version)) | ||||
|   { | ||||
|     Log_ErrorPrint("eglInitialize() failed"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   Log_InfoPrintf("EGL version %d.%d initialized", egl_major_version, egl_minor_version); | ||||
| 
 | ||||
|   static constexpr std::array<int, 11> egl_surface_attribs = {{EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, | ||||
|                                                                EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_SURFACE_TYPE, | ||||
|                                                                EGL_WINDOW_BIT, EGL_NONE}}; | ||||
| 
 | ||||
|   int num_m_egl_configs; | ||||
|   if (!eglChooseConfig(m_egl_display, egl_surface_attribs.data(), &m_egl_config, 1, &num_m_egl_configs)) | ||||
|   { | ||||
|     Log_ErrorPrint("eglChooseConfig() failed"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   eglBindAPI(EGL_OPENGL_ES_API); | ||||
| 
 | ||||
|   // Try GLES 3, then fall back to GLES 2.
 | ||||
|   for (int major_version : {3, 2}) | ||||
|   { | ||||
|     std::array<int, 3> egl_context_attribs = {{EGL_CONTEXT_CLIENT_VERSION, major_version, EGL_NONE}}; | ||||
|     m_egl_context = eglCreateContext(m_egl_display, m_egl_config, EGL_NO_CONTEXT, egl_context_attribs.data()); | ||||
|     if (m_egl_context) | ||||
|       break; | ||||
|   } | ||||
| 
 | ||||
|   if (!m_egl_context) | ||||
|   { | ||||
|     Log_ErrorPrint("eglCreateContext() failed"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   if (!CreateSurface()) | ||||
|     return false; | ||||
| 
 | ||||
|   if (!eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context)) | ||||
|   { | ||||
|     Log_ErrorPrint("eglMakeCurrent() failed"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   // Load GLAD.
 | ||||
|   if (!gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(eglGetProcAddress))) | ||||
|   { | ||||
|     Log_ErrorPrintf("Failed to load GL functions"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   Log_InfoPrintf("GLES Version: %s", glGetString(GL_VERSION)); | ||||
|   Log_InfoPrintf("GLES Renderer: %s", glGetString(GL_RENDERER)); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| bool AndroidGLESHostDisplay::CreateSurface() | ||||
| { | ||||
|   EGLint native_visual; | ||||
|   eglGetConfigAttrib(m_egl_display, m_egl_config, EGL_NATIVE_VISUAL_ID, &native_visual); | ||||
|   ANativeWindow_setBuffersGeometry(m_window, 0, 0, native_visual); | ||||
|   m_window_width = ANativeWindow_getWidth(m_window); | ||||
|   m_window_height = ANativeWindow_getHeight(m_window); | ||||
| 
 | ||||
|   m_egl_surface = eglCreateWindowSurface(m_egl_display, m_egl_config, m_window, nullptr); | ||||
|   if (!m_egl_surface) | ||||
|   { | ||||
|     Log_ErrorPrint("eglCreateWindowSurface() failed"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   WindowResized(m_window_width, m_window_height); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::DestroySurface() | ||||
| { | ||||
|   eglDestroySurface(m_egl_display, m_egl_surface); | ||||
|   m_egl_surface = EGL_NO_SURFACE; | ||||
| } | ||||
| 
 | ||||
| bool AndroidGLESHostDisplay::CreateImGuiContext() | ||||
| { | ||||
|   if (!ImGui_ImplOpenGL3_Init(GetGLSLVersionString())) | ||||
|     return false; | ||||
| 
 | ||||
|   ImGui_ImplOpenGL3_NewFrame(); | ||||
|   ImGui::GetIO().DisplaySize.x = static_cast<float>(m_window_width); | ||||
|   ImGui::GetIO().DisplaySize.y = static_cast<float>(m_window_height); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| bool AndroidGLESHostDisplay::CreateGLResources() | ||||
| { | ||||
|   static constexpr char fullscreen_quad_vertex_shader[] = R"( | ||||
| attribute vec2 a_pos; | ||||
| attribute vec2 a_tex0; | ||||
| 
 | ||||
| varying vec2 v_tex0; | ||||
| 
 | ||||
| void main() | ||||
| { | ||||
|   v_tex0 = a_tex0; | ||||
|   gl_Position = vec4(a_pos, 0.0, 1.0); | ||||
| } | ||||
| )"; | ||||
| 
 | ||||
|   static constexpr char display_fragment_shader[] = R"( | ||||
| uniform sampler2D samp0; | ||||
| 
 | ||||
| varying vec2 v_tex0; | ||||
| 
 | ||||
| void main() | ||||
| { | ||||
|   gl_FragColor = texture2D(samp0, v_tex0); | ||||
| } | ||||
| )"; | ||||
| 
 | ||||
|   if (!m_display_program.Compile(GetGLSLVersionHeader() + fullscreen_quad_vertex_shader, | ||||
|                                  GetGLSLVersionHeader() + display_fragment_shader)) | ||||
|   { | ||||
|     Log_ErrorPrintf("Failed to compile display shaders"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   m_display_program.BindAttribute(0, "a_pos"); | ||||
|   m_display_program.BindAttribute(1, "a_tex0"); | ||||
| 
 | ||||
|   if (!m_display_program.Link()) | ||||
|   { | ||||
|     Log_ErrorPrintf("Failed to link display program"); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   m_display_program.Bind(); | ||||
|   m_display_program.RegisterUniform("samp0"); | ||||
|   m_display_program.Uniform1i(0, 0); | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<HostDisplay> AndroidGLESHostDisplay::Create(ANativeWindow* window) | ||||
| { | ||||
|   std::unique_ptr<AndroidGLESHostDisplay> display = std::make_unique<AndroidGLESHostDisplay>(window); | ||||
|   if (!display->CreateGLContext() || !display->CreateImGuiContext() || !display->CreateGLResources()) | ||||
|     return nullptr; | ||||
| 
 | ||||
|   Log_DevPrintf("%dx%d display created", display->m_window_width, display->m_window_height); | ||||
|   return display; | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::Render() | ||||
| { | ||||
|   glDisable(GL_SCISSOR_TEST); | ||||
|   glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||||
|   glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | ||||
|   glClear(GL_COLOR_BUFFER_BIT); | ||||
| 
 | ||||
|   RenderDisplay(); | ||||
| 
 | ||||
|   ImGui::Render(); | ||||
|   ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); | ||||
| 
 | ||||
|   eglSwapBuffers(m_egl_display, m_egl_surface); | ||||
| 
 | ||||
|   ImGui::NewFrame(); | ||||
|   ImGui_ImplOpenGL3_NewFrame(); | ||||
| 
 | ||||
|   GL::Program::ResetLastProgram(); | ||||
| } | ||||
| 
 | ||||
| void AndroidGLESHostDisplay::RenderDisplay() | ||||
| { | ||||
|   if (!m_display_texture_handle) | ||||
|     return; | ||||
| 
 | ||||
|   const auto [vp_left, vp_top, vp_width, vp_height] = CalculateDrawRect(); | ||||
| 
 | ||||
|   glViewport(vp_left, m_window_height - vp_top - vp_height, vp_width, vp_height); | ||||
| 
 | ||||
|   glDisable(GL_BLEND); | ||||
|   glDisable(GL_CULL_FACE); | ||||
|   glDisable(GL_DEPTH_TEST); | ||||
|   glDisable(GL_SCISSOR_TEST); | ||||
|   glDepthMask(GL_FALSE); | ||||
|   m_display_program.Bind(); | ||||
| 
 | ||||
|   const float tex_left = | ||||
|     (static_cast<float>(m_display_texture_view_x) + 0.25f) / static_cast<float>(m_display_texture_width); | ||||
|   const float tex_top = | ||||
|     (static_cast<float>(m_display_texture_view_y) - 0.25f) / static_cast<float>(m_display_texture_height); | ||||
|   const float tex_right = | ||||
|     (tex_left + static_cast<float>(m_display_texture_view_width) - 0.5f) / static_cast<float>(m_display_texture_width); | ||||
|   const float tex_bottom = | ||||
|     (tex_top + static_cast<float>(m_display_texture_view_height) + 0.5f) / static_cast<float>(m_display_texture_height); | ||||
|   const std::array<std::array<float, 4>, 4> vertices = {{ | ||||
|     {{-1.0f, -1.0f, tex_left, tex_bottom}}, // bottom-left
 | ||||
|     {{1.0f, -1.0f, tex_right, tex_bottom}}, // bottom-right
 | ||||
|     {{-1.0f, 1.0f, tex_left, tex_top}},     // top-left
 | ||||
|     {{1.0f, 1.0f, tex_right, tex_top}},     // top-right
 | ||||
|   }}; | ||||
| 
 | ||||
|   glBindBuffer(GL_ARRAY_BUFFER, 0); | ||||
|   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), &vertices[0][0]); | ||||
|   glEnableVertexAttribArray(0); | ||||
|   glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), &vertices[0][2]); | ||||
|   glEnableVertexAttribArray(1); | ||||
| 
 | ||||
|   glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(reinterpret_cast<uintptr_t>(m_display_texture_handle))); | ||||
| 
 | ||||
|   glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||||
| 
 | ||||
|   glDisableVertexAttribArray(1); | ||||
|   glDisableVertexAttribArray(0); | ||||
| } | ||||
|  | @ -1,61 +0,0 @@ | |||
| #pragma once | ||||
| #include "common/gl/program.h" | ||||
| #include "common/gl/texture.h" | ||||
| #include "core/host_display.h" | ||||
| #include <EGL/egl.h> | ||||
| #include <android/native_window.h> | ||||
| #include <glad.h> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| 
 | ||||
| class AndroidGLESHostDisplay final : public HostDisplay | ||||
| { | ||||
| public: | ||||
|   AndroidGLESHostDisplay(ANativeWindow* window); | ||||
|   ~AndroidGLESHostDisplay(); | ||||
| 
 | ||||
|   static std::unique_ptr<HostDisplay> Create(ANativeWindow* window); | ||||
| 
 | ||||
|   RenderAPI GetRenderAPI() const override; | ||||
|   void* GetRenderDevice() const override; | ||||
|   void* GetRenderContext() const override; | ||||
|   void* GetRenderWindow() const override; | ||||
| 
 | ||||
|   void ChangeRenderWindow(void* new_window) override; | ||||
|   void WindowResized(s32 new_window_width, s32 new_window_height) override; | ||||
| 
 | ||||
|   std::unique_ptr<HostDisplayTexture> CreateTexture(u32 width, u32 height, const void* data, u32 data_stride, | ||||
|                                                     bool dynamic) override; | ||||
|   void UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, | ||||
|                      u32 data_stride) override; | ||||
|   bool DownloadTexture(const void* texture_handle, u32 x, u32 y, u32 width, u32 height, void* out_data, | ||||
|                        u32 out_data_stride) override; | ||||
| 
 | ||||
|   void SetVSync(bool enabled) override; | ||||
| 
 | ||||
|   void Render() override; | ||||
| 
 | ||||
| private: | ||||
|   const char* GetGLSLVersionString() const; | ||||
|   std::string GetGLSLVersionHeader() const; | ||||
| 
 | ||||
|   bool CreateSurface(); | ||||
|   void DestroySurface(); | ||||
| 
 | ||||
|   bool CreateGLContext(); | ||||
|   bool CreateImGuiContext(); | ||||
|   bool CreateGLResources(); | ||||
| 
 | ||||
|   void RenderDisplay(); | ||||
| 
 | ||||
|   ANativeWindow* m_window = nullptr; | ||||
|   int m_window_width = 0; | ||||
|   int m_window_height = 0; | ||||
| 
 | ||||
|   EGLDisplay m_egl_display = EGL_NO_DISPLAY; | ||||
|   EGLSurface m_egl_surface = EGL_NO_SURFACE; | ||||
|   EGLContext m_egl_context = EGL_NO_CONTEXT; | ||||
|   EGLConfig m_egl_config = {}; | ||||
| 
 | ||||
|   GL::Program m_display_program; | ||||
| }; | ||||
|  | @ -1,15 +1,16 @@ | |||
| #include "android_host_interface.h" | ||||
| #include "android_gles_host_display.h" | ||||
| #include "common/assert.h" | ||||
| #include "common/audio_stream.h" | ||||
| #include "common/log.h" | ||||
| #include "common/string.h" | ||||
| #include "common/timestamp.h" | ||||
| #include "core/controller.h" | ||||
| #include "core/game_list.h" | ||||
| #include "core/gpu.h" | ||||
| #include "core/host_display.h" | ||||
| #include "core/system.h" | ||||
| #include "frontend-common/ini_settings_interface.h" | ||||
| #include "frontend-common/opengl_host_display.h" | ||||
| #include "frontend-common/vulkan_host_display.h" | ||||
| #include <android/native_window_jni.h> | ||||
| #include <cmath> | ||||
| #include <imgui.h> | ||||
|  | @ -64,7 +65,7 @@ AndroidHostInterface::~AndroidHostInterface() | |||
| 
 | ||||
| bool AndroidHostInterface::Initialize() | ||||
| { | ||||
|   if (!HostInterface::Initialize()) | ||||
|   if (!CommonHostInterface::Initialize()) | ||||
|     return false; | ||||
| 
 | ||||
|   return true; | ||||
|  | @ -75,6 +76,16 @@ void AndroidHostInterface::Shutdown() | |||
|   HostInterface::Shutdown(); | ||||
| } | ||||
| 
 | ||||
| const char* AndroidHostInterface::GetFrontendName() const | ||||
| { | ||||
|   return "DuckStation Android"; | ||||
| } | ||||
| 
 | ||||
| void AndroidHostInterface::RequestExit() | ||||
| { | ||||
|   ReportError("Ignoring RequestExit()"); | ||||
| } | ||||
| 
 | ||||
| void AndroidHostInterface::ReportError(const char* message) | ||||
| { | ||||
|   HostInterface::ReportError(message); | ||||
|  | @ -85,6 +96,11 @@ void AndroidHostInterface::ReportMessage(const char* message) | |||
|   HostInterface::ReportMessage(message); | ||||
| } | ||||
| 
 | ||||
| std::string AndroidHostInterface::GetSettingValue(const char* section, const char* key, const char* default_value) | ||||
| { | ||||
|   return m_settings_interface->GetStringValue(section, key, default_value); | ||||
| } | ||||
| 
 | ||||
| void AndroidHostInterface::SetUserDirectory() | ||||
| { | ||||
|   // TODO: Should this be customizable or use an API-determined path?
 | ||||
|  | @ -93,10 +109,13 @@ void AndroidHostInterface::SetUserDirectory() | |||
| 
 | ||||
| void AndroidHostInterface::LoadSettings() | ||||
| { | ||||
|   // check settings version, if invalid set defaults, then load settings
 | ||||
|   INISettingsInterface settings_interface(GetSettingsFileName()); | ||||
|   CheckSettings(settings_interface); | ||||
|   UpdateSettings([this, &settings_interface]() { m_settings.Load(settings_interface); }); | ||||
|   m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName()); | ||||
|   CommonHostInterface::LoadSettings(*m_settings_interface); | ||||
| } | ||||
| 
 | ||||
| void AndroidHostInterface::UpdateInputMap() | ||||
| { | ||||
|   CommonHostInterface::UpdateInputMap(*m_settings_interface); | ||||
| } | ||||
| 
 | ||||
| bool AndroidHostInterface::StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params) | ||||
|  | @ -157,6 +176,7 @@ void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function, | |||
| void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params) | ||||
| { | ||||
|   CreateImGuiContext(); | ||||
|   m_surface = initial_surface; | ||||
| 
 | ||||
|   // Boot system.
 | ||||
|   if (!BootSystem(boot_params)) | ||||
|  | @ -172,8 +192,6 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf | |||
|   m_emulation_thread_start_result.store(true); | ||||
|   m_emulation_thread_started.Signal(); | ||||
| 
 | ||||
|   ImGui::NewFrame(); | ||||
| 
 | ||||
|   while (!m_emulation_thread_stop_request.load()) | ||||
|   { | ||||
|     // run any events
 | ||||
|  | @ -203,6 +221,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf | |||
|         m_system->GetGPU()->ResetGraphicsAPIState(); | ||||
| 
 | ||||
|       m_display->Render(); | ||||
|       ImGui::NewFrame(); | ||||
| 
 | ||||
|       if (m_system) | ||||
|       { | ||||
|  | @ -221,21 +240,41 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf | |||
| 
 | ||||
| bool AndroidHostInterface::AcquireHostDisplay() | ||||
| { | ||||
|   std::unique_ptr<HostDisplay> display = AndroidGLESHostDisplay::Create(m_surface); | ||||
|   if (!display) | ||||
|   WindowInfo wi; | ||||
|   wi.type = WindowInfo::Type::Android; | ||||
|   wi.window_handle = m_surface; | ||||
|   wi.surface_width = ANativeWindow_getWidth(m_surface); | ||||
|   wi.surface_height = ANativeWindow_getHeight(m_surface); | ||||
| 
 | ||||
|   std::unique_ptr<HostDisplay> display; | ||||
|   switch (m_settings.gpu_renderer) | ||||
|   { | ||||
|     Log_ErrorPrintf("Failed to create GLES host display"); | ||||
|     case GPURenderer::HardwareVulkan: | ||||
|       display = std::make_unique<FrontendCommon::VulkanHostDisplay>(); | ||||
|       break; | ||||
| 
 | ||||
|     case GPURenderer::HardwareOpenGL: | ||||
|     default: | ||||
|       display = std::make_unique<FrontendCommon::OpenGLHostDisplay>(); | ||||
|       break; | ||||
|   } | ||||
| 
 | ||||
|   if (!display->CreateRenderDevice(wi, {}, m_settings.gpu_use_debug_device) || | ||||
|       !display->InitializeRenderDevice(GetShaderCacheBasePath(), m_settings.gpu_use_debug_device)) | ||||
|   { | ||||
|     ReportError("Failed to acquire host display."); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   m_display = display.release(); | ||||
|   m_display = std::move(display); | ||||
|   ImGui::NewFrame(); | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| void AndroidHostInterface::ReleaseHostDisplay() | ||||
| { | ||||
|   delete m_display; | ||||
|   m_display = nullptr; | ||||
|   m_display->DestroyRenderDevice(); | ||||
|   m_display.reset(); | ||||
| } | ||||
| 
 | ||||
| std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend) | ||||
|  | @ -269,14 +308,23 @@ void AndroidHostInterface::SurfaceChanged(ANativeWindow* surface, int format, in | |||
|   if (m_surface == surface) | ||||
|   { | ||||
|     if (m_display) | ||||
|       m_display->WindowResized(width, height); | ||||
|       m_display->ResizeRenderWindow(width, height); | ||||
| 
 | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   m_surface = surface; | ||||
| 
 | ||||
|   if (m_display) | ||||
|     m_display->ChangeRenderWindow(surface); | ||||
|   { | ||||
|     WindowInfo wi; | ||||
|     wi.type = WindowInfo::Type::Android; | ||||
|     wi.window_handle = surface; | ||||
|     wi.surface_width = width; | ||||
|     wi.surface_height = height; | ||||
| 
 | ||||
|     m_display->ChangeRenderWindow(wi); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void AndroidHostInterface::CreateImGuiContext() | ||||
|  | @ -369,6 +417,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) | |||
| 
 | ||||
| DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused) | ||||
| { | ||||
|   Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG); | ||||
| 
 | ||||
|   // initialize the java side
 | ||||
|   jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor); | ||||
|   if (!java_obj) | ||||
|  | @ -382,7 +432,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused) | |||
| 
 | ||||
|   // initialize the C++ side
 | ||||
|   AndroidHostInterface* cpp_obj = new AndroidHostInterface(java_obj_ref); | ||||
|   if (!cpp_obj) | ||||
|   if (!cpp_obj->Initialize()) | ||||
|   { | ||||
|     // TODO: Do we need to release the original java object reference?
 | ||||
|     Log_ErrorPrint("Failed to create C++ AndroidHostInterface"); | ||||
|  | @ -478,12 +528,14 @@ DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_ca | |||
|       gl.AddDirectory(search_dir.c_str(), search_recursively); | ||||
|   } | ||||
| 
 | ||||
|   gl.Refresh(false, false, nullptr); | ||||
| 
 | ||||
|   jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry"); | ||||
|   Assert(entry_class != nullptr); | ||||
| 
 | ||||
|   jmethodID entry_constructor = | ||||
|     env->GetMethodID(entry_class, "<init>", | ||||
|                      "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V"); | ||||
|   jmethodID entry_constructor = env->GetMethodID(entry_class, "<init>", | ||||
|                                                  "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/" | ||||
|                                                  "String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); | ||||
|   Assert(entry_constructor != nullptr); | ||||
| 
 | ||||
|   jobjectArray entry_array = env->NewObjectArray(gl.GetEntryCount(), entry_class, nullptr); | ||||
|  | @ -492,14 +544,21 @@ DEFINE_JNI_ARGS_METHOD(jarray, GameList_getEntries, jobject unused, jstring j_ca | |||
|   u32 counter = 0; | ||||
|   for (const GameListEntry& entry : gl.GetEntries()) | ||||
|   { | ||||
|     const Timestamp modified_ts( | ||||
|       Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time))); | ||||
| 
 | ||||
|     jstring path = env->NewStringUTF(entry.path.c_str()); | ||||
|     jstring code = env->NewStringUTF(entry.code.c_str()); | ||||
|     jstring title = env->NewStringUTF(entry.title.c_str()); | ||||
|     jstring region = env->NewStringUTF(Settings::GetDiscRegionName(entry.region)); | ||||
|     jstring type = env->NewStringUTF(GameList::EntryTypeToString(entry.type)); | ||||
|     jstring compatibility_rating = | ||||
|       env->NewStringUTF(GameList::EntryCompatibilityRatingToString(entry.compatibility_rating)); | ||||
|     jstring modified_time = env->NewStringUTF(modified_ts.ToString("%Y/%m/%d, %H:%M:%S")); | ||||
|     jlong size = entry.total_size; | ||||
| 
 | ||||
|     jobject entry_jobject = env->NewObject(entry_class, entry_constructor, path, code, title, region, type, size); | ||||
|     jobject entry_jobject = env->NewObject(entry_class, entry_constructor, path, code, title, size, modified_time, | ||||
|                                            region, type, compatibility_rating); | ||||
| 
 | ||||
|     env->SetObjectArrayElement(entry_array, counter++, entry_jobject); | ||||
|   } | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| #pragma once | ||||
| #include "common/event.h" | ||||
| #include "core/host_interface.h" | ||||
| #include "frontend-common/common_host_interface.h" | ||||
| #include "frontend-common/ini_settings_interface.h" | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include <functional> | ||||
|  | @ -12,7 +13,7 @@ struct ANativeWindow; | |||
| 
 | ||||
| class Controller; | ||||
| 
 | ||||
| class AndroidHostInterface final : public HostInterface | ||||
| class AndroidHostInterface final : public CommonHostInterface | ||||
| { | ||||
| public: | ||||
|   AndroidHostInterface(jobject java_object); | ||||
|  | @ -21,9 +22,14 @@ public: | |||
|   bool Initialize() override; | ||||
|   void Shutdown() override; | ||||
| 
 | ||||
|   const char* GetFrontendName() const override; | ||||
|   void RequestExit() override; | ||||
| 
 | ||||
|   void ReportError(const char* message) override; | ||||
|   void ReportMessage(const char* message) override; | ||||
| 
 | ||||
|   std::string GetSettingValue(const char* section, const char* key, const char* default_value = "") override; | ||||
| 
 | ||||
|   bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); } | ||||
|   bool StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params); | ||||
|   void RunOnEmulationThread(std::function<void()> function, bool blocking = false); | ||||
|  | @ -37,16 +43,13 @@ public: | |||
| protected: | ||||
|   void SetUserDirectory() override; | ||||
|   void LoadSettings() override; | ||||
|   void UpdateInputMap() override; | ||||
| 
 | ||||
|   bool AcquireHostDisplay() override; | ||||
|   void ReleaseHostDisplay() override; | ||||
|   std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override; | ||||
| 
 | ||||
| private: | ||||
|   enum : u32 | ||||
|   { | ||||
|     NUM_CONTROLLERS = 2 | ||||
|   }; | ||||
| 
 | ||||
|   void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params); | ||||
| 
 | ||||
|   void CreateImGuiContext(); | ||||
|  | @ -54,6 +57,8 @@ private: | |||
| 
 | ||||
|   jobject m_java_object = {}; | ||||
| 
 | ||||
|   std::unique_ptr<INISettingsInterface> m_settings_interface; | ||||
| 
 | ||||
|   ANativeWindow* m_surface = nullptr; | ||||
| 
 | ||||
|   std::mutex m_callback_mutex; | ||||
|  |  | |||
|  | @ -13,19 +13,33 @@ public class GameListEntry { | |||
|         PSExe | ||||
|     } | ||||
| 
 | ||||
|     public enum CompatibilityRating | ||||
|     { | ||||
|         Unknown, | ||||
|         DoesntBoot, | ||||
|         CrashesInIntro, | ||||
|         CrashesInGame, | ||||
|         GraphicalAudioIssues, | ||||
|         NoIssues, | ||||
|     } | ||||
| 
 | ||||
|     private String mPath; | ||||
|     private String mCode; | ||||
|     private String mTitle; | ||||
|     private long mSize; | ||||
|     private String mModifiedTime; | ||||
|     private ConsoleRegion mRegion; | ||||
|     private EntryType mType; | ||||
|     private long mSize; | ||||
|     private CompatibilityRating mCompatibilityRating; | ||||
| 
 | ||||
|     public GameListEntry(String path, String code, String title, String region, | ||||
|                          String type, long size) { | ||||
| 
 | ||||
|     public GameListEntry(String path, String code, String title, long size, String modifiedTime, String region, | ||||
|                          String type, String compatibilityRating) { | ||||
|         mPath = path; | ||||
|         mCode = code; | ||||
|         mTitle = title; | ||||
|         mSize = size; | ||||
|         mModifiedTime = modifiedTime; | ||||
| 
 | ||||
|         try { | ||||
|             mRegion = ConsoleRegion.valueOf(region); | ||||
|  | @ -38,6 +52,12 @@ public class GameListEntry { | |||
|         } catch (IllegalArgumentException e) { | ||||
|             mType = EntryType.Disc; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             mCompatibilityRating = CompatibilityRating.valueOf(compatibilityRating); | ||||
|         } catch (IllegalArgumentException e) { | ||||
|             mCompatibilityRating = CompatibilityRating.Unknown; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public String getPath() { | ||||
|  | @ -52,12 +72,16 @@ public class GameListEntry { | |||
|         return mTitle; | ||||
|     } | ||||
| 
 | ||||
|     public String getModifiedTime() { return mModifiedTime; } | ||||
| 
 | ||||
|     public ConsoleRegion getRegion() { | ||||
|         return mRegion; | ||||
|     } | ||||
| 
 | ||||
|     public EntryType getType() { return mType; } | ||||
| 
 | ||||
|     public CompatibilityRating getCompatibilityRating() { return mCompatibilityRating; } | ||||
| 
 | ||||
|     public void fillView(View view) { | ||||
|         ((TextView) view.findViewById(R.id.game_list_view_entry_title)).setText(mTitle); | ||||
|         ((TextView) view.findViewById(R.id.game_list_view_entry_path)).setText(mPath); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Connor McLaughlin
						Connor McLaughlin