mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-18 22:35:39 +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
|
set(SRCS
|
||||||
android_host_interface.cpp
|
android_host_interface.cpp
|
||||||
android_host_interface.h
|
android_host_interface.h
|
||||||
android_gles_host_display.cpp
|
|
||||||
android_gles_host_display.h
|
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(duckstation-native SHARED ${SRCS})
|
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_host_interface.h"
|
||||||
#include "android_gles_host_display.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/audio_stream.h"
|
#include "common/audio_stream.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/string.h"
|
#include "common/string.h"
|
||||||
|
#include "common/timestamp.h"
|
||||||
#include "core/controller.h"
|
#include "core/controller.h"
|
||||||
#include "core/game_list.h"
|
#include "core/game_list.h"
|
||||||
#include "core/gpu.h"
|
#include "core/gpu.h"
|
||||||
#include "core/host_display.h"
|
#include "core/host_display.h"
|
||||||
#include "core/system.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 <android/native_window_jni.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
@ -64,7 +65,7 @@ AndroidHostInterface::~AndroidHostInterface()
|
||||||
|
|
||||||
bool AndroidHostInterface::Initialize()
|
bool AndroidHostInterface::Initialize()
|
||||||
{
|
{
|
||||||
if (!HostInterface::Initialize())
|
if (!CommonHostInterface::Initialize())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -75,6 +76,16 @@ void AndroidHostInterface::Shutdown()
|
||||||
HostInterface::Shutdown();
|
HostInterface::Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* AndroidHostInterface::GetFrontendName() const
|
||||||
|
{
|
||||||
|
return "DuckStation Android";
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::RequestExit()
|
||||||
|
{
|
||||||
|
ReportError("Ignoring RequestExit()");
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::ReportError(const char* message)
|
void AndroidHostInterface::ReportError(const char* message)
|
||||||
{
|
{
|
||||||
HostInterface::ReportError(message);
|
HostInterface::ReportError(message);
|
||||||
|
@ -85,6 +96,11 @@ void AndroidHostInterface::ReportMessage(const char* message)
|
||||||
HostInterface::ReportMessage(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()
|
void AndroidHostInterface::SetUserDirectory()
|
||||||
{
|
{
|
||||||
// TODO: Should this be customizable or use an API-determined path?
|
// TODO: Should this be customizable or use an API-determined path?
|
||||||
|
@ -93,10 +109,13 @@ void AndroidHostInterface::SetUserDirectory()
|
||||||
|
|
||||||
void AndroidHostInterface::LoadSettings()
|
void AndroidHostInterface::LoadSettings()
|
||||||
{
|
{
|
||||||
// check settings version, if invalid set defaults, then load settings
|
m_settings_interface = std::make_unique<INISettingsInterface>(GetSettingsFileName());
|
||||||
INISettingsInterface settings_interface(GetSettingsFileName());
|
CommonHostInterface::LoadSettings(*m_settings_interface);
|
||||||
CheckSettings(settings_interface);
|
}
|
||||||
UpdateSettings([this, &settings_interface]() { m_settings.Load(settings_interface); });
|
|
||||||
|
void AndroidHostInterface::UpdateInputMap()
|
||||||
|
{
|
||||||
|
CommonHostInterface::UpdateInputMap(*m_settings_interface);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidHostInterface::StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params)
|
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)
|
void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params)
|
||||||
{
|
{
|
||||||
CreateImGuiContext();
|
CreateImGuiContext();
|
||||||
|
m_surface = initial_surface;
|
||||||
|
|
||||||
// Boot system.
|
// Boot system.
|
||||||
if (!BootSystem(boot_params))
|
if (!BootSystem(boot_params))
|
||||||
|
@ -172,8 +192,6 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
|
||||||
m_emulation_thread_start_result.store(true);
|
m_emulation_thread_start_result.store(true);
|
||||||
m_emulation_thread_started.Signal();
|
m_emulation_thread_started.Signal();
|
||||||
|
|
||||||
ImGui::NewFrame();
|
|
||||||
|
|
||||||
while (!m_emulation_thread_stop_request.load())
|
while (!m_emulation_thread_stop_request.load())
|
||||||
{
|
{
|
||||||
// run any events
|
// run any events
|
||||||
|
@ -203,6 +221,7 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
|
||||||
m_system->GetGPU()->ResetGraphicsAPIState();
|
m_system->GetGPU()->ResetGraphicsAPIState();
|
||||||
|
|
||||||
m_display->Render();
|
m_display->Render();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
if (m_system)
|
if (m_system)
|
||||||
{
|
{
|
||||||
|
@ -221,21 +240,41 @@ void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surf
|
||||||
|
|
||||||
bool AndroidHostInterface::AcquireHostDisplay()
|
bool AndroidHostInterface::AcquireHostDisplay()
|
||||||
{
|
{
|
||||||
std::unique_ptr<HostDisplay> display = AndroidGLESHostDisplay::Create(m_surface);
|
WindowInfo wi;
|
||||||
if (!display)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_display = display.release();
|
m_display = std::move(display);
|
||||||
|
ImGui::NewFrame();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidHostInterface::ReleaseHostDisplay()
|
void AndroidHostInterface::ReleaseHostDisplay()
|
||||||
{
|
{
|
||||||
delete m_display;
|
m_display->DestroyRenderDevice();
|
||||||
m_display = nullptr;
|
m_display.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AndroidHostInterface::CreateAudioStream(AudioBackend backend)
|
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_surface == surface)
|
||||||
{
|
{
|
||||||
if (m_display)
|
if (m_display)
|
||||||
m_display->WindowResized(width, height);
|
m_display->ResizeRenderWindow(width, height);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_surface = surface;
|
m_surface = surface;
|
||||||
|
|
||||||
if (m_display)
|
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()
|
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)
|
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused)
|
||||||
{
|
{
|
||||||
|
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
||||||
|
|
||||||
// initialize the java side
|
// initialize the java side
|
||||||
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor);
|
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor);
|
||||||
if (!java_obj)
|
if (!java_obj)
|
||||||
|
@ -382,7 +432,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused)
|
||||||
|
|
||||||
// initialize the C++ side
|
// initialize the C++ side
|
||||||
AndroidHostInterface* cpp_obj = new AndroidHostInterface(java_obj_ref);
|
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?
|
// TODO: Do we need to release the original java object reference?
|
||||||
Log_ErrorPrint("Failed to create C++ AndroidHostInterface");
|
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.AddDirectory(search_dir.c_str(), search_recursively);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gl.Refresh(false, false, nullptr);
|
||||||
|
|
||||||
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry");
|
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/GameListEntry");
|
||||||
Assert(entry_class != nullptr);
|
Assert(entry_class != nullptr);
|
||||||
|
|
||||||
jmethodID entry_constructor =
|
jmethodID entry_constructor = env->GetMethodID(entry_class, "<init>",
|
||||||
env->GetMethodID(entry_class, "<init>",
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/"
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V");
|
"String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
|
||||||
Assert(entry_constructor != nullptr);
|
Assert(entry_constructor != nullptr);
|
||||||
|
|
||||||
jobjectArray entry_array = env->NewObjectArray(gl.GetEntryCount(), entry_class, 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;
|
u32 counter = 0;
|
||||||
for (const GameListEntry& entry : gl.GetEntries())
|
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 path = env->NewStringUTF(entry.path.c_str());
|
||||||
jstring code = env->NewStringUTF(entry.code.c_str());
|
jstring code = env->NewStringUTF(entry.code.c_str());
|
||||||
jstring title = env->NewStringUTF(entry.title.c_str());
|
jstring title = env->NewStringUTF(entry.title.c_str());
|
||||||
jstring region = env->NewStringUTF(Settings::GetDiscRegionName(entry.region));
|
jstring region = env->NewStringUTF(Settings::GetDiscRegionName(entry.region));
|
||||||
jstring type = env->NewStringUTF(GameList::EntryTypeToString(entry.type));
|
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;
|
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);
|
env->SetObjectArrayElement(entry_array, counter++, entry_jobject);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "common/event.h"
|
#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 <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
@ -12,7 +13,7 @@ struct ANativeWindow;
|
||||||
|
|
||||||
class Controller;
|
class Controller;
|
||||||
|
|
||||||
class AndroidHostInterface final : public HostInterface
|
class AndroidHostInterface final : public CommonHostInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AndroidHostInterface(jobject java_object);
|
AndroidHostInterface(jobject java_object);
|
||||||
|
@ -21,9 +22,14 @@ public:
|
||||||
bool Initialize() override;
|
bool Initialize() override;
|
||||||
void Shutdown() override;
|
void Shutdown() override;
|
||||||
|
|
||||||
|
const char* GetFrontendName() const override;
|
||||||
|
void RequestExit() override;
|
||||||
|
|
||||||
void ReportError(const char* message) override;
|
void ReportError(const char* message) override;
|
||||||
void ReportMessage(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 IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); }
|
||||||
bool StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params);
|
bool StartEmulationThread(ANativeWindow* initial_surface, SystemBootParameters boot_params);
|
||||||
void RunOnEmulationThread(std::function<void()> function, bool blocking = false);
|
void RunOnEmulationThread(std::function<void()> function, bool blocking = false);
|
||||||
|
@ -37,16 +43,13 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void SetUserDirectory() override;
|
void SetUserDirectory() override;
|
||||||
void LoadSettings() override;
|
void LoadSettings() override;
|
||||||
|
void UpdateInputMap() override;
|
||||||
|
|
||||||
bool AcquireHostDisplay() override;
|
bool AcquireHostDisplay() override;
|
||||||
void ReleaseHostDisplay() override;
|
void ReleaseHostDisplay() override;
|
||||||
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
|
std::unique_ptr<AudioStream> CreateAudioStream(AudioBackend backend) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum : u32
|
|
||||||
{
|
|
||||||
NUM_CONTROLLERS = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params);
|
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, SystemBootParameters boot_params);
|
||||||
|
|
||||||
void CreateImGuiContext();
|
void CreateImGuiContext();
|
||||||
|
@ -54,6 +57,8 @@ private:
|
||||||
|
|
||||||
jobject m_java_object = {};
|
jobject m_java_object = {};
|
||||||
|
|
||||||
|
std::unique_ptr<INISettingsInterface> m_settings_interface;
|
||||||
|
|
||||||
ANativeWindow* m_surface = nullptr;
|
ANativeWindow* m_surface = nullptr;
|
||||||
|
|
||||||
std::mutex m_callback_mutex;
|
std::mutex m_callback_mutex;
|
||||||
|
|
|
@ -13,19 +13,33 @@ public class GameListEntry {
|
||||||
PSExe
|
PSExe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum CompatibilityRating
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
DoesntBoot,
|
||||||
|
CrashesInIntro,
|
||||||
|
CrashesInGame,
|
||||||
|
GraphicalAudioIssues,
|
||||||
|
NoIssues,
|
||||||
|
}
|
||||||
|
|
||||||
private String mPath;
|
private String mPath;
|
||||||
private String mCode;
|
private String mCode;
|
||||||
private String mTitle;
|
private String mTitle;
|
||||||
|
private long mSize;
|
||||||
|
private String mModifiedTime;
|
||||||
private ConsoleRegion mRegion;
|
private ConsoleRegion mRegion;
|
||||||
private EntryType mType;
|
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;
|
mPath = path;
|
||||||
mCode = code;
|
mCode = code;
|
||||||
mTitle = title;
|
mTitle = title;
|
||||||
mSize = size;
|
mSize = size;
|
||||||
|
mModifiedTime = modifiedTime;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mRegion = ConsoleRegion.valueOf(region);
|
mRegion = ConsoleRegion.valueOf(region);
|
||||||
|
@ -38,6 +52,12 @@ public class GameListEntry {
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
mType = EntryType.Disc;
|
mType = EntryType.Disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mCompatibilityRating = CompatibilityRating.valueOf(compatibilityRating);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
mCompatibilityRating = CompatibilityRating.Unknown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
|
@ -52,12 +72,16 @@ public class GameListEntry {
|
||||||
return mTitle;
|
return mTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getModifiedTime() { return mModifiedTime; }
|
||||||
|
|
||||||
public ConsoleRegion getRegion() {
|
public ConsoleRegion getRegion() {
|
||||||
return mRegion;
|
return mRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EntryType getType() { return mType; }
|
public EntryType getType() { return mType; }
|
||||||
|
|
||||||
|
public CompatibilityRating getCompatibilityRating() { return mCompatibilityRating; }
|
||||||
|
|
||||||
public void fillView(View view) {
|
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_title)).setText(mTitle);
|
||||||
((TextView) view.findViewById(R.id.game_list_view_entry_path)).setText(mPath);
|
((TextView) view.findViewById(R.id.game_list_view_entry_path)).setText(mPath);
|
||||||
|
|
Loading…
Reference in a new issue