Import initial work on Android frontend
|
@ -31,6 +31,8 @@ endif()
|
||||||
# Required libraries.
|
# Required libraries.
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
find_package(SDL2 REQUIRED)
|
find_package(SDL2 REQUIRED)
|
||||||
|
else()
|
||||||
|
find_package(EGL REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
297
CMakeModules/ECMFindModuleHelpers.cmake
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
#.rst:
|
||||||
|
# ECMFindModuleHelpers
|
||||||
|
# --------------------
|
||||||
|
#
|
||||||
|
# Helper macros for find modules: ecm_find_package_version_check(),
|
||||||
|
# ecm_find_package_parse_components() and
|
||||||
|
# ecm_find_package_handle_library_components().
|
||||||
|
#
|
||||||
|
# ::
|
||||||
|
#
|
||||||
|
# ecm_find_package_version_check(<name>)
|
||||||
|
#
|
||||||
|
# Prints warnings if the CMake version or the project's required CMake version
|
||||||
|
# is older than that required by extra-cmake-modules.
|
||||||
|
#
|
||||||
|
# ::
|
||||||
|
#
|
||||||
|
# ecm_find_package_parse_components(<name>
|
||||||
|
# RESULT_VAR <variable>
|
||||||
|
# KNOWN_COMPONENTS <component1> [<component2> [...]]
|
||||||
|
# [SKIP_DEPENDENCY_HANDLING])
|
||||||
|
#
|
||||||
|
# This macro will populate <variable> with a list of components found in
|
||||||
|
# <name>_FIND_COMPONENTS, after checking that all those components are in the
|
||||||
|
# list of KNOWN_COMPONENTS; if there are any unknown components, it will print
|
||||||
|
# an error or warning (depending on the value of <name>_FIND_REQUIRED) and call
|
||||||
|
# return().
|
||||||
|
#
|
||||||
|
# The order of components in <variable> is guaranteed to match the order they
|
||||||
|
# are listed in the KNOWN_COMPONENTS argument.
|
||||||
|
#
|
||||||
|
# If SKIP_DEPENDENCY_HANDLING is not set, for each component the variable
|
||||||
|
# <name>_<component>_component_deps will be checked for dependent components.
|
||||||
|
# If <component> is listed in <name>_FIND_COMPONENTS, then all its (transitive)
|
||||||
|
# dependencies will also be added to <variable>.
|
||||||
|
#
|
||||||
|
# ::
|
||||||
|
#
|
||||||
|
# ecm_find_package_handle_library_components(<name>
|
||||||
|
# COMPONENTS <component> [<component> [...]]
|
||||||
|
# [SKIP_DEPENDENCY_HANDLING])
|
||||||
|
# [SKIP_PKG_CONFIG])
|
||||||
|
#
|
||||||
|
# Creates an imported library target for each component. The operation of this
|
||||||
|
# macro depends on the presence of a number of CMake variables.
|
||||||
|
#
|
||||||
|
# The <name>_<component>_lib variable should contain the name of this library,
|
||||||
|
# and <name>_<component>_header variable should contain the name of a header
|
||||||
|
# file associated with it (whatever relative path is normally passed to
|
||||||
|
# '#include'). <name>_<component>_header_subdir variable can be used to specify
|
||||||
|
# which subdirectory of the include path the headers will be found in.
|
||||||
|
# ecm_find_package_components() will then search for the library
|
||||||
|
# and include directory (creating appropriate cache variables) and create an
|
||||||
|
# imported library target named <name>::<component>.
|
||||||
|
#
|
||||||
|
# Additional variables can be used to provide additional information:
|
||||||
|
#
|
||||||
|
# If SKIP_PKG_CONFIG, the <name>_<component>_pkg_config variable is set, and
|
||||||
|
# pkg-config is found, the pkg-config module given by
|
||||||
|
# <name>_<component>_pkg_config will be searched for and used to help locate the
|
||||||
|
# library and header file. It will also be used to set
|
||||||
|
# <name>_<component>_VERSION.
|
||||||
|
#
|
||||||
|
# Note that if version information is found via pkg-config,
|
||||||
|
# <name>_<component>_FIND_VERSION can be set to require a particular version
|
||||||
|
# for each component.
|
||||||
|
#
|
||||||
|
# If SKIP_DEPENDENCY_HANDLING is not set, the INTERFACE_LINK_LIBRARIES property
|
||||||
|
# of the imported target for <component> will be set to contain the imported
|
||||||
|
# targets for the components listed in <name>_<component>_component_deps.
|
||||||
|
# <component>_FOUND will also be set to false if any of the compoments in
|
||||||
|
# <name>_<component>_component_deps are not found. This requires the components
|
||||||
|
# in <name>_<component>_component_deps to be listed before <component> in the
|
||||||
|
# COMPONENTS argument.
|
||||||
|
#
|
||||||
|
# The following variables will be set:
|
||||||
|
#
|
||||||
|
# ``<name>_TARGETS``
|
||||||
|
# the imported targets
|
||||||
|
# ``<name>_LIBRARIES``
|
||||||
|
# the found libraries
|
||||||
|
# ``<name>_INCLUDE_DIRS``
|
||||||
|
# the combined required include directories for the components
|
||||||
|
# ``<name>_DEFINITIONS``
|
||||||
|
# the "other" CFLAGS provided by pkg-config, if any
|
||||||
|
# ``<name>_VERSION``
|
||||||
|
# the value of ``<name>_<component>_VERSION`` for the first component that
|
||||||
|
# has this variable set (note that components are searched for in the order
|
||||||
|
# they are passed to the macro), although if it is already set, it will not
|
||||||
|
# be altered
|
||||||
|
#
|
||||||
|
# Note that these variables are never cleared, so if
|
||||||
|
# ecm_find_package_handle_library_components() is called multiple times with
|
||||||
|
# different components (typically because of multiple find_package() calls) then
|
||||||
|
# ``<name>_TARGETS``, for example, will contain all the targets found in any
|
||||||
|
# call (although no duplicates).
|
||||||
|
#
|
||||||
|
# Since pre-1.0.0.
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Copyright 2014 Alex Merry <alex.merry@kde.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# 3. The name of the author may not be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
include(CMakeParseArguments)
|
||||||
|
|
||||||
|
macro(ecm_find_package_version_check module_name)
|
||||||
|
if(CMAKE_VERSION VERSION_LESS 2.8.12)
|
||||||
|
message(FATAL_ERROR "CMake 2.8.12 is required by Find${module_name}.cmake")
|
||||||
|
endif()
|
||||||
|
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12)
|
||||||
|
message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use Find${module_name}.cmake")
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
macro(ecm_find_package_parse_components module_name)
|
||||||
|
set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING)
|
||||||
|
set(ecm_fppc_oneValueArgs RESULT_VAR)
|
||||||
|
set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS)
|
||||||
|
cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN})
|
||||||
|
|
||||||
|
if(ECM_FPPC_UNPARSED_ARGUMENTS)
|
||||||
|
message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}")
|
||||||
|
endif()
|
||||||
|
if(NOT ECM_FPPC_RESULT_VAR)
|
||||||
|
message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components")
|
||||||
|
endif()
|
||||||
|
if(NOT ECM_FPPC_KNOWN_COMPONENTS)
|
||||||
|
message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components")
|
||||||
|
endif()
|
||||||
|
if(NOT ECM_FPPC_DEFAULT_COMPONENTS)
|
||||||
|
set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(${module_name}_FIND_COMPONENTS)
|
||||||
|
set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS})
|
||||||
|
|
||||||
|
if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING)
|
||||||
|
# Make sure deps are included
|
||||||
|
foreach(ecm_fppc_comp ${ecm_fppc_requestedComps})
|
||||||
|
foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps})
|
||||||
|
list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index)
|
||||||
|
if("${ecm_fppc_index}" STREQUAL "-1")
|
||||||
|
if(NOT ${module_name}_FIND_QUIETLY)
|
||||||
|
message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}")
|
||||||
|
endif()
|
||||||
|
list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
else()
|
||||||
|
message(STATUS "Skipping dependency handling for ${module_name}")
|
||||||
|
endif()
|
||||||
|
list(REMOVE_DUPLICATES ecm_fppc_requestedComps)
|
||||||
|
|
||||||
|
# This makes sure components are listed in the same order as
|
||||||
|
# KNOWN_COMPONENTS (potentially important for inter-dependencies)
|
||||||
|
set(${ECM_FPPC_RESULT_VAR})
|
||||||
|
foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS})
|
||||||
|
list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index)
|
||||||
|
if(NOT "${ecm_fppc_index}" STREQUAL "-1")
|
||||||
|
list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}")
|
||||||
|
list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
# if there are any left, they are unknown components
|
||||||
|
if(ecm_fppc_requestedComps)
|
||||||
|
set(ecm_fppc_msgType STATUS)
|
||||||
|
if(${module_name}_FIND_REQUIRED)
|
||||||
|
set(ecm_fppc_msgType FATAL_ERROR)
|
||||||
|
endif()
|
||||||
|
if(NOT ${module_name}_FIND_QUIETLY)
|
||||||
|
message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}")
|
||||||
|
endif()
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS})
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
macro(ecm_find_package_handle_library_components module_name)
|
||||||
|
set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING)
|
||||||
|
set(ecm_fpwc_oneValueArgs)
|
||||||
|
set(ecm_fpwc_multiValueArgs COMPONENTS)
|
||||||
|
cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN})
|
||||||
|
|
||||||
|
if(ECM_FPWC_UNPARSED_ARGUMENTS)
|
||||||
|
message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}")
|
||||||
|
endif()
|
||||||
|
if(NOT ECM_FPWC_COMPONENTS)
|
||||||
|
message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package(PkgConfig)
|
||||||
|
foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS})
|
||||||
|
set(ecm_fpwc_dep_vars)
|
||||||
|
set(ecm_fpwc_dep_targets)
|
||||||
|
if(NOT SKIP_DEPENDENCY_HANDLING)
|
||||||
|
foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps})
|
||||||
|
list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND")
|
||||||
|
list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config)
|
||||||
|
pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET
|
||||||
|
${${module_name}_${ecm_fpwc_comp}_pkg_config})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR
|
||||||
|
NAMES ${${module_name}_${ecm_fpwc_comp}_header}
|
||||||
|
HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS}
|
||||||
|
PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir}
|
||||||
|
)
|
||||||
|
find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY
|
||||||
|
NAMES ${${module_name}_${ecm_fpwc_comp}_lib}
|
||||||
|
HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}")
|
||||||
|
if(NOT ${module_name}_VERSION)
|
||||||
|
set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp}
|
||||||
|
FOUND_VAR
|
||||||
|
${module_name}_${ecm_fpwc_comp}_FOUND
|
||||||
|
REQUIRED_VARS
|
||||||
|
${module_name}_${ecm_fpwc_comp}_LIBRARY
|
||||||
|
${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR
|
||||||
|
${ecm_fpwc_dep_vars}
|
||||||
|
VERSION_VAR
|
||||||
|
${module_name}_${ecm_fpwc_comp}_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
mark_as_advanced(
|
||||||
|
${module_name}_${ecm_fpwc_comp}_LIBRARY
|
||||||
|
${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR
|
||||||
|
)
|
||||||
|
|
||||||
|
if(${module_name}_${ecm_fpwc_comp}_FOUND)
|
||||||
|
list(APPEND ${module_name}_LIBRARIES
|
||||||
|
"${${module_name}_${ecm_fpwc_comp}_LIBRARY}")
|
||||||
|
list(APPEND ${module_name}_INCLUDE_DIRS
|
||||||
|
"${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}")
|
||||||
|
set(${module_name}_DEFINITIONS
|
||||||
|
${${module_name}_DEFINITIONS}
|
||||||
|
${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS})
|
||||||
|
if(NOT TARGET ${module_name}::${ecm_fpwc_comp})
|
||||||
|
add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}"
|
||||||
|
INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}"
|
||||||
|
INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
list(APPEND ${module_name}_TARGETS
|
||||||
|
"${module_name}::${ecm_fpwc_comp}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
if(${module_name}_LIBRARIES)
|
||||||
|
list(REMOVE_DUPLICATES ${module_name}_LIBRARIES)
|
||||||
|
endif()
|
||||||
|
if(${module_name}_INCLUDE_DIRS)
|
||||||
|
list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS)
|
||||||
|
endif()
|
||||||
|
if(${module_name}_DEFINITIONS)
|
||||||
|
list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS)
|
||||||
|
endif()
|
||||||
|
if(${module_name}_TARGETS)
|
||||||
|
list(REMOVE_DUPLICATES ${module_name}_TARGETS)
|
||||||
|
endif()
|
||||||
|
endmacro()
|
1
CMakeModules/ECMFindModuleHelpersStub.cmake
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpers.cmake)
|
172
CMakeModules/FindEGL.cmake
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
#.rst:
|
||||||
|
# FindEGL
|
||||||
|
# -------
|
||||||
|
#
|
||||||
|
# Try to find EGL.
|
||||||
|
#
|
||||||
|
# This will define the following variables:
|
||||||
|
#
|
||||||
|
# ``EGL_FOUND``
|
||||||
|
# True if (the requested version of) EGL is available
|
||||||
|
# ``EGL_VERSION``
|
||||||
|
# The version of EGL; note that this is the API version defined in the
|
||||||
|
# headers, rather than the version of the implementation (eg: Mesa)
|
||||||
|
# ``EGL_LIBRARIES``
|
||||||
|
# This can be passed to target_link_libraries() instead of the ``EGL::EGL``
|
||||||
|
# target
|
||||||
|
# ``EGL_INCLUDE_DIRS``
|
||||||
|
# This should be passed to target_include_directories() if the target is not
|
||||||
|
# used for linking
|
||||||
|
# ``EGL_DEFINITIONS``
|
||||||
|
# This should be passed to target_compile_options() if the target is not
|
||||||
|
# used for linking
|
||||||
|
#
|
||||||
|
# If ``EGL_FOUND`` is TRUE, it will also define the following imported target:
|
||||||
|
#
|
||||||
|
# ``EGL::EGL``
|
||||||
|
# The EGL library
|
||||||
|
#
|
||||||
|
# In general we recommend using the imported target, as it is easier to use.
|
||||||
|
# Bear in mind, however, that if the target is in the link interface of an
|
||||||
|
# exported library, it must be made available by the package config file.
|
||||||
|
#
|
||||||
|
# Since pre-1.0.0.
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Copyright 2014 Alex Merry <alex.merry@kde.org>
|
||||||
|
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# 3. The name of the author may not be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||||
|
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#=============================================================================
|
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpersStub.cmake)
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
include(CMakePushCheckState)
|
||||||
|
|
||||||
|
ecm_find_package_version_check(EGL)
|
||||||
|
|
||||||
|
# Use pkg-config to get the directories and then use these values
|
||||||
|
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||||
|
find_package(PkgConfig)
|
||||||
|
pkg_check_modules(PKG_EGL QUIET egl)
|
||||||
|
|
||||||
|
set(EGL_DEFINITIONS ${PKG_EGL_CFLAGS_OTHER})
|
||||||
|
|
||||||
|
find_path(EGL_INCLUDE_DIR
|
||||||
|
NAMES
|
||||||
|
EGL/egl.h
|
||||||
|
HINTS
|
||||||
|
${PKG_EGL_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
find_library(EGL_LIBRARY
|
||||||
|
NAMES
|
||||||
|
EGL
|
||||||
|
HINTS
|
||||||
|
${PKG_EGL_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
# NB: We do *not* use the version information from pkg-config, as that
|
||||||
|
# is the implementation version (eg: the Mesa version)
|
||||||
|
if(EGL_INCLUDE_DIR)
|
||||||
|
# egl.h has defines of the form EGL_VERSION_x_y for each supported
|
||||||
|
# version; so the header for EGL 1.1 will define EGL_VERSION_1_0 and
|
||||||
|
# EGL_VERSION_1_1. Finding the highest supported version involves
|
||||||
|
# finding all these defines and selecting the highest numbered.
|
||||||
|
file(READ "${EGL_INCLUDE_DIR}/EGL/egl.h" _EGL_header_contents)
|
||||||
|
string(REGEX MATCHALL
|
||||||
|
"[ \t]EGL_VERSION_[0-9_]+"
|
||||||
|
_EGL_version_lines
|
||||||
|
"${_EGL_header_contents}"
|
||||||
|
)
|
||||||
|
unset(_EGL_header_contents)
|
||||||
|
foreach(_EGL_version_line ${_EGL_version_lines})
|
||||||
|
string(REGEX REPLACE
|
||||||
|
"[ \t]EGL_VERSION_([0-9_]+)"
|
||||||
|
"\\1"
|
||||||
|
_version_candidate
|
||||||
|
"${_EGL_version_line}"
|
||||||
|
)
|
||||||
|
string(REPLACE "_" "." _version_candidate "${_version_candidate}")
|
||||||
|
if(NOT DEFINED EGL_VERSION OR EGL_VERSION VERSION_LESS _version_candidate)
|
||||||
|
set(EGL_VERSION "${_version_candidate}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
unset(_EGL_version_lines)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
cmake_push_check_state(RESET)
|
||||||
|
list(APPEND CMAKE_REQUIRED_LIBRARIES "${EGL_LIBRARY}")
|
||||||
|
list(APPEND CMAKE_REQUIRED_INCLUDES "${EGL_INCLUDE_DIR}")
|
||||||
|
|
||||||
|
check_cxx_source_compiles("
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
EGLint x = 0; EGLDisplay dpy = 0; EGLContext ctx = 0;
|
||||||
|
eglDestroyContext(dpy, ctx);
|
||||||
|
}" HAVE_EGL)
|
||||||
|
|
||||||
|
cmake_pop_check_state()
|
||||||
|
|
||||||
|
set(required_vars EGL_INCLUDE_DIR HAVE_EGL)
|
||||||
|
if(NOT EMSCRIPTEN)
|
||||||
|
list(APPEND required_vars EGL_LIBRARY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
find_package_handle_standard_args(EGL
|
||||||
|
FOUND_VAR
|
||||||
|
EGL_FOUND
|
||||||
|
REQUIRED_VARS
|
||||||
|
${required_vars}
|
||||||
|
VERSION_VAR
|
||||||
|
EGL_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
if(EGL_FOUND AND NOT TARGET EGL::EGL)
|
||||||
|
if (EMSCRIPTEN)
|
||||||
|
add_library(EGL::EGL INTERFACE IMPORTED)
|
||||||
|
# Nothing further to be done, system include paths have headers and linkage is implicit.
|
||||||
|
else()
|
||||||
|
add_library(EGL::EGL UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(EGL::EGL PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${EGL_LIBRARY}"
|
||||||
|
INTERFACE_COMPILE_OPTIONS "${EGL_DEFINITIONS}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${EGL_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
mark_as_advanced(EGL_LIBRARY EGL_INCLUDE_DIR HAVE_EGL)
|
||||||
|
|
||||||
|
# compatibility variables
|
||||||
|
set(EGL_LIBRARIES ${EGL_LIBRARY})
|
||||||
|
set(EGL_INCLUDE_DIRS ${EGL_INCLUDE_DIR})
|
||||||
|
set(EGL_VERSION_STRING ${EGL_VERSION})
|
||||||
|
|
||||||
|
include(FeatureSummary)
|
||||||
|
set_package_properties(EGL PROPERTIES
|
||||||
|
URL "https://www.khronos.org/egl/"
|
||||||
|
DESCRIPTION "A platform-agnostic mechanism for creating rendering surfaces for use with other graphics libraries, such as OpenGL|ES and OpenVG."
|
||||||
|
)
|
14
android/.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
1
android/.idea/.name
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DuckStation
|
116
android/.idea/codeStyles/Project.xml
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
16
android/.idea/gradle.xml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<compositeConfiguration>
|
||||||
|
<compositeBuild compositeDefinitionSource="SCRIPT" />
|
||||||
|
</compositeConfiguration>
|
||||||
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
|
<option name="testRunner" value="PLATFORM" />
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
9
android/.idea/misc.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
12
android/.idea/runConfigurations.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||||
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
android/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
1
android/app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
47
android/app/build.gradle
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 29
|
||||||
|
buildToolsVersion "29.0.2"
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.github.stenzek.duckstation"
|
||||||
|
minSdkVersion 21
|
||||||
|
targetSdkVersion 29
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path "../../CMakeLists.txt"
|
||||||
|
version "3.10.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defaultConfig {
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments "-DCMAKE_BUILD_TYPE=RelWithDebInfo"
|
||||||
|
abiFilters "x86"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
implementation 'androidx.preference:preference:1.1.0-alpha05'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
|
}
|
21
android/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("com.github.stenzek.duckstation", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
12
android/app/src/cpp/CMakeLists.txt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
set(SRCS
|
||||||
|
android_audio_stream.cpp
|
||||||
|
android_audio_stream.h
|
||||||
|
android_host_interface.cpp
|
||||||
|
android_host_interface.h
|
||||||
|
android_gles2_host_display.cpp
|
||||||
|
android_gles2_host_display.h
|
||||||
|
main.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(duckstation-native SHARED ${SRCS})
|
||||||
|
target_link_libraries(duckstation-native PRIVATE android core common glad imgui EGL::EGL)
|
68
android/app/src/cpp/android_audio_stream.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#include "android_audio_stream.h"
|
||||||
|
#include "YBaseLib/Assert.h"
|
||||||
|
#include "YBaseLib/Log.h"
|
||||||
|
Log_SetChannel(AndroidAudioStream);
|
||||||
|
|
||||||
|
AndroidAudioStream::AndroidAudioStream() = default;
|
||||||
|
|
||||||
|
AndroidAudioStream::~AndroidAudioStream()
|
||||||
|
{
|
||||||
|
if (m_is_open)
|
||||||
|
AndroidAudioStream::CloseDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AudioStream> AndroidAudioStream::Create()
|
||||||
|
{
|
||||||
|
return std::make_unique<AndroidAudioStream>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidAudioStream::OpenDevice()
|
||||||
|
{
|
||||||
|
DebugAssert(!m_is_open);
|
||||||
|
#if 0
|
||||||
|
SDL_AudioSpec spec = {};
|
||||||
|
spec.freq = m_output_sample_rate;
|
||||||
|
spec.channels = static_cast<Uint8>(m_channels);
|
||||||
|
spec.format = AUDIO_S16;
|
||||||
|
spec.samples = static_cast<Uint16>(m_buffer_size);
|
||||||
|
spec.callback = AudioCallback;
|
||||||
|
spec.userdata = static_cast<void*>(this);
|
||||||
|
|
||||||
|
SDL_AudioSpec obtained = {};
|
||||||
|
if (SDL_OpenAudio(&spec, &obtained) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("SDL_OpenAudio failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_is_open = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidAudioStream::PauseDevice(bool paused)
|
||||||
|
{
|
||||||
|
// SDL_PauseAudio(paused ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidAudioStream::CloseDevice()
|
||||||
|
{
|
||||||
|
DebugAssert(m_is_open);
|
||||||
|
// SDL_CloseAudio();
|
||||||
|
m_is_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
void AndroidAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
||||||
|
{
|
||||||
|
AndroidAudioStream* const this_ptr = static_cast<AndroidAudioStream*>(userdata);
|
||||||
|
const u32 num_samples = len / sizeof(SampleType) / this_ptr->m_channels;
|
||||||
|
const u32 read_samples = this_ptr->ReadSamples(reinterpret_cast<SampleType*>(stream), num_samples);
|
||||||
|
const u32 silence_samples = num_samples - read_samples;
|
||||||
|
if (silence_samples > 0)
|
||||||
|
{
|
||||||
|
std::memset(reinterpret_cast<SampleType*>(stream) + (read_samples * this_ptr->m_channels), 0,
|
||||||
|
silence_samples * this_ptr->m_channels * sizeof(SampleType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
21
android/app/src/cpp/android_audio_stream.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common/audio_stream.h"
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class AndroidAudioStream final : public AudioStream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AndroidAudioStream();
|
||||||
|
~AndroidAudioStream();
|
||||||
|
|
||||||
|
static std::unique_ptr<AudioStream> Create();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool OpenDevice() override;
|
||||||
|
void PauseDevice(bool paused) override;
|
||||||
|
void CloseDevice() override;
|
||||||
|
|
||||||
|
// static void AudioCallback(void* userdata, uint8_t* stream, int len);
|
||||||
|
|
||||||
|
bool m_is_open = false;
|
||||||
|
};
|
392
android/app/src/cpp/android_gles2_host_display.cpp
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
#include "android_gles2_host_display.h"
|
||||||
|
#include "YBaseLib/Log.h"
|
||||||
|
#include <EGL/eglext.h>
|
||||||
|
#include <array>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_impl_opengl3.h>
|
||||||
|
#include <tuple>
|
||||||
|
Log_SetChannel(AndroidGLES2HostDisplay);
|
||||||
|
|
||||||
|
class AndroidGLES2HostDisplayTexture : public HostDisplayTexture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AndroidGLES2HostDisplayTexture(GLuint id, u32 width, u32 height) : m_id(id), m_width(width), m_height(height) {}
|
||||||
|
~AndroidGLES2HostDisplayTexture() 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<AndroidGLES2HostDisplayTexture> 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<AndroidGLES2HostDisplayTexture>(id, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint m_id;
|
||||||
|
u32 m_width;
|
||||||
|
u32 m_height;
|
||||||
|
};
|
||||||
|
|
||||||
|
AndroidGLES2HostDisplay::AndroidGLES2HostDisplay(ANativeWindow* window)
|
||||||
|
: m_window(window), m_window_width(ANativeWindow_getWidth(window)), m_window_height(ANativeWindow_getHeight(window))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidGLES2HostDisplay::~AndroidGLES2HostDisplay()
|
||||||
|
{
|
||||||
|
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 AndroidGLES2HostDisplay::GetRenderAPI() const
|
||||||
|
{
|
||||||
|
return HostDisplay::RenderAPI::OpenGLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AndroidGLES2HostDisplay::GetRenderDevice() const
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AndroidGLES2HostDisplay::GetRenderContext() const
|
||||||
|
{
|
||||||
|
return m_egl_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AndroidGLES2HostDisplay::GetRenderWindow() const
|
||||||
|
{
|
||||||
|
return m_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::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> AndroidGLES2HostDisplay::CreateTexture(u32 width, u32 height, const void* data,
|
||||||
|
u32 data_stride, bool dynamic)
|
||||||
|
{
|
||||||
|
return AndroidGLES2HostDisplayTexture::Create(width, height, data, data_stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y, u32 width, u32 height,
|
||||||
|
const void* data, u32 data_stride)
|
||||||
|
{
|
||||||
|
AndroidGLES2HostDisplayTexture* tex = static_cast<AndroidGLES2HostDisplayTexture*>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::SetDisplayTexture(void* texture, s32 offset_x, s32 offset_y, s32 width, s32 height,
|
||||||
|
u32 texture_width, u32 texture_height, float aspect_ratio)
|
||||||
|
{
|
||||||
|
m_display_texture_id = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture));
|
||||||
|
m_display_offset_x = offset_x;
|
||||||
|
m_display_offset_y = offset_y;
|
||||||
|
m_display_width = width;
|
||||||
|
m_display_height = height;
|
||||||
|
m_display_texture_width = texture_width;
|
||||||
|
m_display_texture_height = texture_height;
|
||||||
|
m_display_aspect_ratio = aspect_ratio;
|
||||||
|
m_display_texture_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::SetDisplayLinearFiltering(bool enabled)
|
||||||
|
{
|
||||||
|
m_display_linear_filtering = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::SetDisplayTopMargin(int height)
|
||||||
|
{
|
||||||
|
m_display_top_margin = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::SetVSync(bool enabled)
|
||||||
|
{
|
||||||
|
eglSwapInterval(m_egl_display, enabled ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<u32, u32> AndroidGLES2HostDisplay::GetWindowSize() const
|
||||||
|
{
|
||||||
|
return std::make_tuple(static_cast<u32>(m_window_width), static_cast<u32>(m_window_height));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::WindowResized()
|
||||||
|
{
|
||||||
|
m_window_width = ANativeWindow_getWidth(m_window);
|
||||||
|
m_window_height = ANativeWindow_getHeight(m_window);
|
||||||
|
Log_InfoPrintf("WindowResized %dx%d", m_window_width, m_window_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* AndroidGLES2HostDisplay::GetGLSLVersionString() const
|
||||||
|
{
|
||||||
|
return "#version 100";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string AndroidGLES2HostDisplay::GetGLSLVersionHeader() const
|
||||||
|
{
|
||||||
|
return R"(
|
||||||
|
#version 100
|
||||||
|
|
||||||
|
precision highp float;
|
||||||
|
precision highp int;
|
||||||
|
)";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidGLES2HostDisplay::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);
|
||||||
|
|
||||||
|
static constexpr std::array<int, 3> egl_context_attribs = {{EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}};
|
||||||
|
m_egl_context = eglCreateContext(m_egl_display, m_egl_config, EGL_NO_CONTEXT, egl_context_attribs.data());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidGLES2HostDisplay::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();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::DestroySurface()
|
||||||
|
{
|
||||||
|
eglDestroySurface(m_egl_display, m_egl_surface);
|
||||||
|
m_egl_surface = EGL_NO_SURFACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidGLES2HostDisplay::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 AndroidGLES2HostDisplay::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> AndroidGLES2HostDisplay::Create(ANativeWindow* window)
|
||||||
|
{
|
||||||
|
std::unique_ptr<AndroidGLES2HostDisplay> display = std::make_unique<AndroidGLES2HostDisplay>(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 AndroidGLES2HostDisplay::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_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
|
||||||
|
eglSwapBuffers(m_egl_display, m_egl_surface);
|
||||||
|
|
||||||
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
|
||||||
|
GL::Program::ResetLastProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidGLES2HostDisplay::RenderDisplay()
|
||||||
|
{
|
||||||
|
if (!m_display_texture_id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// - 20 for main menu padding
|
||||||
|
const auto [vp_left, vp_top, vp_width, vp_height] =
|
||||||
|
CalculateDrawRect(m_window_width, std::max(m_window_height - m_display_top_margin, 1), m_display_aspect_ratio);
|
||||||
|
|
||||||
|
glViewport(vp_left, m_window_height - (m_display_top_margin + 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_offset_x) / static_cast<float>(m_display_texture_width);
|
||||||
|
const float tex_right = tex_left + static_cast<float>(m_display_width) / static_cast<float>(m_display_texture_width);
|
||||||
|
const float tex_top = static_cast<float>(m_display_offset_y) / static_cast<float>(m_display_texture_height);
|
||||||
|
const float tex_bottom =
|
||||||
|
tex_top + static_cast<float>(m_display_height) / 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
|
||||||
|
}};
|
||||||
|
|
||||||
|
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, m_display_texture_id);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
|
||||||
|
glDisableVertexAttribArray(1);
|
||||||
|
glDisableVertexAttribArray(0);
|
||||||
|
}
|
78
android/app/src/cpp/android_gles2_host_display.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#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 AndroidGLES2HostDisplay final : public HostDisplay
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AndroidGLES2HostDisplay(ANativeWindow* window);
|
||||||
|
~AndroidGLES2HostDisplay();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
void SetDisplayTexture(void* texture, s32 offset_x, s32 offset_y, s32 width, s32 height, u32 texture_width,
|
||||||
|
u32 texture_height, float aspect_ratio) override;
|
||||||
|
void SetDisplayLinearFiltering(bool enabled) override;
|
||||||
|
void SetDisplayTopMargin(int height) override;
|
||||||
|
|
||||||
|
void SetVSync(bool enabled) override;
|
||||||
|
|
||||||
|
void Render() override;
|
||||||
|
|
||||||
|
std::tuple<u32, u32> GetWindowSize() const override;
|
||||||
|
void WindowResized() 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;
|
||||||
|
GLuint m_display_texture_id = 0;
|
||||||
|
s32 m_display_offset_x = 0;
|
||||||
|
s32 m_display_offset_y = 0;
|
||||||
|
s32 m_display_width = 0;
|
||||||
|
s32 m_display_height = 0;
|
||||||
|
u32 m_display_texture_width = 0;
|
||||||
|
u32 m_display_texture_height = 0;
|
||||||
|
int m_display_top_margin = 0;
|
||||||
|
float m_display_aspect_ratio = 1.0f;
|
||||||
|
|
||||||
|
bool m_display_texture_changed = false;
|
||||||
|
bool m_display_linear_filtering = false;
|
||||||
|
};
|
416
android/app/src/cpp/android_host_interface.cpp
Normal file
|
@ -0,0 +1,416 @@
|
||||||
|
#include "android_host_interface.h"
|
||||||
|
#include "YBaseLib/Assert.h"
|
||||||
|
#include "YBaseLib/Log.h"
|
||||||
|
#include "YBaseLib/String.h"
|
||||||
|
#include "android_audio_stream.h"
|
||||||
|
#include "android_gles2_host_display.h"
|
||||||
|
#include "core/gpu.h"
|
||||||
|
#include "core/host_display.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
#include <android/native_window_jni.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
Log_SetChannel(AndroidHostInterface);
|
||||||
|
|
||||||
|
static JavaVM* s_jvm;
|
||||||
|
static jclass s_AndroidHostInterface_class;
|
||||||
|
static jmethodID s_AndroidHostInterface_constructor;
|
||||||
|
static jfieldID s_AndroidHostInterface_field_nativePointer;
|
||||||
|
|
||||||
|
// helper for retrieving the current per-thread jni environment
|
||||||
|
static JNIEnv* GetJNIEnv()
|
||||||
|
{
|
||||||
|
JNIEnv* env;
|
||||||
|
if (s_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AndroidHostInterface* GetNativeClass(JNIEnv* env, jobject obj)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<AndroidHostInterface*>(
|
||||||
|
static_cast<uintptr_t>(env->GetLongField(obj, s_AndroidHostInterface_field_nativePointer)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string JStringToString(JNIEnv* env, jstring str)
|
||||||
|
{
|
||||||
|
jsize length = env->GetStringUTFLength(str);
|
||||||
|
if (length == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const char* data = env->GetStringUTFChars(str, nullptr);
|
||||||
|
Assert(data != nullptr);
|
||||||
|
|
||||||
|
std::string ret(data, length);
|
||||||
|
env->ReleaseStringUTFChars(str, data);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidHostInterface::AndroidHostInterface(jobject java_object) : m_java_object(java_object)
|
||||||
|
{
|
||||||
|
m_settings.SetDefaults();
|
||||||
|
m_settings.bios_path = "/sdcard/PSX/BIOS/scph1001.bin";
|
||||||
|
m_settings.memory_card_a_path = "/sdcard/PSX/memory_card_a.mcd";
|
||||||
|
m_settings.gpu_renderer = GPURenderer::Software;
|
||||||
|
m_settings.video_sync_enabled = true;
|
||||||
|
m_settings.audio_sync_enabled = false;
|
||||||
|
// m_settings.debugging.show_vram = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidHostInterface::~AndroidHostInterface()
|
||||||
|
{
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
GetJNIEnv()->DeleteGlobalRef(m_java_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::ReportError(const char* message)
|
||||||
|
{
|
||||||
|
HostInterface::ReportError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::ReportMessage(const char* message)
|
||||||
|
{
|
||||||
|
HostInterface::ReportMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AndroidHostInterface::StartEmulationThread(ANativeWindow* initial_surface, std::string initial_filename,
|
||||||
|
std::string initial_state_filename)
|
||||||
|
{
|
||||||
|
Assert(!IsEmulationThreadRunning());
|
||||||
|
|
||||||
|
Log_DevPrintf("Starting emulation thread...");
|
||||||
|
m_emulation_thread_stop_request.store(false);
|
||||||
|
m_emulation_thread = std::thread(&AndroidHostInterface::EmulationThreadEntryPoint, this, initial_surface,
|
||||||
|
std::move(initial_filename), std::move(initial_state_filename));
|
||||||
|
m_emulation_thread_started.Wait();
|
||||||
|
if (!m_emulation_thread_start_result.load())
|
||||||
|
{
|
||||||
|
m_emulation_thread.join();
|
||||||
|
Log_ErrorPrint("Failed to start emulation in thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::StopEmulationThread()
|
||||||
|
{
|
||||||
|
Assert(IsEmulationThreadRunning());
|
||||||
|
Log_InfoPrint("Stopping emulation thread...");
|
||||||
|
m_emulation_thread_stop_request.store(true);
|
||||||
|
m_emulation_thread.join();
|
||||||
|
Log_InfoPrint("Emulation thread stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::RunOnEmulationThread(std::function<void()> function, bool blocking)
|
||||||
|
{
|
||||||
|
if (!IsEmulationThreadRunning())
|
||||||
|
{
|
||||||
|
function();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_callback_mutex.lock();
|
||||||
|
m_callback_queue.push_back(std::move(function));
|
||||||
|
|
||||||
|
if (blocking)
|
||||||
|
{
|
||||||
|
// TODO: Don't spin
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (m_callback_queue.empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
m_callback_mutex.unlock();
|
||||||
|
m_callback_mutex.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_callback_mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::EmulationThreadEntryPoint(ANativeWindow* initial_surface, std::string initial_filename,
|
||||||
|
std::string initial_state_filename)
|
||||||
|
{
|
||||||
|
CreateImGuiContext();
|
||||||
|
|
||||||
|
// Create display.
|
||||||
|
m_display = AndroidGLES2HostDisplay::Create(initial_surface);
|
||||||
|
if (!m_display)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("Failed to create display on emulation thread.");
|
||||||
|
DestroyImGuiContext();
|
||||||
|
m_emulation_thread_start_result.store(false);
|
||||||
|
m_emulation_thread_started.Signal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create audio stream.
|
||||||
|
m_audio_stream = AndroidAudioStream::Create();
|
||||||
|
if (!m_audio_stream || !m_audio_stream->Reconfigure(44100, 2))
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("Failed to create audio stream on emulation thread.");
|
||||||
|
m_audio_stream.reset();
|
||||||
|
m_display.reset();
|
||||||
|
DestroyImGuiContext();
|
||||||
|
m_emulation_thread_start_result.store(false);
|
||||||
|
m_emulation_thread_started.Signal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot system.
|
||||||
|
if (!CreateSystem() || !BootSystem(initial_filename.empty() ? nullptr : initial_filename.c_str(),
|
||||||
|
initial_state_filename.empty() ? nullptr : initial_state_filename.c_str()))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to boot system on emulation thread (file:%s state:%s).", initial_filename.c_str(),
|
||||||
|
initial_state_filename.c_str());
|
||||||
|
m_audio_stream.reset();
|
||||||
|
m_display.reset();
|
||||||
|
DestroyImGuiContext();
|
||||||
|
m_emulation_thread_start_result.store(false);
|
||||||
|
m_emulation_thread_started.Signal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System is ready to go.
|
||||||
|
m_emulation_thread_start_result.store(true);
|
||||||
|
m_emulation_thread_started.Signal();
|
||||||
|
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
while (!m_emulation_thread_stop_request.load())
|
||||||
|
{
|
||||||
|
// run any events
|
||||||
|
m_callback_mutex.lock();
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (m_callback_queue.empty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto callback = std::move(m_callback_queue.front());
|
||||||
|
m_callback_queue.pop_front();
|
||||||
|
m_callback_mutex.unlock();
|
||||||
|
callback();
|
||||||
|
m_callback_mutex.lock();
|
||||||
|
}
|
||||||
|
m_callback_mutex.unlock();
|
||||||
|
|
||||||
|
// simulate the system if not paused
|
||||||
|
if (m_system && !m_paused)
|
||||||
|
m_system->RunFrame();
|
||||||
|
|
||||||
|
// rendering
|
||||||
|
{
|
||||||
|
DrawImGui();
|
||||||
|
|
||||||
|
if (m_system)
|
||||||
|
m_system->GetGPU()->ResetGraphicsAPIState();
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
m_display->Render();
|
||||||
|
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
if (m_system)
|
||||||
|
{
|
||||||
|
m_system->GetGPU()->RestoreGraphicsAPIState();
|
||||||
|
|
||||||
|
if (m_speed_limiter_enabled)
|
||||||
|
Throttle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_display.reset();
|
||||||
|
m_audio_stream.reset();
|
||||||
|
DestroyImGuiContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::CreateImGuiContext()
|
||||||
|
{
|
||||||
|
ImGui::CreateContext();
|
||||||
|
|
||||||
|
ImGui::GetIO().IniFilename = nullptr;
|
||||||
|
// ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||||
|
// ImGui::GetIO().BackendFlags |= ImGuiBackendFlags_HasGamepad;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::DestroyImGuiContext()
|
||||||
|
{
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::DrawImGui()
|
||||||
|
{
|
||||||
|
DrawOSDMessages();
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::AddOSDMessage(const char* message, float duration)
|
||||||
|
{
|
||||||
|
OSDMessage msg;
|
||||||
|
msg.text = message;
|
||||||
|
msg.duration = duration;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(m_osd_messages_lock);
|
||||||
|
m_osd_messages.push_back(std::move(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::DrawOSDMessages()
|
||||||
|
{
|
||||||
|
constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
|
||||||
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
|
||||||
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lock(m_osd_messages_lock);
|
||||||
|
const float scale = ImGui::GetIO().DisplayFramebufferScale.x;
|
||||||
|
|
||||||
|
auto iter = m_osd_messages.begin();
|
||||||
|
float position_x = 10.0f * scale;
|
||||||
|
float position_y = (10.0f + (m_settings.display_fullscreen ? 0.0f : 20.0f)) * scale;
|
||||||
|
u32 index = 0;
|
||||||
|
while (iter != m_osd_messages.end())
|
||||||
|
{
|
||||||
|
const OSDMessage& msg = *iter;
|
||||||
|
const double time = msg.time.GetTimeSeconds();
|
||||||
|
const float time_remaining = static_cast<float>(msg.duration - time);
|
||||||
|
if (time_remaining <= 0.0f)
|
||||||
|
{
|
||||||
|
iter = m_osd_messages.erase(iter);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float opacity = std::min(time_remaining, 1.0f);
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(position_x, position_y));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, opacity);
|
||||||
|
|
||||||
|
if (ImGui::Begin(SmallString::FromFormat("osd_%u", index++), nullptr, window_flags))
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted(msg.text.c_str());
|
||||||
|
position_y += ImGui::GetWindowSize().y + (4.0f * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidHostInterface::SurfaceChanged(ANativeWindow* window, int format, int width, int height)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("SurfaceChanged %p %d %d %d", window, format, width, height);
|
||||||
|
if (m_display->GetRenderWindow() == window)
|
||||||
|
{
|
||||||
|
m_display->WindowResized();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_display->ChangeRenderWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
|
{
|
||||||
|
Log::GetInstance().SetDebugOutputParams(true, nullptr, LOGLEVEL_DEV);
|
||||||
|
s_jvm = vm;
|
||||||
|
|
||||||
|
JNIEnv* env = GetJNIEnv();
|
||||||
|
if ((s_AndroidHostInterface_class = env->FindClass("com/github/stenzek/duckstation/AndroidHostInterface")) == nullptr)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("AndroidHostInterface class lookup failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create global reference so it doesn't get cleaned up.
|
||||||
|
s_AndroidHostInterface_class = static_cast<jclass>(env->NewGlobalRef(s_AndroidHostInterface_class));
|
||||||
|
if (!s_AndroidHostInterface_class)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("Failed to get reference to AndroidHostInterface");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((s_AndroidHostInterface_constructor = env->GetMethodID(s_AndroidHostInterface_class, "<init>", "()V")) ==
|
||||||
|
nullptr ||
|
||||||
|
(s_AndroidHostInterface_field_nativePointer =
|
||||||
|
env->GetFieldID(s_AndroidHostInterface_class, "nativePointer", "J")) == nullptr)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("AndroidHostInterface lookups failed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEFINE_JNI_METHOD(return_type, name) \
|
||||||
|
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env)
|
||||||
|
|
||||||
|
#define DEFINE_JNI_ARGS_METHOD(return_type, name, ...) \
|
||||||
|
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(JNIEnv* env, __VA_ARGS__)
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused)
|
||||||
|
{
|
||||||
|
// initialize the java side
|
||||||
|
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor);
|
||||||
|
if (!java_obj)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("Failed to create Java AndroidHostInterface");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
jobject java_obj_ref = env->NewGlobalRef(java_obj);
|
||||||
|
Assert(java_obj_ref != nullptr);
|
||||||
|
|
||||||
|
// initialize the C++ side
|
||||||
|
AndroidHostInterface* cpp_obj = new AndroidHostInterface(java_obj_ref);
|
||||||
|
if (!cpp_obj)
|
||||||
|
{
|
||||||
|
// TODO: Do we need to release the original java object reference?
|
||||||
|
Log_ErrorPrint("Failed to create C++ AndroidHostInterface");
|
||||||
|
env->DeleteGlobalRef(java_obj_ref);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
env->SetLongField(java_obj, s_AndroidHostInterface_field_nativePointer,
|
||||||
|
static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj)));
|
||||||
|
|
||||||
|
return java_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_isEmulationThreadRunning, jobject obj)
|
||||||
|
{
|
||||||
|
return GetNativeClass(env, obj)->IsEmulationThreadRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(jboolean, AndroidHostInterface_startEmulationThread, jobject obj, jobject surface,
|
||||||
|
jstring filename, jstring state_filename)
|
||||||
|
{
|
||||||
|
ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface);
|
||||||
|
if (!native_surface)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("ANativeWindow_fromSurface() returned null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetNativeClass(env, obj)->StartEmulationThread(native_surface, JStringToString(env, filename),
|
||||||
|
JStringToString(env, state_filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_stopEmulationThread, jobject obj)
|
||||||
|
{
|
||||||
|
GetNativeClass(env, obj)->StopEmulationThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_surfaceChanged, jobject obj, jobject surface, jint format, jint width,
|
||||||
|
jint height)
|
||||||
|
{
|
||||||
|
ANativeWindow* native_surface = ANativeWindow_fromSurface(env, surface);
|
||||||
|
if (!native_surface)
|
||||||
|
Log_ErrorPrint("ANativeWindow_fromSurface() returned null");
|
||||||
|
|
||||||
|
AndroidHostInterface* hi = GetNativeClass(env, obj);
|
||||||
|
hi->RunOnEmulationThread(
|
||||||
|
[hi, native_surface, format, width, height]() { hi->SurfaceChanged(native_surface, format, width, height); }, true);
|
||||||
|
}
|
60
android/app/src/cpp/android_host_interface.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#pragma once
|
||||||
|
#include "YBaseLib/Event.h"
|
||||||
|
#include "YBaseLib/Timer.h"
|
||||||
|
#include "core/host_interface.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
struct ANativeWindow;
|
||||||
|
|
||||||
|
class AndroidHostInterface final : public HostInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AndroidHostInterface(jobject java_object);
|
||||||
|
~AndroidHostInterface() override;
|
||||||
|
|
||||||
|
void ReportError(const char* message) override;
|
||||||
|
void ReportMessage(const char* message) override;
|
||||||
|
void AddOSDMessage(const char* message, float duration = 2.0f) override;
|
||||||
|
|
||||||
|
bool IsEmulationThreadRunning() const { return m_emulation_thread.joinable(); }
|
||||||
|
bool StartEmulationThread(ANativeWindow* initial_surface, std::string initial_filename,
|
||||||
|
std::string initial_state_filename);
|
||||||
|
void RunOnEmulationThread(std::function<void()> function, bool blocking = false);
|
||||||
|
void StopEmulationThread();
|
||||||
|
|
||||||
|
void SurfaceChanged(ANativeWindow* window, int format, int width, int height);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct OSDMessage
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
Timer time;
|
||||||
|
float duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
void EmulationThreadEntryPoint(ANativeWindow* initial_surface, std::string initial_filename,
|
||||||
|
std::string initial_state_filename);
|
||||||
|
|
||||||
|
void CreateImGuiContext();
|
||||||
|
void DestroyImGuiContext();
|
||||||
|
void DrawImGui();
|
||||||
|
void DrawOSDMessages();
|
||||||
|
|
||||||
|
jobject m_java_object = {};
|
||||||
|
|
||||||
|
std::deque<OSDMessage> m_osd_messages;
|
||||||
|
std::mutex m_osd_messages_lock;
|
||||||
|
|
||||||
|
std::mutex m_callback_mutex;
|
||||||
|
std::deque<std::function<void()>> m_callback_queue;
|
||||||
|
|
||||||
|
std::thread m_emulation_thread;
|
||||||
|
std::atomic_bool m_emulation_thread_stop_request{false};
|
||||||
|
std::atomic_bool m_emulation_thread_start_result{false};
|
||||||
|
Event m_emulation_thread_started;
|
||||||
|
};
|
17
android/app/src/cpp/main.cpp
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#include "core/host_interface.h"
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
#define DEFINE_JNI_METHOD(return_type, name, ...) \
|
||||||
|
extern "C" JNIEXPORT return_type JNICALL Java_com_github_stenzek_duckstation_##name(__VA_ARGS__)
|
||||||
|
|
||||||
|
DEFINE_JNI_METHOD(bool, createSystem)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_METHOD(bool, bootSystem, const char* filename, const char* state_filename)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_JNI_METHOD(void, runFrame) {}
|
46
android/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.github.stenzek.duckstation">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme"
|
||||||
|
android:requestLegacyExternalStorage="true">
|
||||||
|
<activity
|
||||||
|
android:name=".EmulationActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||||
|
android:label="@string/title_activity_emulation"
|
||||||
|
android:parentActivityName=".MainActivity"
|
||||||
|
android:theme="@style/FullscreenTheme">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".SettingsActivity"
|
||||||
|
android:label="@string/title_activity_settings"
|
||||||
|
android:parentActivityName=".MainActivity">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||||
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
|
public class AndroidHostInterface
|
||||||
|
{
|
||||||
|
private long nativePointer;
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.loadLibrary("duckstation-native");
|
||||||
|
}
|
||||||
|
|
||||||
|
static public native AndroidHostInterface create();
|
||||||
|
|
||||||
|
public AndroidHostInterface(long nativePointer)
|
||||||
|
{
|
||||||
|
this.nativePointer = nativePointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public native boolean isEmulationThreadRunning();
|
||||||
|
public native boolean startEmulationThread(Surface surface, String filename, String state_filename);
|
||||||
|
public native void stopEmulationThread();
|
||||||
|
|
||||||
|
public native void surfaceChanged(Surface surface, int format, int width, int height);
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import androidx.core.app.NavUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An example full-screen activity that shows and hides the system UI (i.e.
|
||||||
|
* status bar and navigation/system bar) with user interaction.
|
||||||
|
*/
|
||||||
|
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
||||||
|
/** Interface to the native emulator core */
|
||||||
|
AndroidHostInterface mHostInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the system UI should be auto-hidden after
|
||||||
|
* {@link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
|
||||||
|
*/
|
||||||
|
private static final boolean AUTO_HIDE = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If {@link #AUTO_HIDE} is set, the number of milliseconds to wait after
|
||||||
|
* user interaction before hiding the system UI.
|
||||||
|
*/
|
||||||
|
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some older devices needs a small delay between UI widget updates
|
||||||
|
* and a change of the status and navigation bar.
|
||||||
|
*/
|
||||||
|
private static final int UI_ANIMATION_DELAY = 300;
|
||||||
|
private final Handler mHideHandler = new Handler();
|
||||||
|
private SurfaceView mContentView;
|
||||||
|
private final Runnable mHidePart2Runnable = new Runnable() {
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Delayed removal of status and navigation bar
|
||||||
|
|
||||||
|
// Note that some of these constants are new as of API 16 (Jelly Bean)
|
||||||
|
// and API 19 (KitKat). It is safe to use them, as they are inlined
|
||||||
|
// at compile-time and do nothing on earlier devices.
|
||||||
|
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
|
||||||
|
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||||
|
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final Runnable mShowPart2Runnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
// Delayed display of UI elements
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private boolean mVisible;
|
||||||
|
private final Runnable mHideRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Touch listener to use for in-layout UI controls to delay hiding the
|
||||||
|
* system UI. This is to prevent the jarring behavior of controls going away
|
||||||
|
* while interacting with activity UI.
|
||||||
|
*/
|
||||||
|
private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||||
|
if (AUTO_HIDE) {
|
||||||
|
delayedHide(AUTO_HIDE_DELAY_MILLIS);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
|
// Once we get a surface, we can boot.
|
||||||
|
if (mHostInterface.isEmulationThreadRunning()) {
|
||||||
|
mHostInterface.surfaceChanged(holder.getSurface(), format, width, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String filename = new String();
|
||||||
|
String state_filename = new String();
|
||||||
|
if (!mHostInterface.startEmulationThread(holder.getSurface(),filename, state_filename))
|
||||||
|
{
|
||||||
|
Log.e("EmulationActivity", "Failed to start emulation thread");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
if (!mHostInterface.isEmulationThreadRunning())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log.i("EmulationActivity", "Stopping emulation thread");
|
||||||
|
mHostInterface.stopEmulationThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_emulation);
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mVisible = true;
|
||||||
|
mContentView = (SurfaceView)findViewById(R.id.fullscreen_content);
|
||||||
|
Log.e("EmulationActivity", "adding callback");
|
||||||
|
mContentView.getHolder().addCallback(this);
|
||||||
|
|
||||||
|
|
||||||
|
// Set up the user interaction to manually show or hide the system UI.
|
||||||
|
mContentView.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mHostInterface = AndroidHostInterface.create();
|
||||||
|
if (mHostInterface == null)
|
||||||
|
throw new InstantiationError("Failed to create host interface");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// Trigger the initial hide() shortly after the activity has been
|
||||||
|
// created, to briefly hint to the user that UI controls
|
||||||
|
// are available.
|
||||||
|
delayedHide(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
if (id == android.R.id.home) {
|
||||||
|
// This ID represents the Home or Up button.
|
||||||
|
NavUtils.navigateUpFromSameTask(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggle() {
|
||||||
|
if (mVisible) {
|
||||||
|
hide();
|
||||||
|
} else {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hide() {
|
||||||
|
// Hide UI first
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.hide();
|
||||||
|
}
|
||||||
|
mVisible = false;
|
||||||
|
|
||||||
|
// Schedule a runnable to remove the status and navigation bar after a delay
|
||||||
|
mHideHandler.removeCallbacks(mShowPart2Runnable);
|
||||||
|
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private void show() {
|
||||||
|
// Show the system bar
|
||||||
|
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
|
||||||
|
mVisible = true;
|
||||||
|
|
||||||
|
// Schedule a runnable to display UI elements after a delay
|
||||||
|
mHideHandler.removeCallbacks(mHidePart2Runnable);
|
||||||
|
mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a call to hide() in delay milliseconds, canceling any
|
||||||
|
* previously scheduled calls.
|
||||||
|
*/
|
||||||
|
private void delayedHide(int delayMillis) {
|
||||||
|
mHideHandler.removeCallbacks(mHideRunnable);
|
||||||
|
mHideHandler.postDelayed(mHideRunnable, delayMillis);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
/*Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
|
||||||
|
.setAction("Action", null).show();*/
|
||||||
|
startEmulation("nonexistant.cue");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
getMenuInflater().inflate(R.menu.menu_main, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
// Handle action bar item clicks here. The action bar will
|
||||||
|
// automatically handle clicks on the Home/Up button, so long
|
||||||
|
// as you specify a parent activity in AndroidManifest.xml.
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
//noinspection SimplifiableIfStatement
|
||||||
|
if (id == R.id.action_settings) {
|
||||||
|
Intent intent = new Intent(this, SettingsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkForExternalStoragePermissions() {
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&
|
||||||
|
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean startEmulation(String bootPath) {
|
||||||
|
if (!checkForExternalStoragePermissions()) {
|
||||||
|
Snackbar.make(findViewById(R.id.fab), "External storage permissions are required to start emulation.", Snackbar.LENGTH_LONG);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Intent intent = new Intent(this, EmulationActivity.class);
|
||||||
|
intent.putExtra("bootPath", bootPath);
|
||||||
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
public class NativeLibrary {
|
||||||
|
static
|
||||||
|
{
|
||||||
|
System.loadLibrary("duckstation-native");
|
||||||
|
}
|
||||||
|
|
||||||
|
public native boolean createSystem();
|
||||||
|
public native boolean bootSystem(String filename, String stateFilename);
|
||||||
|
public native void runFrame();
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
|
public class SettingsActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.settings_activity);
|
||||||
|
getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.settings, new SettingsFragment())
|
||||||
|
.commit();
|
||||||
|
ActionBar actionBar = getSupportActionBar();
|
||||||
|
if (actionBar != null) {
|
||||||
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
@Override
|
||||||
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
|
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
170
android/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#008577"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
19
android/app/src/main/res/layout/activity_emulation.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#0099cc"
|
||||||
|
tools:context=".EmulationActivity">
|
||||||
|
|
||||||
|
<!-- The primary full-screen view. This can be replaced with whatever view
|
||||||
|
is needed to present your content, e.g. VideoView, SurfaceView,
|
||||||
|
TextureView, etc. -->
|
||||||
|
<SurfaceView
|
||||||
|
android:id="@+id/fullscreen_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:keepScreenOn="true" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
34
android/app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/content_main"/>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="@dimen/fab_margin"
|
||||||
|
app:srcCompat="@android:drawable/ic_dialog_email" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
21
android/app/src/main/res/layout/content_main.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
|
tools:showIn="@layout/activity_main"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hello World!"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
9
android/app/src/main/res/layout/settings_activity.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/settings"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</LinearLayout>
|
9
android/app/src/main/res/menu/menu_main.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context="com.github.stenzek.duckstation.MainActivity" >
|
||||||
|
<item android:id="@+id/action_settings"
|
||||||
|
android:title="@string/action_settings"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
</menu>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 15 KiB |
68
android/app/src/main/res/values/arrays.xml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<resources>
|
||||||
|
<string-array name="settings_console_region_entries">
|
||||||
|
<item>Auto-Detect</item>
|
||||||
|
<item>NTSC-J (Japan)</item>
|
||||||
|
<item>NTSC-U (US)</item>
|
||||||
|
<item>PAL (Europe, Australia)</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_console_region_values">
|
||||||
|
<item>Auto</item>
|
||||||
|
<item>NTSC-J</item>
|
||||||
|
<item>NTSC-U</item>
|
||||||
|
<item>PAL</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_cpu_execution_mode_entries">
|
||||||
|
<item>Interpreter (Slowest)</item>
|
||||||
|
<item>Cached Interpreter (Faster)</item>
|
||||||
|
<item>Recompiler (Fastest)</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_cpu_execution_mode_values">
|
||||||
|
<item>Interpreter</item>
|
||||||
|
<item>CachedInterpreter</item>
|
||||||
|
<item>Recompiler</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="gpu_renderer_entries">
|
||||||
|
<item>Hardware (OpenGL)</item>
|
||||||
|
<item>Software</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="gpu_renderer_values">
|
||||||
|
<item>OpenGL</item>
|
||||||
|
<item>Software</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_gpu_resolution_scale_entries">
|
||||||
|
<item>1x (1024x512)</item>
|
||||||
|
<item>2x (2048x1024)</item>
|
||||||
|
<item>3x (3072x1536)</item>
|
||||||
|
<item>4x (4096x2048)</item>
|
||||||
|
<item>5x (5120x2560)</item>
|
||||||
|
<item>6x (6144x3072)</item>
|
||||||
|
<item>7x (7168x3584)</item>
|
||||||
|
<item>8x (8192x4096)</item>
|
||||||
|
<item>9x (9216x4608)</item>
|
||||||
|
<item>10x (10240x5120)</item>
|
||||||
|
<item>11x (11264x5632)</item>
|
||||||
|
<item>12x (12288x6144)</item>
|
||||||
|
<item>13x (13312x6656)</item>
|
||||||
|
<item>14x (14336x7168)</item>
|
||||||
|
<item>15x (15360x7680)</item>
|
||||||
|
<item>16x (16384x8192)</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="settings_gpu_resolution_scale_values">
|
||||||
|
<item>1</item>
|
||||||
|
<item>2</item>
|
||||||
|
<item>3</item>
|
||||||
|
<item>4</item>
|
||||||
|
<item>5</item>
|
||||||
|
<item>6</item>
|
||||||
|
<item>7</item>
|
||||||
|
<item>8</item>
|
||||||
|
<item>9</item>
|
||||||
|
<item>10</item>
|
||||||
|
<item>11</item>
|
||||||
|
<item>12</item>
|
||||||
|
<item>13</item>
|
||||||
|
<item>14</item>
|
||||||
|
<item>15</item>
|
||||||
|
<item>16</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
12
android/app/src/main/res/values/attrs.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Declare custom theme attributes that allow changing which styles are
|
||||||
|
used for button bars depending on the API level.
|
||||||
|
?android:attr/buttonBarStyle is new as of API 11 so this is
|
||||||
|
necessary to support previous API levels. -->
|
||||||
|
<declare-styleable name="ButtonBarContainerTheme">
|
||||||
|
<attr name="metaButtonBarStyle" format="reference" />
|
||||||
|
<attr name="metaButtonBarButtonStyle" format="reference" />
|
||||||
|
</declare-styleable>
|
||||||
|
|
||||||
|
</resources>
|
8
android/app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#008577</color>
|
||||||
|
<color name="colorPrimaryDark">#00574B</color>
|
||||||
|
<color name="colorAccent">#D81B60</color>
|
||||||
|
|
||||||
|
<color name="black_overlay">#66000000</color>
|
||||||
|
</resources>
|
3
android/app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
</resources>
|
42
android/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">DuckStation</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="title_activity_settings">Settings</string>
|
||||||
|
|
||||||
|
<!-- Preference Titles -->
|
||||||
|
<string name="settings_console_header">Console</string>
|
||||||
|
<string name="settings_behavior_header">Behavior</string>
|
||||||
|
<string name="settings_host_synchronization_header">Host Synchronization</string>
|
||||||
|
<string name="settings_cpu_header">CPU</string>
|
||||||
|
<string name="settings_gpu_header">GPU</string>
|
||||||
|
|
||||||
|
<!-- Console Preferences -->
|
||||||
|
<string name="settings_console_region">Region</string>
|
||||||
|
<string name="settings_console_region_default">Auto</string>
|
||||||
|
<string name="settings_console_bios_path">BIOS Path</string>
|
||||||
|
<string name="settings_console_tty_output">Enable TTY Output</string>
|
||||||
|
<string name="settings_console_fast_boot">Fast Boot</string>
|
||||||
|
|
||||||
|
<!-- Behavior Preferences -->
|
||||||
|
<string name="settings_behavior_enable_speed_limiter">Enable Speed Limiter</string>
|
||||||
|
<string name="settings_behavior_pause_on_start">Pause On Start</string>
|
||||||
|
|
||||||
|
<!-- Host Synchronization Preferences -->
|
||||||
|
<string name="settings_host_synchronization_sync_to_audio">Sync To Audio</string>
|
||||||
|
<string name="settings_host_synchronization_sync_to_video">Sync To Video</string>
|
||||||
|
|
||||||
|
<!-- CPU Preferences -->
|
||||||
|
<string name="settings_cpu_execution_mode">Execution Mode</string>
|
||||||
|
<string name="settings_cpu_execution_mode_default">Interpreter</string>
|
||||||
|
|
||||||
|
<!-- GPU Preferences -->
|
||||||
|
<string name="settings_gpu_renderer">Renderer</string>
|
||||||
|
<string name="settings_gpu_renderer_default">OpenGL</string>
|
||||||
|
<string name="settings_gpu_display_linear_filtering">Display Linear Filtering</string>
|
||||||
|
<string name="settings_gpu_resolution_scale">Resolution Scale</string>
|
||||||
|
<string name="settings_gpu_true_color">True 24-Bit Color (Disables Dithering)</string>
|
||||||
|
|
||||||
|
<string name="title_activity_emulation">EmulationActivity</string>
|
||||||
|
<string name="dummy_button">Dummy Button</string>
|
||||||
|
<string name="dummy_content">DUMMY\nCONTENT</string>
|
||||||
|
</resources>
|
32
android/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||||
|
|
||||||
|
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||||
|
|
||||||
|
<style name="FullscreenTheme" parent="AppTheme">
|
||||||
|
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
|
||||||
|
<item name="android:windowActionBarOverlay">true</item>
|
||||||
|
<item name="android:windowBackground">@null</item>
|
||||||
|
<item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item>
|
||||||
|
<item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="FullscreenActionBarStyle" parent="Widget.AppCompat.ActionBar">
|
||||||
|
<item name="android:background">@color/black_overlay</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
104
android/app/src/main/res/xml/root_preferences.xml
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<!--
|
||||||
|
~ Copyright 2018 The app Open Source Project
|
||||||
|
~
|
||||||
|
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~ you may not use this file except in compliance with the License.
|
||||||
|
~ You may obtain a copy of the License at
|
||||||
|
~
|
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~
|
||||||
|
~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~ See the License for the specific language governing permissions and
|
||||||
|
~ limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/settings_console_header">
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
app:key="Console/Region"
|
||||||
|
app:title="@string/settings_console_region"
|
||||||
|
app:entries="@array/settings_console_region_entries"
|
||||||
|
app:entryValues="@array/settings_console_region_values"
|
||||||
|
app:defaultValue="@string/settings_console_region_default"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
app:key="BIOS/Path"
|
||||||
|
app:title="@string/settings_console_bios_path"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="BIOS/PatchTTYEnable"
|
||||||
|
app:title="@string/settings_console_tty_output"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="BIOS/PatchFastBoot"
|
||||||
|
app:title="@string/settings_console_fast_boot"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/settings_behavior_header">
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="General/SpeedLimiterEnabled"
|
||||||
|
app:title="@string/settings_behavior_enable_speed_limiter"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="General/StartPaused"
|
||||||
|
app:title="@string/settings_behavior_pause_on_start"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="General/SyncToAudio"
|
||||||
|
app:title="@string/settings_host_synchronization_sync_to_audio"
|
||||||
|
app:defaultValue="true"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="General/SyncToVideo"
|
||||||
|
app:title="@string/settings_host_synchronization_sync_to_video"
|
||||||
|
app:defaultValue="true"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/settings_cpu_header">
|
||||||
|
<ListPreference
|
||||||
|
app:key="CPU/ExecutionMode"
|
||||||
|
app:title="@string/settings_cpu_execution_mode"
|
||||||
|
app:entries="@array/settings_cpu_execution_mode_entries"
|
||||||
|
app:entryValues="@array/settings_cpu_execution_mode_values"
|
||||||
|
app:defaultValue="@string/settings_cpu_execution_mode_default"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
</PreferenceCategory>
|
||||||
|
<PreferenceCategory app:title="@string/settings_gpu_header">
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
app:key="GPU/Renderer"
|
||||||
|
app:title="@string/settings_gpu_renderer"
|
||||||
|
app:entries="@array/gpu_renderer_entries"
|
||||||
|
app:entryValues="@array/gpu_renderer_values"
|
||||||
|
app:defaultValue="@string/settings_gpu_renderer_default"/>
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
app:key="GPU/ResolutionScale"
|
||||||
|
app:title="@string/settings_gpu_resolution_scale"
|
||||||
|
app:entries="@array/settings_gpu_resolution_scale_entries"
|
||||||
|
app:entryValues="@array/settings_gpu_resolution_scale_values"
|
||||||
|
app:defaultValue="@string/settings_gpu_renderer_default"/>
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="GPU/TrueColor"
|
||||||
|
app:title="@string/settings_gpu_true_color"
|
||||||
|
app:defaultValue="true"/>
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
app:key="Display/LinearFiltering"
|
||||||
|
app:title="@string/settings_gpu_display_linear_filtering"
|
||||||
|
app:defaultValue="true"/>
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
27
android/build.gradle
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.5.2'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
20
android/gradle.properties
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Automatically convert third-party libraries to use AndroidX
|
||||||
|
android.enableJetifier=true
|
||||||
|
|
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#Wed Nov 27 22:04:24 AEST 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
172
android/gradlew
vendored
Executable file
|
@ -0,0 +1,172 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
84
android/gradlew.bat
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
2
android/settings.gradle
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
include ':app'
|
||||||
|
rootProject.name='DuckStation'
|