Merge pull request #764 from Zer0xFF/libretro

Libretro port
This commit is contained in:
Jean-Philip Desjardins 2020-01-06 20:35:55 -05:00 committed by GitHub
commit 884ae3b96c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 3661 additions and 36 deletions

View File

@ -42,10 +42,21 @@ travis_before_install()
travis_script()
{
if [ "$TARGET_OS" = "Android" ]; then
pushd build_android
./gradlew
./gradlew assembleRelease
popd
if [ "$BUILD_LIBRETRO" = "yes" ]; then
CMAKE_PATH=/usr/local/android-sdk/cmake/3.10.2.4988404
export PATH=${CMAKE_PATH}/bin:$PATH
export NINJA_EXE=${CMAKE_PATH}/bin/ninja
export ANDROID_NDK=/usr/local/android-sdk/ndk/20.0.5594570
export ANDROID_TOOLCHAIN_FILE=${ANDROID_NDK}/build/cmake/android.toolchain.cmake
pushd build_retro
bash android_build.sh
popd
else
pushd build_android
./gradlew
./gradlew assembleRelease
popd
fi
elif [ "$TARGET_OS" = "Linux_Clang_Format" ]; then
set +e
find ./Source/ ./tools/ -iname *.h -o -iname *.cpp -o -iname *.m -iname *.mm | xargs clang-format-6.0 -i
@ -69,7 +80,7 @@ travis_script()
if [ "$CXX" = "g++" ]; then export CXX="g++-9" CC="gcc-9"; fi
source /opt/qt512/bin/qt512-env.sh || true
export PATH=$PATH:/opt/qt512/lib/cmake
cmake .. -G"$BUILD_TYPE" -DCMAKE_INSTALL_PREFIX=./appdir/usr;
cmake .. -G"$BUILD_TYPE" -DCMAKE_INSTALL_PREFIX=./appdir/usr -DBUILD_LIBRETRO_CORE=yes;
cmake --build .
ctest
cmake --build . --target install
@ -91,13 +102,13 @@ travis_script()
fi
elif [ "$TARGET_OS" = "OSX" ]; then
export CMAKE_PREFIX_PATH="$(brew --prefix qt5)"
cmake .. -G"$BUILD_TYPE"
cmake .. -G"$BUILD_TYPE" -DBUILD_LIBRETRO_CORE=yes
cmake --build . --config Release
ctest -C Release
$(brew --prefix qt5)/bin/macdeployqt Source/ui_qt/Release/Play.app
appdmg ../installer_macosx/spec.json Play.dmg
elif [ "$TARGET_OS" = "IOS" ]; then
cmake .. -G"$BUILD_TYPE" -DCMAKE_TOOLCHAIN_FILE=../deps/Dependencies/cmake-ios/ios.cmake -DTARGET_IOS=ON -DBUILD_PSFPLAYER=ON
cmake .. -G"$BUILD_TYPE" -DCMAKE_TOOLCHAIN_FILE=../deps/Dependencies/cmake-ios/ios.cmake -DTARGET_IOS=ON -DBUILD_PSFPLAYER=ON -DBUILD_LIBRETRO_CORE=yes
cmake --build . --config Release
codesign -s "-" Source/ui_ios/Release-iphoneos/Play.app
pushd ..
@ -125,21 +136,31 @@ travis_before_deploy()
if [ "$TARGET_OS" = "Linux" ]; then
if [ "$TARGET_ARCH" = "x86_64" ]; then
cp ../../build/Play*.AppImage .
cp ../../build/Source/ui_libretro/play_libretro.so play_libretro_linux-x86_64.so
else
cp ../../build/Source/ui_libretro/play_libretro.so play_libretro_linux-ARM64.so
fi;
fi;
if [ "$TARGET_OS" = "Android" ]; then
cp ../../build_android/build/outputs/apk/release/Play-release-unsigned.apk .
export ANDROID_BUILD_TOOLS=$ANDROID_HOME/build-tools/28.0.3
$ANDROID_BUILD_TOOLS/zipalign -v -p 4 Play-release-unsigned.apk Play-release.apk
$ANDROID_BUILD_TOOLS/apksigner sign --ks ../../installer_android/deploy.keystore --ks-key-alias deploy --ks-pass env:ANDROID_KEYSTORE_PASS --key-pass env:ANDROID_KEYSTORE_PASS Play-release.apk
if [ "$BUILD_LIBRETRO" = "yes" ]; then
cp ../../build_retro/play_* .
ABI_LIST="arm64-v8a armeabi-v7a x86 x86_64"
else
cp ../../build_android/build/outputs/apk/release/Play-release-unsigned.apk .
export ANDROID_BUILD_TOOLS=$ANDROID_HOME/build-tools/28.0.3
$ANDROID_BUILD_TOOLS/zipalign -v -p 4 Play-release-unsigned.apk Play-release.apk
$ANDROID_BUILD_TOOLS/apksigner sign --ks ../../installer_android/deploy.keystore --ks-key-alias deploy --ks-pass env:ANDROID_KEYSTORE_PASS --key-pass env:ANDROID_KEYSTORE_PASS Play-release.apk
fi
fi;
if [ "$TARGET_OS" = "OSX" ]; then
cp ../../build/Play.dmg .
cp ../../build/Source/ui_libretro/Release/play_libretro.dylib play_libretro_macOS-x86_64.dylib
fi;
if [ "$TARGET_OS" = "IOS" ]; then
cp ../../installer_ios/Play.ipa .
cp ../../installer_ios/Play.deb .
cp ../../installer_ios/Packages.bz2 .
cp ../../build/Source/ui_libretro/Release/play_libretro.dylib play_libretro_iOS-FAT.dylib
fi;
popd
popd

View File

@ -43,6 +43,20 @@ matrix:
- build-tools-28.0.3
- android-28
- extra-android-m2repository
- os: linux
dist: trusty
language: android
sudo: required
env:
- TARGET_OS=Android
- BUILD_LIBRETRO=yes
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-28
- extra-android-m2repository
language: cpp

View File

@ -18,6 +18,7 @@ set(BUILD_PSFPLAYER OFF CACHE BOOL "Build PsfPlayer")
set(BUILD_TESTS ON CACHE BOOL "Build Tests")
set(USE_AOT_CACHE OFF CACHE BOOL "Use AOT block cache")
set(BUILD_AOT_CACHE OFF CACHE BOOL "Build AOT block cache (for PsfPlayer only)")
set(BUILD_LIBRETRO_CORE OFF CACHE BOOL "Build Libretro Core")
set(PROJECT_NAME "Play!")
set(PROJECT_Version 0.30)
@ -35,6 +36,13 @@ set(PROJECT_LIBS)
include(PrecompiledHeader)
if(BUILD_LIBRETRO_CORE)
if(NOT MSVC)
add_compile_options("-fPIC")
endif()
add_subdirectory(Source/ui_libretro)
endif()
if(BUILD_PLAY)
if(TARGET_PLATFORM_UNIX OR TARGET_PLATFORM_MACOS OR TARGET_PLATFORM_WIN32 OR USE_QT)
add_subdirectory(Source/ui_qt/)

View File

@ -539,6 +539,11 @@ void CPS2VM::CreateSoundHandlerImpl(const CSoundHandler::FactoryFunction& factor
m_soundHandler = factoryFunction();
}
CSoundHandler* CPS2VM::GetSoundHandler()
{
return m_soundHandler;
}
void CPS2VM::DestroySoundHandlerImpl()
{
if(m_soundHandler == nullptr) return;

View File

@ -62,6 +62,7 @@ public:
void DestroyPadHandler();
void CreateSoundHandler(const CSoundHandler::FactoryFunction&);
CSoundHandler* GetSoundHandler();
void DestroySoundHandler();
void ReloadSpuBlockCount();

View File

@ -46,14 +46,14 @@ CGSH_Direct3D9::CGSH_Direct3D9(Framework::Win32::CWindow* outputWindow)
Framework::CBitmap CGSH_Direct3D9::GetFramebuffer(uint64 frameReg)
{
Framework::CBitmap result;
m_mailBox.SendCall([&]() { result = GetFramebufferImpl(frameReg); }, true);
SendGSCall([&]() { result = GetFramebufferImpl(frameReg); }, true);
return result;
}
Framework::CBitmap CGSH_Direct3D9::GetTexture(uint64 tex0Reg, uint32 maxMip, uint64 miptbp1Reg, uint64 miptbp2Reg, uint32 mipLevel)
{
Framework::CBitmap result;
m_mailBox.SendCall([&]() { result = GetTextureImpl(tex0Reg, maxMip, miptbp1Reg, miptbp2Reg, mipLevel); }, true);
SendGSCall([&]() { result = GetTextureImpl(tex0Reg, maxMip, miptbp1Reg, miptbp2Reg, mipLevel); }, true);
return result;
}

View File

@ -56,8 +56,9 @@ static uint32 MakeColor(uint8 r, uint8 g, uint8 b, uint8 a)
return (a << 24) | (b << 16) | (g << 8) | (r);
}
CGSH_OpenGL::CGSH_OpenGL()
: m_pCvtBuffer(nullptr)
CGSH_OpenGL::CGSH_OpenGL(bool gsThreaded)
: CGSHandler(gsThreaded)
, m_pCvtBuffer(nullptr)
{
RegisterPreferences();
LoadPreferences();
@ -292,7 +293,7 @@ void CGSH_OpenGL::FlipImpl()
void CGSH_OpenGL::LoadState(Framework::CZipArchiveReader& archive)
{
CGSHandler::LoadState(archive);
m_mailBox.SendCall(
SendGSCall(
[this]() {
m_textureCache.InvalidateRange(0, RAMSIZE);
});

View File

@ -22,7 +22,7 @@
class CGSH_OpenGL : public CGSHandler
{
public:
CGSH_OpenGL();
CGSH_OpenGL(bool = true);
virtual ~CGSH_OpenGL();
static void RegisterPreferences();

View File

@ -62,13 +62,14 @@ struct MASSIVEWRITE_INFO
CGSHandler::RegisterWriteList writes;
};
CGSHandler::CGSHandler()
CGSHandler::CGSHandler(bool gsThreaded)
: m_threadDone(false)
, m_drawCallCount(0)
, m_pCLUT(nullptr)
, m_pRAM(nullptr)
, m_frameDump(nullptr)
, m_loggingEnabled(true)
, m_gsThreaded(gsThreaded)
{
RegisterPreferences();
@ -100,13 +101,19 @@ CGSHandler::CGSHandler()
ResetBase();
m_thread = std::thread([&]() { ThreadProc(); });
if(m_gsThreaded)
{
m_thread = std::thread([&]() { ThreadProc(); });
}
}
CGSHandler::~CGSHandler()
{
m_mailBox.SendCall([this]() { m_threadDone = true; });
m_thread.join();
if(m_gsThreaded)
{
SendGSCall([this]() { m_threadDone = true; });
m_thread.join();
}
delete[] m_pRAM;
delete[] m_pCLUT;
}
@ -118,7 +125,7 @@ void CGSHandler::RegisterPreferences()
void CGSHandler::NotifyPreferencesChanged()
{
m_mailBox.SendCall([this]() { NotifyPreferencesChangedImpl(); });
SendGSCall([this]() { NotifyPreferencesChangedImpl(); });
}
void CGSHandler::SetIntc(CINTC* intc)
@ -129,7 +136,7 @@ void CGSHandler::SetIntc(CINTC* intc)
void CGSHandler::Reset()
{
ResetBase();
m_mailBox.SendCall(std::bind(&CGSHandler::ResetImpl, this), true);
SendGSCall(std::bind(&CGSHandler::ResetImpl, this), true);
}
void CGSHandler::ResetBase()
@ -374,27 +381,28 @@ void CGSHandler::WritePrivRegister(uint32 nAddress, uint32 nData)
void CGSHandler::Initialize()
{
m_mailBox.SendCall(std::bind(&CGSHandler::InitializeImpl, this), true);
SendGSCall(std::bind(&CGSHandler::InitializeImpl, this), true);
}
void CGSHandler::Release()
{
m_mailBox.SendCall(std::bind(&CGSHandler::ReleaseImpl, this), true);
SendGSCall(std::bind(&CGSHandler::ReleaseImpl, this), true);
}
void CGSHandler::Flip(bool showOnly)
{
if(!showOnly)
{
m_mailBox.FlushCalls();
m_mailBox.SendCall(std::bind(&CGSHandler::MarkNewFrame, this));
SendGSCall([]() {}, true);
SendGSCall(std::bind(&CGSHandler::MarkNewFrame, this));
}
m_mailBox.SendCall(std::bind(&CGSHandler::FlipImpl, this), true);
SendGSCall(std::bind(&CGSHandler::FlipImpl, this), true, true);
}
void CGSHandler::FlipImpl()
{
OnFlipComplete();
m_flipped = true;
}
void CGSHandler::MarkNewFrame()
@ -428,7 +436,7 @@ void CGSHandler::SetSMODE2(uint64 value)
void CGSHandler::WriteRegister(uint8 registerId, uint64 value)
{
m_mailBox.SendCall(std::bind(&CGSHandler::WriteRegisterImpl, this, registerId, value));
SendGSCall(std::bind(&CGSHandler::WriteRegisterImpl, this, registerId, value));
}
void CGSHandler::FeedImageData(const void* data, uint32 length)
@ -440,7 +448,7 @@ void CGSHandler::FeedImageData(const void* data, uint32 length)
std::vector<uint8> imageData(length + 0x10);
memcpy(imageData.data(), data, length);
m_mailBox.SendCall(
SendGSCall(
[this, imageData = std::move(imageData), length]() {
FeedImageDataImpl(imageData.data(), length);
});
@ -448,7 +456,7 @@ void CGSHandler::FeedImageData(const void* data, uint32 length)
void CGSHandler::ReadImageData(void* data, uint32 length)
{
m_mailBox.SendCall([this, data, length]() { ReadImageDataImpl(data, length); }, true);
SendGSCall([this, data, length]() { ReadImageDataImpl(data, length); }, true);
}
void CGSHandler::WriteRegisterMassively(RegisterWriteList registerWrites, const CGsPacketMetadata* metadata)
@ -500,7 +508,7 @@ void CGSHandler::WriteRegisterMassively(RegisterWriteList registerWrites, const
}
#endif
m_mailBox.SendCall(
SendGSCall(
[this, massiveWrite = std::move(massiveWrite)]() {
WriteRegisterMassivelyImpl(massiveWrite);
});
@ -1728,6 +1736,30 @@ void CGSHandler::ThreadProc()
}
}
void CGSHandler::SendGSCall(const CMailBox::FunctionType& function, bool waitForCompletion, bool forceWaitForCompletion)
{
if(!m_gsThreaded)
{
waitForCompletion = false;
}
waitForCompletion |= forceWaitForCompletion;
m_mailBox.SendCall(function, waitForCompletion);
}
void CGSHandler::ProcessSingleFrame()
{
assert(!m_gsThreaded);
while(!m_flipped)
{
m_mailBox.WaitForCall();
while(m_mailBox.IsPending() && !m_flipped)
{
m_mailBox.ReceiveCall();
}
}
m_flipped = false;
}
Framework::CBitmap CGSHandler::GetScreenshot()
{
throw std::runtime_error("Screenshot feature is not implemented in current backend.");

View File

@ -704,7 +704,7 @@ public:
typedef Framework::CSignal<void()> FlipCompleteEvent;
typedef Framework::CSignal<void(uint32)> NewFrameEvent;
CGSHandler();
CGSHandler(bool = true);
virtual ~CGSHandler();
static void RegisterPreferences();
@ -763,6 +763,7 @@ public:
bool GetCrtIsFrameMode() const;
virtual Framework::CBitmap GetScreenshot();
void ProcessSingleFrame();
FlipCompleteEvent OnFlipComplete;
NewFrameEvent OnNewFrame;
@ -952,6 +953,7 @@ protected:
void ReadCLUT8(const TEX0&);
static bool IsCompatibleFramebufferPSM(unsigned int, unsigned int);
void SendGSCall(const CMailBox::FunctionType&, bool = false, bool = false);
bool m_loggingEnabled;
@ -983,9 +985,13 @@ protected:
std::thread m_thread;
std::recursive_mutex m_registerMutex;
std::atomic<int> m_transferCount;
CMailBox m_mailBox;
bool m_threadDone;
CFrameDump* m_frameDump;
bool m_drawEnabled = true;
CINTC* m_intc = nullptr;
bool m_gsThreaded = true;
bool m_flipped = false;
private:
CMailBox m_mailBox;
};

View File

@ -58,7 +58,7 @@ void CGSH_OpenGLAndroid::PresentBackbuffer()
void CGSH_OpenGLAndroid::SetWindow(NativeWindowType window)
{
m_window = window;
m_mailBox.SendCall(
SendGSCall(
[this]() {
SetupContext();
},

View File

@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.5)
set(CMAKE_MODULE_PATH
${CMAKE_CURRENT_SOURCE_DIR}/../../../deps/Dependencies/cmake-modules
${CMAKE_MODULE_PATH}
)
include(Header)
project(Play_Libretro_Core)
add_definitions(-DPLAY_VERSION="${PROJECT_Version}")
if(NOT TARGET PlayCore)
add_subdirectory(
${CMAKE_CURRENT_SOURCE_DIR}/../
${CMAKE_CURRENT_BINARY_DIR}/Source
)
endif()
list(APPEND PROJECT_LIBS PlayCore)
if(NOT TARGET gsh_opengl)
add_subdirectory(
${CMAKE_CURRENT_SOURCE_DIR}/../gs/GSH_OpenGL
${CMAKE_CURRENT_BINARY_DIR}/gs/GSH_OpenGL
)
endif()
list(INSERT PROJECT_LIBS 0 gsh_opengl)
set(SRC
main_libretro.cpp
GSH_OpenGL_Libretro.cpp
GSH_OpenGL_Libretro.h
PH_Libretro_Input.cpp
PH_Libretro_Input.h
SH_LibreAudio.cpp
SH_LibreAudio.h
)
if(TARGET_PLATFORM_ANDROID)
list(APPEND PROJECT_LIBS android log GLESv3 EGL)
elseif(TARGET_PLATFORM_IOS)
list(APPEND PROJECT_LIBS "-ObjC -lsqlite3 -framework OpenGLES")
endif()
add_library(play_libretro SHARED ${SRC})
target_include_directories(play_libretro PRIVATE
./
../../
${CMAKE_CURRENT_BINARY_DIR}
)
if(TARGET_PLATFORM_ANDROID)
set_target_properties(play_libretro PROPERTIES SUFFIX "_android.so")
endif()
if(TARGET_PLATFORM_IOS)
set_target_properties(play_libretro PROPERTIES SUFFIX "_ios.dylib")
endif()
target_link_libraries(play_libretro ${PROJECT_LIBS})
set_target_properties(play_libretro PROPERTIES PREFIX "")

View File

@ -0,0 +1,114 @@
#include "GSH_OpenGL_Libretro.h"
#include "Log.h"
#define LOG_NAME "LIBRETRO"
extern int g_res_factor;
extern CGSHandler::PRESENTATION_MODE g_presentation_mode;
extern retro_video_refresh_t g_video_cb;
extern struct retro_hw_render_callback g_hw_render;
CGSH_OpenGL_Libretro::CGSH_OpenGL_Libretro()
: CGSH_OpenGL(false)
{
}
CGSH_OpenGL_Libretro::~CGSH_OpenGL_Libretro()
{
}
CGSH_OpenGL::FactoryFunction CGSH_OpenGL_Libretro::GetFactoryFunction()
{
return []() { return new CGSH_OpenGL_Libretro(); };
}
void CGSH_OpenGL_Libretro::InitializeImpl()
{
fprintf(stderr, "%s\n", __FUNCTION__);
#if defined(USE_GLEW)
glewExperimental = GL_TRUE;
auto result = glewInit();
CLog::GetInstance().Warn(LOG_NAME, "glewInit %d\n", result == GLEW_OK);
assert(result == GLEW_OK);
if(result != GLEW_OK)
{
fprintf(stderr, "Error: %s\n", glewGetErrorString(result));
CLog::GetInstance().Warn(LOG_NAME, "Error: %s\n", glewGetErrorString(result));
return;
}
#endif
if(g_hw_render.get_current_framebuffer)
m_presentFramebuffer = g_hw_render.get_current_framebuffer();
UpdatePresentationImpl();
CGSH_OpenGL::InitializeImpl();
}
void CGSH_OpenGL_Libretro::UpdatePresentation()
{
SendGSCall([this]() { UpdatePresentationImpl(); });
}
void CGSH_OpenGL_Libretro::UpdatePresentationImpl()
{
PRESENTATION_PARAMS presentationParams;
presentationParams.mode = g_presentation_mode;
presentationParams.windowWidth = GetCrtWidth() * g_res_factor;
presentationParams.windowHeight = GetCrtHeight() * g_res_factor;
SetPresentationParams(presentationParams);
NotifyPreferencesChanged();
}
void CGSH_OpenGL_Libretro::FlushMailBox()
{
bool isFlushed = false;
SendGSCall([&]() {
isFlushed = true;
},
true);
while(!isFlushed)
{
// Wait for flush to complete
ProcessSingleFrame();
}
}
void CGSH_OpenGL_Libretro::Reset()
{
FlushMailBox();
ResetBase();
CGSH_OpenGL::ReleaseImpl();
InitializeImpl();
}
void CGSH_OpenGL_Libretro::Release()
{
FlushMailBox();
ResetBase();
CGSH_OpenGL::ReleaseImpl();
}
void CGSH_OpenGL_Libretro::FlipImpl()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(g_hw_render.get_current_framebuffer)
m_presentFramebuffer = g_hw_render.get_current_framebuffer();
else
return;
CGSH_OpenGL::FlipImpl();
}
void CGSH_OpenGL_Libretro::PresentBackbuffer()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(g_video_cb)
g_video_cb(RETRO_HW_FRAME_BUFFER_VALID, GetCrtWidth() * g_res_factor, GetCrtHeight() * g_res_factor, 0);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include "gs/GSH_OpenGL/GSH_OpenGL.h"
#include "libretro.h"
class CGSH_OpenGL_Libretro : public CGSH_OpenGL
{
public:
CGSH_OpenGL_Libretro();
virtual ~CGSH_OpenGL_Libretro();
static FactoryFunction GetFactoryFunction();
void InitializeImpl() override;
void FlipImpl() override;
void Reset();
void Release();
void PresentBackbuffer() override;
void UpdatePresentation();
void FlushMailBox();
private:
void UpdatePresentationImpl();
};

View File

@ -0,0 +1,71 @@
#include "PH_Libretro_Input.h"
extern bool libretro_supports_bitmasks;
extern retro_input_poll_t g_input_poll_cb;
extern retro_input_state_t g_input_state_cb;
extern std::map<int, int> g_ds2_to_retro_btn_map;
void CPH_Libretro_Input::UpdateInputState()
{
std::lock_guard<std::mutex> lock(m_input_mutex);
g_input_poll_cb();
if(libretro_supports_bitmasks)
{
m_btns_state = g_input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_MASK);
}
else
{
m_btns_state = 0;
for(unsigned int i = 0; i <= RETRO_DEVICE_ID_JOYPAD_R3; i++)
{
if(g_input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, i))
m_btns_state |= (1 << i);
}
}
for(unsigned int i = 0; i <= PS2::CControllerInfo::ANALOG_RIGHT_Y + 1; i++)
{
auto currentButtonId = static_cast<PS2::CControllerInfo::BUTTON>(i);
if(PS2::CControllerInfo::IsAxis(currentButtonId))
{
int index;
if(i < 2)
index = RETRO_DEVICE_INDEX_ANALOG_LEFT;
else
index = RETRO_DEVICE_INDEX_ANALOG_RIGHT;
float value = g_input_state_cb(0, RETRO_DEVICE_ANALOG, index, g_ds2_to_retro_btn_map[currentButtonId]);
uint8 val = static_cast<int8>((value / 0xFF) + 0.5) + 0x7F;
if(val > 0x7F - 5 && val < 0x7F + 5)
val = 0x7F;
m_axis_btn_state[currentButtonId] = val;
}
}
}
void CPH_Libretro_Input::Update(uint8* ram)
{
std::lock_guard<std::mutex> lock(m_input_mutex);
for(auto* listener : m_listeners)
{
for(unsigned int i = 0; i < PS2::CControllerInfo::MAX_BUTTONS; i++)
{
auto currentButtonId = static_cast<PS2::CControllerInfo::BUTTON>(i);
if(PS2::CControllerInfo::IsAxis(currentButtonId))
{
listener->SetAxisState(0, currentButtonId, m_axis_btn_state[currentButtonId], ram);
}
else
{
uint32 val = m_btns_state & (1 << g_ds2_to_retro_btn_map[currentButtonId]);
listener->SetButtonState(0, currentButtonId, val != 0, ram);
}
}
}
}
CPadHandler::FactoryFunction CPH_Libretro_Input::GetFactoryFunction()
{
return []() { return new CPH_Libretro_Input(); };
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <map>
#include <mutex>
#include "PadHandler.h"
// #include "InputBindingManager.h"
#include "libretro.h"
class CPH_Libretro_Input : public CPadHandler
{
public:
CPH_Libretro_Input() = default;
virtual ~CPH_Libretro_Input() = default;
void Update(uint8*) override;
static FactoryFunction GetFactoryFunction();
void UpdateInputState();
private:
int16 m_btns_state = 0;
uint8 m_axis_btn_state[4] = {0x7F};
std::mutex m_input_mutex;
};

View File

@ -0,0 +1,43 @@
#include "SH_LibreAudio.h"
#include "libretro.h"
#include <cstring>
#include <mutex>
extern retro_audio_sample_batch_t g_set_audio_sample_batch_cb;
std::mutex m_buffer_lock;
CSoundHandler* CSH_LibreAudio::HandlerFactory()
{
return new CSH_LibreAudio();
}
void CSH_LibreAudio::Write(int16* buffer, unsigned int sampleCount, unsigned int sampleRate)
{
std::lock_guard<std::mutex> lock(m_buffer_lock);
m_buffer.resize(sampleCount * sizeof(int16));
memcpy(m_buffer.data(), buffer, sampleCount * sizeof(int16));
}
void CSH_LibreAudio::ProcessBuffer()
{
if(!m_buffer.empty())
{
std::lock_guard<std::mutex> lock(m_buffer_lock);
if(g_set_audio_sample_batch_cb)
g_set_audio_sample_batch_cb(m_buffer.data(), m_buffer.size() / (2 * sizeof(int16)));
m_buffer.clear();
}
}
bool CSH_LibreAudio::HasFreeBuffers()
{
return false;
}
void CSH_LibreAudio::Reset()
{
}
void CSH_LibreAudio::RecycleBuffers()
{
}

View File

@ -0,0 +1,26 @@
#pragma once
#include "tools/PsfPlayer/Source/SoundHandler.h"
#include <deque>
#include <vector>
#include <mutex>
class CSH_LibreAudio : public CSoundHandler
{
public:
CSH_LibreAudio() = default;
static CSoundHandler* HandlerFactory();
void Reset() override;
void Write(int16*, unsigned int, unsigned int) override;
bool HasFreeBuffers() override;
void RecycleBuffers() override;
void ProcessBuffer();
private:
std::vector<int16> m_buffer;
std::mutex m_buffer_lock;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,589 @@
#include "libretro.h"
#include "Log.h"
#include "AppConfig.h"
#include "PS2VM.h"
#include "PS2VM_Preferences.h"
#include "GSH_OpenGL_Libretro.h"
#include "SH_LibreAudio.h"
#include "PH_Libretro_Input.h"
#include "PathUtils.h"
#include "PtrStream.h"
#include "MemStream.h"
#include "filesystem_def.h"
#include <vector>
#include <cstdlib>
#define LOG_NAME "LIBRETRO"
static CPS2VM* m_virtualMachine = nullptr;
static bool first_run = false;
bool libretro_supports_bitmasks = false;
retro_video_refresh_t g_video_cb;
retro_environment_t g_environ_cb;
retro_input_poll_t g_input_poll_cb;
retro_input_state_t g_input_state_cb;
retro_audio_sample_batch_t g_set_audio_sample_batch_cb;
std::map<int, int> g_ds2_to_retro_btn_map;
struct retro_hw_render_callback g_hw_render
{
};
int g_res_factor = 1;
CGSHandler::PRESENTATION_MODE g_presentation_mode = CGSHandler::PRESENTATION_MODE::PRESENTATION_MODE_FIT;
bool g_forceBilinearTextures = false;
static std::vector<struct retro_variable> m_vars =
{
{"play_res_multi", "Resolution Multiplier; 1x|2x|4x|8x"},
{"play_presentation_mode", "Presentation Mode; Fit Screen|Fill Screen|Original Size"},
{"play_bilinear_filtering", "Force Bilinear Filtering; false|true"},
{NULL, NULL},
};
enum BootType
{
CD,
ELF
};
struct LastOpenCommand
{
LastOpenCommand() = default;
LastOpenCommand(BootType type, fs::path path)
: type(type)
, path(path)
{
}
BootType type = BootType::CD;
fs::path path;
};
LastOpenCommand m_bootCommand;
unsigned retro_api_version()
{
return RETRO_API_VERSION;
}
void SetupVideoHandler()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
auto gsHandler = m_virtualMachine->GetGSHandler();
if(!gsHandler)
{
m_virtualMachine->CreateGSHandler(CGSH_OpenGL_Libretro::GetFactoryFunction());
}
else
{
auto retro_gs = static_cast<CGSH_OpenGL_Libretro*>(gsHandler);
retro_gs->Reset();
}
}
static void retro_context_destroy()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
}
static void retro_context_reset()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(m_virtualMachine)
{
SetupVideoHandler();
}
}
void SetupSoundHandler()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(m_virtualMachine)
{
m_virtualMachine->CreateSoundHandler(&CSH_LibreAudio::HandlerFactory);
}
}
void SetupInputHandler()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(!m_virtualMachine->GetPadHandler())
{
static struct retro_input_descriptor descDS2[] =
{
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_X, "Left Stick X"},
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_LEFT, RETRO_DEVICE_ID_ANALOG_Y, "Left Stick Y"},
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_X, "Right Stick X"},
{0, RETRO_DEVICE_ANALOG, RETRO_DEVICE_INDEX_ANALOG_RIGHT, RETRO_DEVICE_ID_ANALOG_Y, "Right Stick Y"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Square"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Tringle"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Circle"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "Cross"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L1"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L2, "L2"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3, "L3"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R1"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R2, "R2"},
{0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3, "R3"},
{0},
};
g_environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, descDS2);
static const struct retro_controller_description controllers[] = {
{"PS2 DualShock2", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0)},
};
static const struct retro_controller_info ports[] = {
{controllers, 1},
{NULL, 0},
};
g_environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
for(unsigned int i = 0; i < PS2::CControllerInfo::MAX_BUTTONS; i++)
{
auto ds2_button = static_cast<PS2::CControllerInfo::BUTTON>(i);
auto retro_button = descDS2[i].id;
g_ds2_to_retro_btn_map[ds2_button] = retro_button;
}
m_virtualMachine->CreatePadHandler(CPH_Libretro_Input::GetFactoryFunction());
}
}
void retro_get_system_info(struct retro_system_info* info)
{
*info = {};
info->library_name = "Play!";
info->library_version = PLAY_VERSION;
info->need_fullpath = true;
info->valid_extensions = "elf|iso|cso|isz|bin";
}
void retro_get_system_av_info(struct retro_system_av_info* info)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
*info = {};
info->timing.fps = 60.0;
info->timing.sample_rate = 44100;
info->geometry.base_width = 640;
info->geometry.base_height = 448;
info->geometry.max_width = 640 * 8;
info->geometry.max_height = 448 * 8;
info->geometry.aspect_ratio = 4.0 / 3.0;
}
void retro_set_video_refresh(retro_video_refresh_t cb)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
g_video_cb = cb;
}
void retro_set_environment(retro_environment_t cb)
{
g_environ_cb = cb;
}
void retro_set_input_poll(retro_input_poll_t cb)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
g_input_poll_cb = cb;
}
void retro_set_input_state(retro_input_state_t cb)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
g_input_state_cb = cb;
}
void retro_set_controller_port_device(unsigned port, unsigned device)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
}
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
g_set_audio_sample_batch_cb = cb;
}
void retro_set_audio_sample(retro_audio_sample_t)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
}
unsigned retro_get_region(void)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
return RETRO_REGION_NTSC;
}
size_t retro_serialize_size(void)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
return 40 * 1024 * 1024;
}
bool retro_serialize(void* data, size_t size)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
try
{
Framework::CMemStream stateStream;
Framework::CZipArchiveWriter archive;
m_virtualMachine->m_ee->SaveState(archive);
m_virtualMachine->m_iop->SaveState(archive);
m_virtualMachine->m_ee->m_gs->SaveState(archive);
archive.Write(stateStream);
stateStream.Seek(0, Framework::STREAM_SEEK_DIRECTION::STREAM_SEEK_SET);
stateStream.Read(data, size);
}
catch(...)
{
return false;
}
return true;
}
bool retro_unserialize(const void* data, size_t size)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
try
{
Framework::CPtrStream stateStream(data, size);
Framework::CZipArchiveReader archive(stateStream);
try
{
m_virtualMachine->m_ee->LoadState(archive);
m_virtualMachine->m_iop->LoadState(archive);
m_virtualMachine->m_ee->m_gs->LoadState(archive);
}
catch(...)
{
//Any error that occurs in the previous block is critical
throw;
}
}
catch(...)
{
return false;
}
m_virtualMachine->OnMachineStateChange();
return true;
}
void* retro_get_memory_data(unsigned id)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(id == RETRO_MEMORY_SYSTEM_RAM)
{
return m_virtualMachine->m_ee->m_ram;
}
return NULL;
}
size_t retro_get_memory_size(unsigned id)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(id == RETRO_MEMORY_SYSTEM_RAM)
{
return PS2::EE_RAM_SIZE;
}
return 0;
}
void retro_cheat_reset(void)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
}
void retro_cheat_set(unsigned index, bool enabled, const char* code)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
(void)index;
(void)enabled;
(void)code;
}
void updateVars()
{
for(int i = 0; i < m_vars.size() - 1; ++i)
{
auto item = m_vars[i];
if(!item.key)
continue;
struct retro_variable var = {nullptr};
var.key = item.key;
if(g_environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value)
{
bool videoUpdate = false;
switch(i)
{
case 0:
{
std::string val = var.value;
auto res_factor = std::atoi(val.substr(0, -1).c_str());
if(res_factor != g_res_factor)
{
g_res_factor = res_factor;
CAppConfig::GetInstance().SetPreferenceInteger(PREF_CGSH_OPENGL_RESOLUTION_FACTOR, res_factor);
videoUpdate = true;
}
}
break;
case 1:
{
CGSHandler::PRESENTATION_MODE presentation_mode = CGSHandler::PRESENTATION_MODE::PRESENTATION_MODE_FIT;
std::string val(var.value);
if(val == "Fill Screen")
presentation_mode = CGSHandler::PRESENTATION_MODE::PRESENTATION_MODE_FILL;
else if(val == "Original Size")
presentation_mode = CGSHandler::PRESENTATION_MODE::PRESENTATION_MODE_ORIGINAL;
if(presentation_mode != g_presentation_mode)
{
g_presentation_mode = presentation_mode;
CAppConfig::GetInstance().SetPreferenceInteger(PREF_CGSHANDLER_PRESENTATION_MODE, presentation_mode);
videoUpdate = true;
}
}
break;
case 2:
{
bool forceBilinearTextures = (std::string(var.value) == "true");
if(forceBilinearTextures != g_forceBilinearTextures)
{
g_forceBilinearTextures = forceBilinearTextures;
CAppConfig::GetInstance().SetPreferenceBoolean(PREF_CGSH_OPENGL_FORCEBILINEARTEXTURES, forceBilinearTextures);
videoUpdate = true;
}
}
break;
}
if(videoUpdate)
{
if(m_virtualMachine)
if(m_virtualMachine->GetGSHandler())
static_cast<CGSH_OpenGL_Libretro*>(m_virtualMachine->GetGSHandler())->UpdatePresentation();
}
}
}
}
void checkVarsUpdates()
{
static bool updates = true;
if(!updates)
g_environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updates);
if(updates)
{
updateVars();
}
updates = false;
}
void retro_run()
{
// CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
checkVarsUpdates();
if(!first_run)
{
if(m_virtualMachine)
{
// m_virtualMachine->Pause();
m_virtualMachine->Reset();
if(m_bootCommand.type == BootType::CD)
{
m_virtualMachine->m_ee->m_os->BootFromCDROM();
}
else
{
m_virtualMachine->m_ee->m_os->BootFromFile(m_bootCommand.path);
}
m_virtualMachine->Resume();
first_run = true;
CLog::GetInstance().Print(LOG_NAME, "%s\n", "Start Game");
}
}
if(m_virtualMachine)
{
auto pad = m_virtualMachine->GetPadHandler();
if(pad)
static_cast<CPH_Libretro_Input*>(pad)->UpdateInputState();
if(m_virtualMachine->GetSoundHandler())
static_cast<CSH_LibreAudio*>(m_virtualMachine->GetSoundHandler())->ProcessBuffer();
if(m_virtualMachine->GetGSHandler())
m_virtualMachine->GetGSHandler()->ProcessSingleFrame();
}
}
void retro_reset(void)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(m_virtualMachine)
{
if(!m_virtualMachine->GetGSHandler())
SetupVideoHandler();
// m_virtualMachine->Pause();
m_virtualMachine->Reset();
m_virtualMachine->m_ee->m_os->BootFromCDROM();
m_virtualMachine->Resume();
CLog::GetInstance().Print(LOG_NAME, "%s\n", "Reset Game");
}
first_run = false;
}
bool IsBootableExecutablePath(const fs::path& filePath)
{
auto extension = filePath.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return (extension == ".elf");
}
bool IsBootableDiscImagePath(const fs::path& filePath)
{
auto extension = filePath.extension().string();
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
return (extension == ".iso") ||
(extension == ".isz") ||
(extension == ".cso") ||
(extension == ".bin");
}
bool retro_load_game(const retro_game_info* info)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
fs::path filePath = info->path;
if(IsBootableExecutablePath(filePath))
{
m_bootCommand = LastOpenCommand(BootType::ELF, filePath);
}
else if(IsBootableDiscImagePath(filePath))
{
m_bootCommand = LastOpenCommand(BootType::CD, filePath);
CAppConfig::GetInstance().SetPreferencePath(PREF_PS2_CDROM0_PATH, filePath);
CAppConfig::GetInstance().Save();
}
first_run = false;
auto rgb = RETRO_PIXEL_FORMAT_XRGB8888;
g_environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &rgb);
#ifdef GLES_COMPATIBILITY
g_hw_render.context_type = RETRO_HW_CONTEXT_OPENGLES3;
#else
g_hw_render.context_type = RETRO_HW_CONTEXT_OPENGL_CORE;
#endif
g_hw_render.version_major = 3;
g_hw_render.version_minor = 2;
g_hw_render.context_reset = retro_context_reset;
g_hw_render.context_destroy = retro_context_destroy;
g_hw_render.cache_context = false;
g_hw_render.bottom_left_origin = true;
g_hw_render.depth = true;
g_environ_cb(RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT, nullptr);
g_environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER, &g_hw_render);
g_environ_cb(RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT, nullptr);
g_environ_cb(RETRO_ENVIRONMENT_SET_VARIABLES, (void*)m_vars.data());
return true;
}
void retro_unload_game(void)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
}
bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info)
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
return false;
}
void retro_init()
{
#ifdef __ANDROID__
Framework::PathUtils::SetFilesDirPath(getenv("EXTERNAL_STORAGE"));
#endif
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(g_environ_cb(RETRO_ENVIRONMENT_GET_INPUT_BITMASKS, NULL))
libretro_supports_bitmasks = true;
CAppConfig::GetInstance().RegisterPreferenceInteger(PREF_AUDIO_SPUBLOCKCOUNT, 22);
m_virtualMachine = new CPS2VM();
m_virtualMachine->Initialize();
SetupInputHandler();
SetupSoundHandler();
first_run = false;
}
void retro_deinit()
{
CLog::GetInstance().Print(LOG_NAME, "%s\n", __FUNCTION__);
if(m_virtualMachine)
{
// Note: since we're forced GS into running on this thread
// we need to clear its queue, to prevent it freezing the reset of the system during delete
auto gsHandler = m_virtualMachine->GetGSHandler();
if(gsHandler)
static_cast<CGSH_OpenGL_Libretro*>(gsHandler)->Release();
m_virtualMachine->Pause();
m_virtualMachine->DestroyPadHandler();
m_virtualMachine->DestroyGSHandler();
m_virtualMachine->DestroySoundHandler();
m_virtualMachine->Destroy();
delete m_virtualMachine;
m_virtualMachine = nullptr;
}
libretro_supports_bitmasks = false;
}

View File

@ -30,6 +30,8 @@ build_script:
artifacts:
- path: $(REPO_COMMIT_SHORT)\*.exe
name: Binaries
- path: $(REPO_COMMIT_SHORT)\play_libretro.dll
name: Libretro_Core
deploy:
- provider: S3
access_key_id: AKIAJGVKEDYESR2BIP7Q

View File

@ -5,7 +5,8 @@ mkdir build
cd build
if "%BUILD_PLAY%" == "ON" (
cmake .. -G"%BUILD_TYPE%" -T v141_xp -DUSE_QT=on -DCMAKE_PREFIX_PATH="C:\Qt\5.12\%QT_FLAVOR%"
set BUILD_DIR=%cd%
cmake .. -G"%BUILD_TYPE%" -T v141_xp -DUSE_QT=on -DBUILD_LIBRETRO_CORE=yes -DCMAKE_PREFIX_PATH="C:\Qt\5.12\%QT_FLAVOR%"
if !errorlevel! neq 0 exit /b !errorlevel!
cmake --build . --config %CONFIG_TYPE%
@ -21,6 +22,7 @@ if "%BUILD_PLAY%" == "ON" (
mkdir %REPO_COMMIT_SHORT%
move installer_win32\*.exe %REPO_COMMIT_SHORT%
move build\Source\ui_libretro\Release\play_libretro.dll %REPO_COMMIT_SHORT%
)
if "%BUILD_PSFPLAYER%" == "ON" (

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
if [ -z "$ANDROID_NDK" ]
then
echo "Please set ANDROID_NDK and run again"
exit -1
fi
STRIP="${ANDROID_NDK}/toolchains/llvm/prebuilt/*/bin/llvm-strip"
ABI_LIST="arm64-v8a armeabi-v7a x86 x86_64"
for ABI in $ABI_LIST
do
mkdir "build_$ABI"
pushd "build_$ABI"
cmake ../.. -DBUILD_LIBRETRO_CORE=yes -DBUILD_PLAY=off \
-GNinja \
-DANDROID_ABI="${ABI}" \
-DANDROID_NDK=${ANDROID_NDK} \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE=${ANDROID_TOOLCHAIN_FILE} \
-DANDROID_NATIVE_API_LEVEL=19 \
-DANDROID_STL=c++_static \
-DANDROID_TOOLCHAIN=clang \
-DCMAKE_MAKE_PROGRAM=${NINJA_EXE}
cmake --build . --target play_libretro
${STRIP} -strip-all -o ../play_libretro_${ABI}_android.so Source/ui_libretro/play_libretro_android.so
popd
done