Import initial work on Android frontend
|
@ -31,6 +31,8 @@ endif()
|
|||
# Required libraries.
|
||||
if(NOT ANDROID)
|
||||
find_package(SDL2 REQUIRED)
|
||||
else()
|
||||
find_package(EGL REQUIRED)
|
||||
endif()
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -0,0 +1 @@
|
|||
include(${CMAKE_CURRENT_LIST_DIR}/ECMFindModuleHelpers.cmake)
|
|
@ -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."
|
||||
)
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
DuckStation
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -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'
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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) {}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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" "$@"
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
include ':app'
|
||||
rootProject.name='DuckStation'
|