mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Merge branch 'master' into master
This commit is contained in:
commit
401377818c
249 changed files with 11459 additions and 2236 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -47,3 +47,6 @@
|
|||
[submodule "ext/rcheevos"]
|
||||
path = ext/rcheevos
|
||||
url = https://github.com/RetroAchievements/rcheevos.git
|
||||
[submodule "ext/naett"]
|
||||
path = ext/naett
|
||||
url = https://github.com/erkkah/naett.git
|
||||
|
|
|
@ -182,14 +182,13 @@ if(UNIX AND NOT (APPLE OR ANDROID) AND VULKAN)
|
|||
endif()
|
||||
|
||||
# add_definitions(-DVK_USE_PLATFORM_XCB_KHR)
|
||||
if(USE_WAYLAND_WSI)
|
||||
find_package(Wayland)
|
||||
if(NOT WAYLAND_FOUND)
|
||||
message(STATUS "Could not find Wayland libraries, disabling Wayland WSI support for Vulkan.")
|
||||
else()
|
||||
include_directories(${WAYLAND_INCLUDE_DIR})
|
||||
add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR)
|
||||
endif()
|
||||
find_package(Wayland)
|
||||
if(NOT WAYLAND_FOUND)
|
||||
message(STATUS "Could not find Wayland libraries, disabling Wayland WSI support for Vulkan.")
|
||||
else()
|
||||
include_directories(${WAYLAND_INCLUDE_DIR})
|
||||
add_definitions(-DVK_USE_PLATFORM_WAYLAND_KHR)
|
||||
add_definitions(-DUSE_WAYLAND_WSI=ON)
|
||||
endif()
|
||||
|
||||
if(USE_VULKAN_DISPLAY_KHR)
|
||||
|
@ -219,6 +218,16 @@ else()
|
|||
set(CoreLinkType STATIC)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
set(HTTPS_NOT_AVAILABLE ON)
|
||||
endif()
|
||||
|
||||
# Made this flag negative because it's hopefully quite temporary and didn't
|
||||
# want to have to update all build systems.
|
||||
if(HTTPS_NOT_AVAILABLE)
|
||||
add_definitions(-DHTTPS_NOT_AVAILABLE)
|
||||
endif()
|
||||
|
||||
# Work around for some misfeature of the current glslang build system
|
||||
include_directories(ext/glslang)
|
||||
|
||||
|
@ -248,11 +257,14 @@ endif()
|
|||
|
||||
if(NOT LIBRETRO AND NOT IOS AND NOT MACOSX)
|
||||
find_package(SDL2)
|
||||
find_package(SDL2_TTF)
|
||||
find_package(Fontconfig)
|
||||
endif()
|
||||
|
||||
if(MACOSX AND NOT IOS)
|
||||
if(USE_SYSTEM_LIBSDL2)
|
||||
find_package(SDL2)
|
||||
find_package(SDL2_TTF)
|
||||
else()
|
||||
find_library(SDL2Fwk SDL2 REQUIRED PATHS SDL/macOS)
|
||||
message(STATUS "found SDL2Fwk=${SDL2Fwk}")
|
||||
|
@ -406,7 +418,7 @@ if(NOT MSVC)
|
|||
add_definitions(-Wno-psabi)
|
||||
endif()
|
||||
add_definitions(-D_XOPEN_SOURCE=700)
|
||||
add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1 -D_BSD_SOURCE)
|
||||
add_definitions(-D_XOPEN_SOURCE_EXTENDED -D__BSD_VISIBLE=1 -D_BSD_SOURCE -D_DEFAULT_SOURCE)
|
||||
add_definitions(-D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64)
|
||||
elseif(ANDROID)
|
||||
add_definitions(-fsigned-char)
|
||||
|
@ -710,6 +722,10 @@ add_library(Common STATIC
|
|||
Common/Net/HTTPClient.h
|
||||
Common/Net/HTTPHeaders.cpp
|
||||
Common/Net/HTTPHeaders.h
|
||||
Common/Net/HTTPNaettRequest.cpp
|
||||
Common/Net/HTTPNaettRequest.h
|
||||
Common/Net/HTTPRequest.cpp
|
||||
Common/Net/HTTPRequest.h
|
||||
Common/Net/HTTPServer.cpp
|
||||
Common/Net/HTTPServer.h
|
||||
Common/Net/NetBuffer.cpp
|
||||
|
@ -1303,6 +1319,15 @@ else()
|
|||
SDL/SDLVulkanGraphicsContext.cpp
|
||||
)
|
||||
endif()
|
||||
if(SDL2_TTF_FOUND)
|
||||
add_definitions(-DUSE_SDL2_TTF)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} SDL2_ttf::SDL2_ttf)
|
||||
|
||||
if(FONTCONFIG_FOUND)
|
||||
add_definitions(-DUSE_SDL2_TTF_FONTCONFIG)
|
||||
set(nativeExtraLibs ${nativeExtraLibs} Fontconfig::Fontconfig)
|
||||
endif()
|
||||
endif()
|
||||
if(APPLE)
|
||||
set(nativeExtra ${nativeExtra}
|
||||
SDL/SDLMain.h
|
||||
|
@ -1444,6 +1469,8 @@ add_library(native STATIC
|
|||
${nativeExtra}
|
||||
Common/Render/Text/draw_text_qt.cpp
|
||||
Common/Render/Text/draw_text_qt.h
|
||||
Common/Render/Text/draw_text_sdl.cpp
|
||||
Common/Render/Text/draw_text_sdl.h
|
||||
ext/jpge/jpgd.cpp
|
||||
ext/jpge/jpgd.h
|
||||
ext/jpge/jpge.cpp
|
||||
|
@ -1512,6 +1539,8 @@ set(CoreExtra)
|
|||
set(CoreExtraLibs)
|
||||
|
||||
set(CoreExtra ${CoreExtra}
|
||||
Core/MIPS/IR/IRAnalysis.cpp
|
||||
Core/MIPS/IR/IRAnalysis.h
|
||||
Core/MIPS/IR/IRCompALU.cpp
|
||||
Core/MIPS/IR/IRCompBranch.cpp
|
||||
Core/MIPS/IR/IRCompFPU.cpp
|
||||
|
@ -1595,6 +1624,19 @@ list(APPEND CoreExtra
|
|||
)
|
||||
|
||||
list(APPEND CoreExtra
|
||||
Core/MIPS/RiscV/RiscVAsm.cpp
|
||||
Core/MIPS/RiscV/RiscVCompALU.cpp
|
||||
Core/MIPS/RiscV/RiscVCompBranch.cpp
|
||||
Core/MIPS/RiscV/RiscVCompFPU.cpp
|
||||
Core/MIPS/RiscV/RiscVCompLoadStore.cpp
|
||||
Core/MIPS/RiscV/RiscVCompSystem.cpp
|
||||
Core/MIPS/RiscV/RiscVCompVec.cpp
|
||||
Core/MIPS/RiscV/RiscVJit.cpp
|
||||
Core/MIPS/RiscV/RiscVJit.h
|
||||
Core/MIPS/RiscV/RiscVRegCache.cpp
|
||||
Core/MIPS/RiscV/RiscVRegCache.h
|
||||
Core/MIPS/RiscV/RiscVRegCacheFPU.cpp
|
||||
Core/MIPS/RiscV/RiscVRegCacheFPU.h
|
||||
GPU/Common/VertexDecoderRiscV.cpp
|
||||
)
|
||||
|
||||
|
@ -2223,6 +2265,10 @@ endif()
|
|||
target_link_libraries(${CoreLibName} Common native kirk cityhash sfmt19937 xbrz xxhash rcheevos ${GlslangLibs}
|
||||
${CoreExtraLibs} ${OPENGL_LIBRARIES} ${X11_LIBRARIES} ${CMAKE_DL_LIBS})
|
||||
|
||||
if(NOT HTTPS_NOT_AVAILABLE)
|
||||
target_link_libraries(${CoreLibName} naett)
|
||||
endif()
|
||||
|
||||
target_compile_features(${CoreLibName} PUBLIC cxx_std_17)
|
||||
|
||||
if(FFmpeg_FOUND)
|
||||
|
@ -2373,6 +2419,8 @@ set(WindowsFiles
|
|||
Windows/Debugger/Debugger_VFPUDlg.h
|
||||
Windows/Debugger/WatchItemWindow.cpp
|
||||
Windows/Debugger/WatchItemWindow.h
|
||||
Windows/Debugger/EditSymbolsWindow.cpp
|
||||
Windows/Debugger/EditSymbolsWindow.h
|
||||
Windows/GEDebugger/CtrlDisplayListView.cpp
|
||||
Windows/GEDebugger/SimpleGLWindow.cpp
|
||||
Windows/GEDebugger/TabState.cpp
|
||||
|
|
|
@ -109,7 +109,6 @@ struct CPUInfo {
|
|||
bool RiscV_D;
|
||||
bool RiscV_C;
|
||||
bool RiscV_V;
|
||||
bool RiscV_B;
|
||||
bool RiscV_Zicsr;
|
||||
bool RiscV_Zba;
|
||||
bool RiscV_Zbb;
|
||||
|
|
|
@ -399,6 +399,7 @@
|
|||
<ClInclude Include="..\ext\libpng17\pnglibconf.h" />
|
||||
<ClInclude Include="..\ext\libpng17\pngpriv.h" />
|
||||
<ClInclude Include="..\ext\libpng17\pngstruct.h" />
|
||||
<ClInclude Include="..\ext\naett\naett.h" />
|
||||
<ClInclude Include="..\ext\vma\vk_mem_alloc.h" />
|
||||
<ClInclude Include="ABI.h" />
|
||||
<ClInclude Include="Arm64Emitter.h" />
|
||||
|
@ -490,10 +491,12 @@
|
|||
<ClInclude Include="Math\lin\vec3.h" />
|
||||
<ClInclude Include="Math\math_util.h" />
|
||||
<ClInclude Include="Math\Statistics.h" />
|
||||
<ClInclude Include="Net\HTTPNaettRequest.h" />
|
||||
<ClInclude Include="Net\NetBuffer.h" />
|
||||
<ClInclude Include="Net\HTTPClient.h" />
|
||||
<ClInclude Include="Net\HTTPHeaders.h" />
|
||||
<ClInclude Include="Net\HTTPServer.h" />
|
||||
<ClInclude Include="Net\HTTPRequest.h" />
|
||||
<ClInclude Include="Net\Resolve.h" />
|
||||
<ClInclude Include="Net\Sinks.h" />
|
||||
<ClInclude Include="Net\URL.h" />
|
||||
|
@ -847,6 +850,7 @@
|
|||
<ForcedIncludeFiles Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
</ForcedIncludeFiles>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ext\naett\naett.c" />
|
||||
<ClCompile Include="..\ext\vma\vk_mem_alloc.cpp" />
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
<ClCompile Include="Arm64Emitter.cpp" />
|
||||
|
@ -934,10 +938,12 @@
|
|||
<ClCompile Include="Math\lin\vec3.cpp" />
|
||||
<ClCompile Include="Math\math_util.cpp" />
|
||||
<ClCompile Include="Math\Statistics.cpp" />
|
||||
<ClCompile Include="Net\HTTPNaettRequest.cpp" />
|
||||
<ClCompile Include="Net\NetBuffer.cpp" />
|
||||
<ClCompile Include="Net\HTTPClient.cpp" />
|
||||
<ClCompile Include="Net\HTTPHeaders.cpp" />
|
||||
<ClCompile Include="Net\HTTPServer.cpp" />
|
||||
<ClCompile Include="Net\HTTPRequest.cpp" />
|
||||
<ClCompile Include="Net\Resolve.cpp" />
|
||||
<ClCompile Include="Net\Sinks.cpp" />
|
||||
<ClCompile Include="Net\URL.cpp" />
|
||||
|
|
|
@ -512,6 +512,15 @@
|
|||
<ClInclude Include="System\OSD.h">
|
||||
<Filter>System</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Net\HTTPRequest.h">
|
||||
<Filter>Net</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ext\naett\naett.h">
|
||||
<Filter>ext\naett</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Net\HTTPNaettRequest.h">
|
||||
<Filter>Net</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
@ -959,6 +968,15 @@
|
|||
<ClCompile Include="System\OSD.cpp">
|
||||
<Filter>System</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Net\HTTPRequest.cpp">
|
||||
<Filter>Net</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ext\naett\naett.c">
|
||||
<Filter>ext\naett</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Net\HTTPNaettRequest.cpp">
|
||||
<Filter>Net</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Crypto">
|
||||
|
@ -1069,6 +1087,9 @@
|
|||
<Filter Include="ext\basis_universal">
|
||||
<UniqueIdentifier>{d6d5f6e0-1c72-496b-af11-6d52d5123033}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ext\naett">
|
||||
<UniqueIdentifier>{34f45db9-5c08-49cb-b349-b9e760ce3213}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="..\ext\libpng17\CMakeLists.txt">
|
||||
|
|
|
@ -88,7 +88,7 @@ public:
|
|||
T &insert(T *iter) {
|
||||
int pos = iter - data_;
|
||||
ExtendByOne();
|
||||
if (pos + 1 < size_) {
|
||||
if (pos + 1 < (int)size_) {
|
||||
memmove(data_ + pos + 1, data_ + pos, (size_ - pos) * sizeof(T));
|
||||
}
|
||||
return data_[pos];
|
||||
|
|
|
@ -41,6 +41,7 @@ static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
|
|||
"Upgrade",
|
||||
"VR",
|
||||
"Achievements",
|
||||
"PSPSettings",
|
||||
};
|
||||
|
||||
I18NRepo g_i18nrepo;
|
||||
|
|
|
@ -57,6 +57,7 @@ enum class I18NCat : uint8_t {
|
|||
UPGRADE,
|
||||
VR,
|
||||
ACHIEVEMENTS,
|
||||
PSPSETTINGS,
|
||||
CATEGORY_COUNT,
|
||||
NONE = CATEGORY_COUNT,
|
||||
};
|
||||
|
|
|
@ -38,7 +38,7 @@ bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *can
|
|||
http::RequestParams req(baseURL.Resource(), "text/plain, text/html; q=0.9, */*; q=0.8");
|
||||
if (http.Resolve(baseURL.Host().c_str(), baseURL.Port())) {
|
||||
if (http.Connect(2, 20.0, cancel)) {
|
||||
http::RequestProgress progress(cancel);
|
||||
net::RequestProgress progress(cancel);
|
||||
code = http.GET(req, &result, responseHeaders, &progress);
|
||||
http.Disconnect();
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ static HMODULE g_D3DCompileModule;
|
|||
|
||||
LPCREATEDXGIFACTORY ptr_CreateDXGIFactory;
|
||||
LPD3D11CREATEDEVICE ptr_D3D11CreateDevice;
|
||||
LPD3D11CREATEDEVICEANDSWAPCHAIN ptr_D3D11CreateDeviceAndSwapChain;
|
||||
pD3DCompile ptr_D3DCompile;
|
||||
|
||||
LoadD3D11Error LoadD3D11() {
|
||||
|
@ -22,7 +21,6 @@ LoadD3D11Error LoadD3D11() {
|
|||
g_D3D11Module = LoadLibrary(L"d3d11.dll");
|
||||
if (g_D3D11Module) {
|
||||
ptr_D3D11CreateDevice = (LPD3D11CREATEDEVICE)GetProcAddress(g_D3D11Module, "D3D11CreateDevice");
|
||||
ptr_D3D11CreateDeviceAndSwapChain = (LPD3D11CREATEDEVICEANDSWAPCHAIN)GetProcAddress(g_D3D11Module, "D3D11CreateDeviceAndSwapChain");
|
||||
} else {
|
||||
return LoadD3D11Error::FAIL_NO_D3D11;
|
||||
}
|
||||
|
|
|
@ -14,12 +14,10 @@
|
|||
#endif
|
||||
|
||||
typedef HRESULT (WINAPI *LPCREATEDXGIFACTORY)(REFIID, void **);
|
||||
typedef HRESULT (WINAPI *LPD3D11CREATEDEVICEANDSWAPCHAIN)(__in_opt IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, __in_ecount_opt(FeatureLevels) CONST D3D_FEATURE_LEVEL *pFeatureLevels, UINT FeatureLevels, UINT SDKVersion, __in_opt CONST DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, __out_opt IDXGISwapChain **ppSwapChain, __out_opt ID3D11Device **ppDevice, __out_opt D3D_FEATURE_LEVEL *pFeatureLevel, __out_opt ID3D11DeviceContext **ppImmediateContext);
|
||||
typedef HRESULT (WINAPI *LPD3D11CREATEDEVICE)(IDXGIAdapter *, D3D_DRIVER_TYPE, HMODULE, UINT32, D3D_FEATURE_LEVEL *, UINT, UINT32, ID3D11Device **, D3D_FEATURE_LEVEL *, ID3D11DeviceContext **);
|
||||
|
||||
extern LPCREATEDXGIFACTORY ptr_CreateDXGIFactory;
|
||||
extern LPD3D11CREATEDEVICE ptr_D3D11CreateDevice;
|
||||
extern LPD3D11CREATEDEVICEANDSWAPCHAIN ptr_D3D11CreateDeviceAndSwapChain;
|
||||
extern pD3DCompile ptr_D3DCompile;
|
||||
|
||||
enum class LoadD3D11Error {
|
||||
|
|
|
@ -842,7 +842,7 @@ bool D3D11Texture::FillLevel(ID3D11DeviceContext *context, int level, int w, int
|
|||
for (int y = 0; y < h; ++y) {
|
||||
void *dest = (uint8_t *)mapped.pData + mapped.DepthPitch * s + mapped.RowPitch * y;
|
||||
uint32_t byteStride = w * (uint32_t)DataFormatSizeInBytes(format_);
|
||||
const void *src = data[level] + byteStride * (y + h * d);
|
||||
const void *src = data[level] + byteStride * (y + h * s);
|
||||
memcpy(dest, src, byteStride);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
|
||||
#if 0 // def _DEBUG
|
||||
#define VLOG(...) NOTICE_LOG(G3D, __VA_ARGS__)
|
||||
#else
|
||||
#define VLOG(...)
|
||||
#endif
|
||||
|
||||
void CachedReadback::Destroy(VulkanContext *vulkan) {
|
||||
if (buffer) {
|
||||
vulkan->Delete().QueueDeleteBufferAllocation(buffer, allocation);
|
||||
|
@ -196,12 +202,16 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame
|
|||
|
||||
VkResult res;
|
||||
if (fenceToTrigger == fence) {
|
||||
VLOG("Doing queue submit, fencing frame %d", this->index);
|
||||
// The fence is waited on by the main thread, they are not allowed to access it simultaneously.
|
||||
res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger);
|
||||
std::lock_guard<std::mutex> lock(fenceMutex);
|
||||
readyForFence = true;
|
||||
fenceCondVar.notify_one();
|
||||
if (sharedData.useMultiThreading) {
|
||||
std::lock_guard<std::mutex> lock(fenceMutex);
|
||||
readyForFence = true;
|
||||
fenceCondVar.notify_one();
|
||||
}
|
||||
} else {
|
||||
VLOG("Doing queue submit, fencing something (%p)", fenceToTrigger);
|
||||
res = vkQueueSubmit(vulkan->GetGraphicsQueue(), 1, &submit_info, fenceToTrigger);
|
||||
}
|
||||
|
||||
|
@ -219,7 +229,7 @@ void FrameData::SubmitPending(VulkanContext *vulkan, FrameSubmitType type, Frame
|
|||
}
|
||||
}
|
||||
|
||||
void FrameDataShared::Init(VulkanContext *vulkan) {
|
||||
void FrameDataShared::Init(VulkanContext *vulkan, bool useMultiThreading) {
|
||||
VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
|
||||
semaphoreCreateInfo.flags = 0;
|
||||
VkResult res = vkCreateSemaphore(vulkan->GetDevice(), &semaphoreCreateInfo, nullptr, &acquireSemaphore);
|
||||
|
@ -230,6 +240,8 @@ void FrameDataShared::Init(VulkanContext *vulkan) {
|
|||
// This fence is used for synchronizing readbacks. Does not need preinitialization.
|
||||
readbackFence = vulkan->CreateFence(false);
|
||||
vulkan->SetDebugName(readbackFence, VK_OBJECT_TYPE_FENCE, "readbackFence");
|
||||
|
||||
this->useMultiThreading = useMultiThreading;
|
||||
}
|
||||
|
||||
void FrameDataShared::Destroy(VulkanContext *vulkan) {
|
||||
|
|
|
@ -53,8 +53,9 @@ struct FrameDataShared {
|
|||
|
||||
// For synchronous readbacks.
|
||||
VkFence readbackFence = VK_NULL_HANDLE;
|
||||
bool useMultiThreading;
|
||||
|
||||
void Init(VulkanContext *vulkan);
|
||||
void Init(VulkanContext *vulkan, bool useMultiThreading);
|
||||
void Destroy(VulkanContext *vulkan);
|
||||
};
|
||||
|
||||
|
|
|
@ -270,6 +270,12 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
bool multiview = RenderPassTypeHasMultiView(rpType);
|
||||
bool multisample = RenderPassTypeHasMultisample(rpType);
|
||||
|
||||
_dbg_assert_(!(isBackbuffer && multisample));
|
||||
|
||||
if (isBackbuffer) {
|
||||
_dbg_assert_(key.depthLoadAction == VKRRenderPassLoadAction::CLEAR);
|
||||
}
|
||||
|
||||
if (multiview) {
|
||||
// TODO: Assert that the device has multiview support enabled.
|
||||
}
|
||||
|
@ -296,7 +302,7 @@ VkRenderPass CreateRenderPass(VulkanContext *vulkan, const RPKey &key, RenderPas
|
|||
attachments[attachmentCount].storeOp = ConvertStoreAction(key.depthStoreAction);
|
||||
attachments[attachmentCount].stencilLoadOp = multisample ? VK_ATTACHMENT_LOAD_OP_DONT_CARE : ConvertLoadAction(key.stencilLoadAction);
|
||||
attachments[attachmentCount].stencilStoreOp = ConvertStoreAction(key.stencilStoreAction);
|
||||
attachments[attachmentCount].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
attachments[attachmentCount].initialLayout = isBackbuffer ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
attachments[attachmentCount].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
attachmentCount++;
|
||||
}
|
||||
|
|
|
@ -247,15 +247,16 @@ bool VKRComputePipeline::CreateAsync(VulkanContext *vulkan) {
|
|||
return true;
|
||||
}
|
||||
|
||||
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan)
|
||||
VulkanRenderManager::VulkanRenderManager(VulkanContext *vulkan, bool useThread)
|
||||
: vulkan_(vulkan), queueRunner_(vulkan),
|
||||
initTimeMs_("initTimeMs"),
|
||||
totalGPUTimeMs_("totalGPUTimeMs"),
|
||||
renderCPUTimeMs_("renderCPUTimeMs")
|
||||
renderCPUTimeMs_("renderCPUTimeMs"),
|
||||
useRenderThread_(useThread)
|
||||
{
|
||||
inflightFramesAtStart_ = vulkan_->GetInflightFrames();
|
||||
|
||||
frameDataShared_.Init(vulkan);
|
||||
frameDataShared_.Init(vulkan, useThread);
|
||||
|
||||
for (int i = 0; i < inflightFramesAtStart_; i++) {
|
||||
frameData_[i].Init(vulkan, i);
|
||||
|
@ -292,12 +293,14 @@ bool VulkanRenderManager::CreateBackbuffers() {
|
|||
|
||||
outOfDateFrames_ = 0;
|
||||
|
||||
// Start the thread.
|
||||
// Start the thread(s).
|
||||
if (HasBackbuffers()) {
|
||||
run_ = true; // For controlling the compiler thread's exit
|
||||
|
||||
INFO_LOG(G3D, "Starting Vulkan submission thread");
|
||||
thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this);
|
||||
if (useRenderThread_) {
|
||||
INFO_LOG(G3D, "Starting Vulkan submission thread");
|
||||
thread_ = std::thread(&VulkanRenderManager::ThreadFunc, this);
|
||||
}
|
||||
INFO_LOG(G3D, "Starting Vulkan compiler thread");
|
||||
compileThread_ = std::thread(&VulkanRenderManager::CompileThreadFunc, this);
|
||||
}
|
||||
|
@ -306,7 +309,8 @@ bool VulkanRenderManager::CreateBackbuffers() {
|
|||
|
||||
// Called from main thread.
|
||||
void VulkanRenderManager::StopThread() {
|
||||
{
|
||||
if (useRenderThread_) {
|
||||
_dbg_assert_(thread_.joinable());
|
||||
// Tell the render thread to quit when it's done.
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::EXIT);
|
||||
task->frame = vulkan_->GetCurFrame();
|
||||
|
@ -319,7 +323,9 @@ void VulkanRenderManager::StopThread() {
|
|||
run_ = false;
|
||||
|
||||
// Stop the thread.
|
||||
thread_.join();
|
||||
if (useRenderThread_) {
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
for (int i = 0; i < vulkan_->GetInflightFrames(); i++) {
|
||||
auto &frameData = frameData_[i];
|
||||
|
@ -492,6 +498,8 @@ void VulkanRenderManager::DrainCompileQueue() {
|
|||
void VulkanRenderManager::ThreadFunc() {
|
||||
SetCurrentThreadName("RenderMan");
|
||||
while (true) {
|
||||
_dbg_assert_(useRenderThread_);
|
||||
|
||||
// Pop a task of the queue and execute it.
|
||||
VKRRenderThreadTask *task = nullptr;
|
||||
{
|
||||
|
@ -534,7 +542,7 @@ void VulkanRenderManager::BeginFrame(bool enableProfiling, bool enableLogProfile
|
|||
|
||||
// Makes sure the submission from the previous time around has happened. Otherwise
|
||||
// we are not allowed to wait from another thread here..
|
||||
{
|
||||
if (useRenderThread_) {
|
||||
std::unique_lock<std::mutex> lock(frameData.fenceMutex);
|
||||
while (!frameData.readyForFence) {
|
||||
frameData.fenceCondVar.wait(lock);
|
||||
|
@ -1263,11 +1271,16 @@ void VulkanRenderManager::Finish() {
|
|||
VLOG("PUSH: Frame[%d]", curFrame);
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::PRESENT);
|
||||
task->frame = curFrame;
|
||||
{
|
||||
if (useRenderThread_) {
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
pushCondVar_.notify_one();
|
||||
} else {
|
||||
// Just do it!
|
||||
task->steps = std::move(steps_);
|
||||
Run(*task);
|
||||
delete task;
|
||||
}
|
||||
|
||||
steps_.clear();
|
||||
|
@ -1348,7 +1361,7 @@ void VulkanRenderManager::Run(VKRRenderThreadTask &task) {
|
|||
// The submit will trigger the readbackFence, and also do the wait for it.
|
||||
frameData.SubmitPending(vulkan_, FrameSubmitType::Sync, frameDataShared_);
|
||||
|
||||
{
|
||||
if (useRenderThread_) {
|
||||
std::unique_lock<std::mutex> lock(syncMutex_);
|
||||
syncCondVar_.notify_one();
|
||||
}
|
||||
|
@ -1374,24 +1387,34 @@ void VulkanRenderManager::FlushSync() {
|
|||
int curFrame = vulkan_->GetCurFrame();
|
||||
FrameData &frameData = frameData_[curFrame];
|
||||
|
||||
{
|
||||
VLOG("PUSH: Frame[%d]", curFrame);
|
||||
if (useRenderThread_) {
|
||||
{
|
||||
VLOG("PUSH: Frame[%d]", curFrame);
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC);
|
||||
task->frame = curFrame;
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
pushCondVar_.notify_one();
|
||||
steps_.clear();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(syncMutex_);
|
||||
// Wait for the flush to be hit, since we're syncing.
|
||||
while (!frameData.syncDone) {
|
||||
VLOG("PUSH: Waiting for frame[%d].syncDone = 1 (sync)", curFrame);
|
||||
syncCondVar_.wait(lock);
|
||||
}
|
||||
frameData.syncDone = false;
|
||||
}
|
||||
} else {
|
||||
VKRRenderThreadTask *task = new VKRRenderThreadTask(VKRRunType::SYNC);
|
||||
task->frame = curFrame;
|
||||
std::unique_lock<std::mutex> lock(pushMutex_);
|
||||
renderThreadQueue_.push(task);
|
||||
renderThreadQueue_.back()->steps = std::move(steps_);
|
||||
pushCondVar_.notify_one();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(syncMutex_);
|
||||
// Wait for the flush to be hit, since we're syncing.
|
||||
while (!frameData.syncDone) {
|
||||
VLOG("PUSH: Waiting for frame[%d].syncDone = 1 (sync)", curFrame);
|
||||
syncCondVar_.wait(lock);
|
||||
}
|
||||
frameData.syncDone = false;
|
||||
task->steps = std::move(steps_);
|
||||
Run(*task);
|
||||
delete task;
|
||||
steps_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ struct CompileQueueEntry {
|
|||
|
||||
class VulkanRenderManager {
|
||||
public:
|
||||
VulkanRenderManager(VulkanContext *vulkan);
|
||||
VulkanRenderManager(VulkanContext *vulkan, bool useThread);
|
||||
~VulkanRenderManager();
|
||||
|
||||
// Makes sure that the GPU has caught up enough that we can start writing buffers of this frame again.
|
||||
|
@ -489,6 +489,8 @@ private:
|
|||
bool insideFrame_ = false;
|
||||
bool run_ = false;
|
||||
|
||||
bool useRenderThread_ = true;
|
||||
|
||||
// This is the offset within this frame, in case of a mid-frame sync.
|
||||
VKRStep *curRenderStep_ = nullptr;
|
||||
bool curStepHasViewport_ = false;
|
||||
|
|
|
@ -384,7 +384,7 @@ class VKFramebuffer;
|
|||
|
||||
class VKContext : public DrawContext {
|
||||
public:
|
||||
VKContext(VulkanContext *vulkan);
|
||||
VKContext(VulkanContext *vulkan, bool useRenderThread);
|
||||
~VKContext();
|
||||
|
||||
void DebugAnnotate(const char *annotation) override;
|
||||
|
@ -857,8 +857,8 @@ static DataFormat DataFormatFromVulkanDepth(VkFormat fmt) {
|
|||
return DataFormat::UNDEFINED;
|
||||
}
|
||||
|
||||
VKContext::VKContext(VulkanContext *vulkan)
|
||||
: vulkan_(vulkan), renderManager_(vulkan) {
|
||||
VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread)
|
||||
: vulkan_(vulkan), renderManager_(vulkan, useRenderThread) {
|
||||
shaderLanguageDesc_.Init(GLSL_VULKAN);
|
||||
|
||||
VkFormat depthStencilFormat = vulkan->GetDeviceInfo().preferredDepthStencilFormat;
|
||||
|
@ -1582,8 +1582,8 @@ void VKContext::Clear(int clearMask, uint32_t colorval, float depthVal, int sten
|
|||
renderManager_.Clear(colorval, depthVal, stencilVal, mask);
|
||||
}
|
||||
|
||||
DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan) {
|
||||
return new VKContext(vulkan);
|
||||
DrawContext *T3DCreateVulkanContext(VulkanContext *vulkan, bool useRenderThread) {
|
||||
return new VKContext(vulkan, useRenderThread);
|
||||
}
|
||||
|
||||
void AddFeature(std::vector<std::string> &features, const char *name, VkBool32 available, VkBool32 enabled) {
|
||||
|
|
|
@ -31,6 +31,6 @@ DrawContext *T3DCreateDX9Context(IDirect3D9 *d3d, IDirect3D9Ex *d3dEx, int adapt
|
|||
DrawContext *T3DCreateD3D11Context(ID3D11Device *device, ID3D11DeviceContext *context, ID3D11Device1 *device1, ID3D11DeviceContext1 *context1, D3D_FEATURE_LEVEL featureLevel, HWND hWnd, std::vector<std::string> adapterNames);
|
||||
#endif
|
||||
|
||||
DrawContext *T3DCreateVulkanContext(VulkanContext *context);
|
||||
DrawContext *T3DCreateVulkanContext(VulkanContext *context, bool useRenderThread);
|
||||
|
||||
} // namespace Draw
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/System/OSD.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <netinet/in.h>
|
||||
|
@ -217,7 +218,7 @@ bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::
|
|||
return found;
|
||||
}
|
||||
|
||||
void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *progress) {
|
||||
void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength) {
|
||||
int dechunkedBytes = 0;
|
||||
while (true) {
|
||||
std::string line;
|
||||
|
@ -236,14 +237,11 @@ void DeChunk(Buffer *inbuffer, Buffer *outbuffer, int contentLength, float *prog
|
|||
return;
|
||||
}
|
||||
dechunkedBytes += chunkSize;
|
||||
if (progress && contentLength) {
|
||||
*progress = (float)dechunkedBytes / contentLength;
|
||||
}
|
||||
inbuffer->Skip(2);
|
||||
}
|
||||
}
|
||||
|
||||
int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, RequestProgress *progress) {
|
||||
int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, net::RequestProgress *progress) {
|
||||
const char *otherHeaders =
|
||||
"Accept-Encoding: gzip\r\n";
|
||||
int err = SendRequest("GET", req, otherHeaders, progress);
|
||||
|
@ -264,13 +262,13 @@ int Client::GET(const RequestParams &req, Buffer *output, std::vector<std::strin
|
|||
return code;
|
||||
}
|
||||
|
||||
int Client::GET(const RequestParams &req, Buffer *output, RequestProgress *progress) {
|
||||
int Client::GET(const RequestParams &req, Buffer *output, net::RequestProgress *progress) {
|
||||
std::vector<std::string> responseHeaders;
|
||||
int code = GET(req, output, responseHeaders, progress);
|
||||
return code;
|
||||
}
|
||||
|
||||
int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress) {
|
||||
int Client::POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress) {
|
||||
char otherHeaders[2048];
|
||||
if (mime.empty()) {
|
||||
snprintf(otherHeaders, sizeof(otherHeaders), "Content-Length: %lld\r\n", (long long)data.size());
|
||||
|
@ -296,16 +294,16 @@ int Client::POST(const RequestParams &req, const std::string &data, const std::s
|
|||
return code;
|
||||
}
|
||||
|
||||
int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, RequestProgress *progress) {
|
||||
int Client::POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress) {
|
||||
return POST(req, data, "", output, progress);
|
||||
}
|
||||
|
||||
int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, RequestProgress *progress) {
|
||||
int Client::SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress) {
|
||||
return SendRequestWithData(method, req, "", otherHeaders, progress);
|
||||
}
|
||||
|
||||
int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, RequestProgress *progress) {
|
||||
progress->progress = 0.01f;
|
||||
int Client::SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress) {
|
||||
progress->Update(0, 0, false);
|
||||
|
||||
net::Buffer buffer;
|
||||
const char *tpl =
|
||||
|
@ -331,7 +329,7 @@ int Client::SendRequestWithData(const char *method, const RequestParams &req, co
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, RequestProgress *progress) {
|
||||
int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, net::RequestProgress *progress) {
|
||||
// Snarf all the data we can into RAM. A little unsafe but hey.
|
||||
static constexpr float CANCEL_INTERVAL = 0.25f;
|
||||
bool ready = false;
|
||||
|
@ -371,7 +369,7 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &
|
|||
|
||||
while (true) {
|
||||
int sz = readbuf->TakeLineCRLF(&line);
|
||||
if (!sz)
|
||||
if (!sz || sz < 0)
|
||||
break;
|
||||
responseHeaders.push_back(line);
|
||||
}
|
||||
|
@ -384,7 +382,9 @@ int Client::ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &
|
|||
return code;
|
||||
}
|
||||
|
||||
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, RequestProgress *progress) {
|
||||
int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, net::RequestProgress *progress) {
|
||||
_dbg_assert_(progress->cancelled);
|
||||
|
||||
bool gzip = false;
|
||||
bool chunked = false;
|
||||
int contentLength = 0;
|
||||
|
@ -412,30 +412,18 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
|
|||
}
|
||||
|
||||
if (contentLength < 0) {
|
||||
WARN_LOG(IO, "Negative content length %d", contentLength);
|
||||
// Just sanity checking...
|
||||
contentLength = 0;
|
||||
}
|
||||
|
||||
if (!contentLength) {
|
||||
// Content length is unknown.
|
||||
// Set progress to 1% so it looks like something is happening...
|
||||
progress->progress = 0.1f;
|
||||
}
|
||||
|
||||
if (!contentLength) {
|
||||
// No way to know how far along we are. Let's just not update the progress counter.
|
||||
if (!readbuf->ReadAllWithProgress(sock(), contentLength, nullptr, &progress->kBps, progress->cancelled))
|
||||
return -1;
|
||||
} else {
|
||||
// Let's read in chunks, updating progress between each.
|
||||
if (!readbuf->ReadAllWithProgress(sock(), contentLength, &progress->progress, &progress->kBps, progress->cancelled))
|
||||
return -1;
|
||||
}
|
||||
if (!readbuf->ReadAllWithProgress(sock(), contentLength, progress))
|
||||
return -1;
|
||||
|
||||
// output now contains the rest of the reply. Dechunk it.
|
||||
if (!output->IsVoid()) {
|
||||
if (chunked) {
|
||||
DeChunk(readbuf, output, contentLength, &progress->progress);
|
||||
DeChunk(readbuf, output, contentLength);
|
||||
} else {
|
||||
output->Append(*readbuf);
|
||||
}
|
||||
|
@ -447,30 +435,32 @@ int Client::ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::stri
|
|||
bool result = decompress_string(compressed, &decompressed);
|
||||
if (!result) {
|
||||
ERROR_LOG(IO, "Error decompressing using zlib");
|
||||
progress->progress = 0.0f;
|
||||
progress->Update(0, 0, true);
|
||||
return -1;
|
||||
}
|
||||
output->Append(decompressed);
|
||||
}
|
||||
}
|
||||
|
||||
progress->progress = 1.0f;
|
||||
progress->Update(contentLength, contentLength, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Download::Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile)
|
||||
: method_(method), progress_(&cancelled_), url_(url), postData_(postData), postMime_(postMime), outfile_(outfile) {
|
||||
HTTPRequest::HTTPRequest(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, const std::string &name)
|
||||
: Request(method, url, name, &cancelled_, progressBarMode), postData_(postData), postMime_(postMime), outfile_(outfile) {
|
||||
}
|
||||
|
||||
Download::~Download() {
|
||||
HTTPRequest::~HTTPRequest() {
|
||||
g_OSD.RemoveProgressBar(url_, Failed() ? false : true, 0.5f);
|
||||
|
||||
_assert_msg_(joined_, "Download destructed without join");
|
||||
}
|
||||
|
||||
void Download::Start() {
|
||||
thread_ = std::thread(std::bind(&Download::Do, this));
|
||||
void HTTPRequest::Start() {
|
||||
thread_ = std::thread(std::bind(&HTTPRequest::Do, this));
|
||||
}
|
||||
|
||||
void Download::Join() {
|
||||
void HTTPRequest::Join() {
|
||||
if (joined_) {
|
||||
ERROR_LOG(IO, "Already joined thread!");
|
||||
}
|
||||
|
@ -478,13 +468,13 @@ void Download::Join() {
|
|||
joined_ = true;
|
||||
}
|
||||
|
||||
void Download::SetFailed(int code) {
|
||||
void HTTPRequest::SetFailed(int code) {
|
||||
failed_ = true;
|
||||
progress_.progress = 1.0f;
|
||||
progress_.Update(0, 0, true);
|
||||
completed_ = true;
|
||||
}
|
||||
|
||||
int Download::Perform(const std::string &url) {
|
||||
int HTTPRequest::Perform(const std::string &url) {
|
||||
Url fileUrl(url);
|
||||
if (!fileUrl.Valid()) {
|
||||
return -1;
|
||||
|
@ -521,7 +511,7 @@ int Download::Perform(const std::string &url) {
|
|||
}
|
||||
}
|
||||
|
||||
std::string Download::RedirectLocation(const std::string &baseUrl) {
|
||||
std::string HTTPRequest::RedirectLocation(const std::string &baseUrl) {
|
||||
std::string redirectUrl;
|
||||
if (GetHeaderValue(responseHeaders_, "Location", &redirectUrl)) {
|
||||
Url url(baseUrl);
|
||||
|
@ -532,11 +522,10 @@ std::string Download::RedirectLocation(const std::string &baseUrl) {
|
|||
return redirectUrl;
|
||||
}
|
||||
|
||||
void Download::Do() {
|
||||
SetCurrentThreadName("Downloader::Do");
|
||||
void HTTPRequest::Do() {
|
||||
SetCurrentThreadName("HTTPDownload::Do");
|
||||
|
||||
AndroidJNIThreadContext jniContext;
|
||||
|
||||
resultCode_ = 0;
|
||||
|
||||
std::string downloadURL = url_;
|
||||
|
@ -575,97 +564,9 @@ void Download::Do() {
|
|||
resultCode_ = resultCode;
|
||||
}
|
||||
|
||||
progress_.progress = 1.0f;
|
||||
|
||||
// Set this last to ensure no race conditions when checking Done. Users must always check
|
||||
// Done before looking at the result code.
|
||||
completed_ = true;
|
||||
}
|
||||
|
||||
std::shared_ptr<Download> Downloader::StartDownload(const std::string &url, const Path &outfile, const char *acceptMime) {
|
||||
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
if (acceptMime)
|
||||
dl->SetAccept(acceptMime);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
||||
std::shared_ptr<Download> Downloader::StartDownloadWithCallback(
|
||||
const std::string &url,
|
||||
const Path &outfile,
|
||||
std::function<void(Download &)> callback,
|
||||
const char *acceptMime) {
|
||||
std::shared_ptr<Download> dl(new Download(RequestMethod::GET, url, "", "", outfile));
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
if (acceptMime)
|
||||
dl->SetAccept(acceptMime);
|
||||
dl->SetCallback(callback);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
||||
std::shared_ptr<Download> Downloader::AsyncPostWithCallback(
|
||||
const std::string &url,
|
||||
const std::string &postData,
|
||||
const std::string &postMime,
|
||||
std::function<void(Download &)> callback) {
|
||||
std::shared_ptr<Download> dl(new Download(RequestMethod::POST, url, postData, postMime, Path()));
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
dl->SetCallback(callback);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
||||
void Downloader::Update() {
|
||||
for (auto iter : newDownloads_) {
|
||||
downloads_.push_back(iter);
|
||||
}
|
||||
newDownloads_.clear();
|
||||
|
||||
restart:
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
auto dl = downloads_[i];
|
||||
if (dl->Done()) {
|
||||
dl->RunCallback();
|
||||
dl->Join();
|
||||
downloads_.erase(downloads_.begin() + i);
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Downloader::WaitForAll() {
|
||||
// TODO: Should lock? Though, OK if called from main thread, where Update() is called from.
|
||||
while (!downloads_.empty()) {
|
||||
Update();
|
||||
sleep_ms(10);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> Downloader::GetCurrentProgress() {
|
||||
std::vector<float> progress;
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
if (!downloads_[i]->IsHidden())
|
||||
progress.push_back(downloads_[i]->Progress());
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
void Downloader::CancelAll() {
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
downloads_[i]->Cancel();
|
||||
}
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
downloads_[i]->Join();
|
||||
}
|
||||
downloads_.clear();
|
||||
}
|
||||
|
||||
} // http
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Common/File/Path.h"
|
||||
#include "Common/Net/NetBuffer.h"
|
||||
#include "Common/Net/Resolve.h"
|
||||
#include "Common/Net/HTTPRequest.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
|
@ -44,16 +45,8 @@ namespace http {
|
|||
|
||||
bool GetHeaderValue(const std::vector<std::string> &responseHeaders, const std::string &header, std::string *value);
|
||||
|
||||
struct RequestProgress {
|
||||
RequestProgress() {}
|
||||
explicit RequestProgress(bool *c) : cancelled(c) {}
|
||||
|
||||
float progress = 0.0f;
|
||||
float kBps = 0.0f;
|
||||
bool *cancelled = nullptr;
|
||||
};
|
||||
|
||||
struct RequestParams {
|
||||
class RequestParams {
|
||||
public:
|
||||
RequestParams() {}
|
||||
explicit RequestParams(const char *r) : resource(r) {}
|
||||
RequestParams(const std::string &r, const char *a) : resource(r), acceptMime(a) {}
|
||||
|
@ -68,20 +61,20 @@ public:
|
|||
~Client();
|
||||
|
||||
// Return value is the HTTP return code. 200 means OK. < 0 means some local error.
|
||||
int GET(const RequestParams &req, Buffer *output, RequestProgress *progress);
|
||||
int GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, RequestProgress *progress);
|
||||
int GET(const RequestParams &req, Buffer *output, net::RequestProgress *progress);
|
||||
int GET(const RequestParams &req, Buffer *output, std::vector<std::string> &responseHeaders, net::RequestProgress *progress);
|
||||
|
||||
// Return value is the HTTP return code.
|
||||
int POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, RequestProgress *progress);
|
||||
int POST(const RequestParams &req, const std::string &data, Buffer *output, RequestProgress *progress);
|
||||
int POST(const RequestParams &req, const std::string &data, const std::string &mime, Buffer *output, net::RequestProgress *progress);
|
||||
int POST(const RequestParams &req, const std::string &data, Buffer *output, net::RequestProgress *progress);
|
||||
|
||||
// HEAD, PUT, DELETE aren't implemented yet, but can be done with SendRequest.
|
||||
|
||||
int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, RequestProgress *progress);
|
||||
int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, RequestProgress *progress);
|
||||
int ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, RequestProgress *progress);
|
||||
int SendRequest(const char *method, const RequestParams &req, const char *otherHeaders, net::RequestProgress *progress);
|
||||
int SendRequestWithData(const char *method, const RequestParams &req, const std::string &data, const char *otherHeaders, net::RequestProgress *progress);
|
||||
int ReadResponseHeaders(net::Buffer *readbuf, std::vector<std::string> &responseHeaders, net::RequestProgress *progress);
|
||||
// If your response contains a response, you must read it.
|
||||
int ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, RequestProgress *progress);
|
||||
int ReadResponseEntity(net::Buffer *readbuf, const std::vector<std::string> &responseHeaders, Buffer *output, net::RequestProgress *progress);
|
||||
|
||||
void SetDataTimeout(double t) {
|
||||
dataTimeout_ = t;
|
||||
|
@ -97,139 +90,52 @@ protected:
|
|||
double dataTimeout_ = 900.0;
|
||||
};
|
||||
|
||||
enum class RequestMethod {
|
||||
GET,
|
||||
POST,
|
||||
};
|
||||
|
||||
// Really an asynchronous request.
|
||||
class Download {
|
||||
class HTTPRequest : public Request {
|
||||
public:
|
||||
Download(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile);
|
||||
~Download();
|
||||
HTTPRequest(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = "");
|
||||
~HTTPRequest();
|
||||
|
||||
void Start();
|
||||
void Start() override;
|
||||
void Join() override;
|
||||
|
||||
void Join();
|
||||
|
||||
// Returns 1.0 when done. That one value can be compared exactly - or just use Done().
|
||||
float Progress() const { return progress_.progress; }
|
||||
float SpeedKBps() const { return progress_.kBps; }
|
||||
|
||||
bool Done() const { return completed_; }
|
||||
bool Failed() const { return failed_; }
|
||||
bool Done() override { return completed_; }
|
||||
bool Failed() const override { return failed_; }
|
||||
|
||||
// NOTE! The value of ResultCode is INVALID until Done() returns true.
|
||||
int ResultCode() const { return resultCode_; }
|
||||
int ResultCode() const override { return resultCode_; }
|
||||
|
||||
std::string url() const { return url_; }
|
||||
const Path &outfile() const { return outfile_; }
|
||||
|
||||
void SetAccept(const char *mime) {
|
||||
acceptMime_ = mime;
|
||||
}
|
||||
const Path &outfile() const override { return outfile_; }
|
||||
|
||||
// If not downloading to a file, access this to get the result.
|
||||
Buffer &buffer() { return buffer_; }
|
||||
const Buffer &buffer() const { return buffer_; }
|
||||
Buffer &buffer() override { return buffer_; }
|
||||
const Buffer &buffer() const override { return buffer_; }
|
||||
|
||||
void Cancel() {
|
||||
void Cancel() override {
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
||||
bool IsCancelled() const {
|
||||
bool IsCancelled() const override {
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
// NOTE: Callbacks are NOT executed until RunCallback is called. This is so that
|
||||
// the call will end up on the thread that calls g_DownloadManager.Update().
|
||||
void SetCallback(std::function<void(Download &)> callback) {
|
||||
callback_ = callback;
|
||||
}
|
||||
void RunCallback() {
|
||||
if (callback_) {
|
||||
callback_(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// Just metadata. Convenient for download managers, for example, if set,
|
||||
// Downloader::GetCurrentProgress won't return it in the results.
|
||||
bool IsHidden() const { return hidden_; }
|
||||
void SetHidden(bool hidden) { hidden_ = hidden; }
|
||||
void SetUserAgent(const std::string &userAgent) {
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
private:
|
||||
void Do(); // Actually does the download. Runs on thread.
|
||||
int Perform(const std::string &url);
|
||||
std::string RedirectLocation(const std::string &baseUrl);
|
||||
void SetFailed(int code);
|
||||
|
||||
RequestProgress progress_;
|
||||
RequestMethod method_;
|
||||
std::string postData_;
|
||||
std::string userAgent_;
|
||||
Buffer buffer_;
|
||||
std::vector<std::string> responseHeaders_;
|
||||
std::string url_;
|
||||
Path outfile_;
|
||||
std::thread thread_;
|
||||
const char *acceptMime_ = "*/*";
|
||||
std::string postMime_;
|
||||
int resultCode_ = 0;
|
||||
bool completed_ = false;
|
||||
bool failed_ = false;
|
||||
bool cancelled_ = false;
|
||||
bool hidden_ = false;
|
||||
bool joined_ = false;
|
||||
std::function<void(Download &)> callback_;
|
||||
};
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class Downloader {
|
||||
public:
|
||||
~Downloader() {
|
||||
CancelAll();
|
||||
}
|
||||
|
||||
std::shared_ptr<Download> StartDownload(const std::string &url, const Path &outfile, const char *acceptMime = nullptr);
|
||||
|
||||
std::shared_ptr<Download> StartDownloadWithCallback(
|
||||
const std::string &url,
|
||||
const Path &outfile,
|
||||
std::function<void(Download &)> callback,
|
||||
const char *acceptMime = nullptr);
|
||||
|
||||
std::shared_ptr<Download> AsyncPostWithCallback(
|
||||
const std::string &url,
|
||||
const std::string &postData,
|
||||
const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder.
|
||||
std::function<void(Download &)> callback);
|
||||
|
||||
// Drops finished downloads from the list.
|
||||
void Update();
|
||||
void CancelAll();
|
||||
|
||||
void WaitForAll();
|
||||
void SetUserAgent(const std::string &userAgent) {
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
std::vector<float> GetCurrentProgress();
|
||||
|
||||
size_t GetActiveCount() const {
|
||||
return downloads_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Download>> downloads_;
|
||||
// These get copied to downloads_ in Update(). It's so that callbacks can add new downloads
|
||||
// while running.
|
||||
std::vector<std::shared_ptr<Download>> newDownloads_;
|
||||
|
||||
std::string userAgent_;
|
||||
};
|
||||
|
||||
} // http
|
||||
|
|
127
Common/Net/HTTPNaettRequest.cpp
Normal file
127
Common/Net/HTTPNaettRequest.cpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/Net/HTTPRequest.h"
|
||||
#include "Common/Net/HTTPNaettRequest.h"
|
||||
#include "Common/Thread/ThreadUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Log.h"
|
||||
|
||||
#include "ext/naett/naett.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
HTTPSRequest::HTTPSRequest(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode, const std::string &name)
|
||||
: Request(method, url, name, &cancelled_, progressBarMode), method_(method), postData_(postData), postMime_(postMime), outfile_(outfile) {
|
||||
}
|
||||
|
||||
HTTPSRequest::~HTTPSRequest() {
|
||||
Join();
|
||||
}
|
||||
|
||||
void HTTPSRequest::Start() {
|
||||
_dbg_assert_(!req_);
|
||||
_dbg_assert_(!res_);
|
||||
|
||||
std::vector<naettOption *> options;
|
||||
options.push_back(naettMethod(method_ == RequestMethod::GET ? "GET" : "POST"));
|
||||
options.push_back(naettHeader("Accept", acceptMime_));
|
||||
options.push_back(naettUserAgent(userAgent_.c_str()));
|
||||
if (!postMime_.empty()) {
|
||||
options.push_back(naettHeader("Content-Type", postMime_.c_str()));
|
||||
}
|
||||
if (method_ == RequestMethod::POST) {
|
||||
if (!postData_.empty()) {
|
||||
// Note: Naett does not take ownership over the body.
|
||||
options.push_back(naettBody(postData_.data(), (int)postData_.size()));
|
||||
}
|
||||
} else {
|
||||
_dbg_assert_(postData_.empty());
|
||||
}
|
||||
// 30 s timeout - not sure what's reasonable?
|
||||
options.push_back(naettTimeout(30 * 1000)); // milliseconds
|
||||
|
||||
const naettOption **opts = (const naettOption **)options.data();
|
||||
req_ = naettRequestWithOptions(url_.c_str(), (int)options.size(), opts);
|
||||
res_ = naettMake(req_);
|
||||
|
||||
progress_.Update(0, 0, false);
|
||||
}
|
||||
|
||||
void HTTPSRequest::Join() {
|
||||
if (!res_ || !req_)
|
||||
return; // No pending operation.
|
||||
// Tear down.
|
||||
if (completed_ && res_) {
|
||||
_dbg_assert_(req_);
|
||||
naettClose(res_);
|
||||
naettFree(req_);
|
||||
res_ = nullptr;
|
||||
req_ = nullptr;
|
||||
} else {
|
||||
ERROR_LOG(IO, "HTTPSDownload::Join not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPSRequest::Done() {
|
||||
if (completed_)
|
||||
return true;
|
||||
|
||||
if (!naettComplete(res_)) {
|
||||
int total = 0;
|
||||
int size = naettGetTotalBytesRead(res_, &total);
|
||||
progress_.Update(size, total, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// -1000 is a code specified by us to represent cancellation, that is unlikely to ever collide with naett error codes.
|
||||
resultCode_ = IsCancelled() ? -1000 : naettGetStatus(res_);
|
||||
if (resultCode_ < 0) {
|
||||
// It's a naett error. Translate and handle.
|
||||
switch (resultCode_) {
|
||||
case naettConnectionError: // -1
|
||||
ERROR_LOG(IO, "Connection error");
|
||||
break;
|
||||
case naettProtocolError: // -2
|
||||
ERROR_LOG(IO, "Protocol error");
|
||||
break;
|
||||
case naettReadError: // -3
|
||||
ERROR_LOG(IO, "Read error");
|
||||
break;
|
||||
case naettWriteError: // -4
|
||||
ERROR_LOG(IO, "Write error");
|
||||
break;
|
||||
case naettGenericError: // -5
|
||||
ERROR_LOG(IO, "Generic error");
|
||||
break;
|
||||
default:
|
||||
ERROR_LOG(IO, "Unhandled naett error %d", resultCode_);
|
||||
break;
|
||||
}
|
||||
failed_ = true;
|
||||
progress_.Update(0, 0, true);
|
||||
} else if (resultCode_ == 200) {
|
||||
int bodyLength;
|
||||
const void *body = naettGetBody(res_, &bodyLength);
|
||||
char *dest = buffer_.Append(bodyLength);
|
||||
memcpy(dest, body, bodyLength);
|
||||
if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) {
|
||||
ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str());
|
||||
}
|
||||
progress_.Update(bodyLength, bodyLength, true);
|
||||
} else {
|
||||
WARN_LOG(IO, "Naett request failed: %d", resultCode_);
|
||||
failed_ = true;
|
||||
progress_.Update(0, 0, true);
|
||||
}
|
||||
|
||||
completed_ = true;
|
||||
|
||||
// The callback will be called later.
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTPS_NOT_AVAILABLE
|
63
Common/Net/HTTPNaettRequest.h
Normal file
63
Common/Net/HTTPNaettRequest.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "Common/Net/HTTPRequest.h"
|
||||
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
|
||||
#include "ext/naett/naett.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
// Really an asynchronous request.
|
||||
class HTTPSRequest : public Request {
|
||||
public:
|
||||
HTTPSRequest(RequestMethod method, const std::string &url, const std::string &postData, const std::string &postMime, const Path &outfile, ProgressBarMode progressBarMode = ProgressBarMode::DELAYED, const std::string &name = "");
|
||||
~HTTPSRequest();
|
||||
|
||||
void Start() override;
|
||||
void Join() override;
|
||||
|
||||
// Also acts as a Poll.
|
||||
bool Done() override;
|
||||
bool Failed() const override { return failed_; }
|
||||
|
||||
// NOTE! The value of ResultCode is INVALID until Done() returns true.
|
||||
int ResultCode() const override { return resultCode_; }
|
||||
|
||||
const Path &outfile() const override { return outfile_; }
|
||||
|
||||
// If not downloading to a file, access this to get the result.
|
||||
Buffer &buffer() override { return buffer_; }
|
||||
const Buffer &buffer() const override { return buffer_; }
|
||||
|
||||
void Cancel() override {
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
||||
bool IsCancelled() const override {
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
private:
|
||||
RequestMethod method_;
|
||||
std::string postData_;
|
||||
Buffer buffer_;
|
||||
std::vector<std::string> responseHeaders_;
|
||||
Path outfile_;
|
||||
std::string postMime_;
|
||||
int resultCode_ = 0;
|
||||
bool completed_ = false;
|
||||
bool failed_ = false;
|
||||
bool cancelled_ = false;
|
||||
bool joined_ = false;
|
||||
|
||||
// Naett state
|
||||
naettReq *req_ = nullptr;
|
||||
naettRes *res_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTPS_NOT_AVAILABLE
|
147
Common/Net/HTTPRequest.cpp
Normal file
147
Common/Net/HTTPRequest.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
#include "Common/Net/HTTPRequest.h"
|
||||
#include "Common/Net/HTTPClient.h"
|
||||
#include "Common/Net/HTTPNaettRequest.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/System/OSD.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
Request::Request(RequestMethod method, const std::string &url, const std::string &name, bool *cancelled, ProgressBarMode mode) : method_(method), url_(url), name_(name), progress_(cancelled), progressBarMode_(mode) {
|
||||
progress_.callback = [=](int64_t bytes, int64_t contentLength, bool done) {
|
||||
std::string message;
|
||||
if (!name_.empty()) {
|
||||
message = name_;
|
||||
} else {
|
||||
std::size_t pos = url_.rfind('/');
|
||||
if (pos != std::string::npos) {
|
||||
message = url_.substr(pos + 1);
|
||||
} else {
|
||||
message = url_;
|
||||
}
|
||||
}
|
||||
if (progressBarMode_ != ProgressBarMode::NONE) {
|
||||
if (!done) {
|
||||
g_OSD.SetProgressBar(url_, std::move(message), 0.0f, (float)contentLength, (float)bytes, progressBarMode_ == ProgressBarMode::DELAYED ? 3.0f : 0.0f); // delay 3 seconds before showing.
|
||||
} else {
|
||||
g_OSD.RemoveProgressBar(url_, Failed() ? false : true, 0.5f);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool RequestManager::IsHttpsUrl(const std::string &url) {
|
||||
return startsWith(url, "https:");
|
||||
}
|
||||
|
||||
std::shared_ptr<Request> RequestManager::StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime) {
|
||||
std::shared_ptr<Request> dl;
|
||||
if (IsHttpsUrl(url)) {
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
dl.reset(new HTTPSRequest(RequestMethod::GET, url, "", "", outfile, mode));
|
||||
#else
|
||||
return std::shared_ptr<Request>();
|
||||
#endif
|
||||
} else {
|
||||
dl.reset(new HTTPRequest(RequestMethod::GET, url, "", "", outfile, mode));
|
||||
}
|
||||
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
if (acceptMime)
|
||||
dl->SetAccept(acceptMime);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
||||
std::shared_ptr<Request> RequestManager::StartDownloadWithCallback(
|
||||
const std::string &url,
|
||||
const Path &outfile,
|
||||
ProgressBarMode mode,
|
||||
std::function<void(Request &)> callback,
|
||||
const std::string &name,
|
||||
const char *acceptMime) {
|
||||
std::shared_ptr<Request> dl;
|
||||
if (IsHttpsUrl(url)) {
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
dl.reset(new HTTPSRequest(RequestMethod::GET, url, "", "", outfile, mode, name));
|
||||
#else
|
||||
return std::shared_ptr<Request>();
|
||||
#endif
|
||||
} else {
|
||||
dl.reset(new HTTPRequest(RequestMethod::GET, url, "", "", outfile, mode, name));
|
||||
}
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
if (acceptMime)
|
||||
dl->SetAccept(acceptMime);
|
||||
dl->SetCallback(callback);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
||||
std::shared_ptr<Request> RequestManager::AsyncPostWithCallback(
|
||||
const std::string &url,
|
||||
const std::string &postData,
|
||||
const std::string &postMime,
|
||||
ProgressBarMode mode,
|
||||
std::function<void(Request &)> callback,
|
||||
const std::string &name) {
|
||||
std::shared_ptr<Request> dl;
|
||||
if (IsHttpsUrl(url)) {
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
dl.reset(new HTTPSRequest(RequestMethod::POST, url, postData, postMime, Path(), mode, name));
|
||||
#else
|
||||
return std::shared_ptr<Request>();
|
||||
#endif
|
||||
} else {
|
||||
dl.reset(new HTTPRequest(RequestMethod::POST, url, postData, postMime, Path(), mode, name));
|
||||
}
|
||||
if (!userAgent_.empty())
|
||||
dl->SetUserAgent(userAgent_);
|
||||
dl->SetCallback(callback);
|
||||
newDownloads_.push_back(dl);
|
||||
dl->Start();
|
||||
return dl;
|
||||
}
|
||||
|
||||
void RequestManager::Update() {
|
||||
for (auto iter : newDownloads_) {
|
||||
downloads_.push_back(iter);
|
||||
}
|
||||
newDownloads_.clear();
|
||||
|
||||
restart:
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
auto dl = downloads_[i];
|
||||
if (dl->Done()) {
|
||||
dl->RunCallback();
|
||||
dl->Join();
|
||||
downloads_.erase(downloads_.begin() + i);
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RequestManager::WaitForAll() {
|
||||
// TODO: Should lock? Though, OK if called from main thread, where Update() is called from.
|
||||
while (!downloads_.empty()) {
|
||||
Update();
|
||||
sleep_ms(10);
|
||||
}
|
||||
}
|
||||
|
||||
void RequestManager::CancelAll() {
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
downloads_[i]->Cancel();
|
||||
}
|
||||
for (size_t i = 0; i < downloads_.size(); i++) {
|
||||
downloads_[i]->Join();
|
||||
}
|
||||
downloads_.clear();
|
||||
}
|
||||
|
||||
} // namespace
|
127
Common/Net/HTTPRequest.h
Normal file
127
Common/Net/HTTPRequest.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/Net/NetBuffer.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
enum class RequestMethod {
|
||||
GET,
|
||||
POST,
|
||||
};
|
||||
|
||||
enum class ProgressBarMode {
|
||||
NONE,
|
||||
VISIBLE,
|
||||
DELAYED,
|
||||
};
|
||||
|
||||
// Abstract request.
|
||||
class Request {
|
||||
public:
|
||||
Request(RequestMethod method, const std::string &url, const std::string &name, bool *cancelled, ProgressBarMode mode);
|
||||
virtual ~Request() {}
|
||||
|
||||
void SetAccept(const char *mime) {
|
||||
acceptMime_ = mime;
|
||||
}
|
||||
|
||||
void SetUserAgent(const std::string &userAgent) {
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
// NOTE: Completion callbacks (which these are) are deferred until RunCallback is called. This is so that
|
||||
// the call will end up on the thread that calls g_DownloadManager.Update().
|
||||
void SetCallback(std::function<void(Request &)> callback) {
|
||||
callback_ = callback;
|
||||
}
|
||||
void RunCallback() {
|
||||
if (callback_) {
|
||||
callback_(*this);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Start() = 0;
|
||||
virtual void Join() = 0;
|
||||
|
||||
virtual bool Done() = 0;
|
||||
virtual bool Failed() const = 0;
|
||||
|
||||
virtual int ResultCode() const = 0;
|
||||
|
||||
// Returns 1.0 when done. That one value can be compared exactly - or just use Done().
|
||||
float Progress() const { return progress_.progress; }
|
||||
float SpeedKBps() const { return progress_.kBps; }
|
||||
std::string url() const { return url_; }
|
||||
virtual const Path &outfile() const = 0;
|
||||
|
||||
virtual void Cancel() = 0;
|
||||
virtual bool IsCancelled() const = 0;
|
||||
|
||||
// Response
|
||||
virtual Buffer &buffer() = 0;
|
||||
virtual const Buffer &buffer() const = 0;
|
||||
|
||||
protected:
|
||||
std::function<void(Request &)> callback_;
|
||||
RequestMethod method_;
|
||||
std::string url_;
|
||||
std::string name_;
|
||||
const char *acceptMime_ = "*/*";
|
||||
std::string userAgent_;
|
||||
|
||||
net::RequestProgress progress_;
|
||||
ProgressBarMode progressBarMode_;
|
||||
};
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class RequestManager {
|
||||
public:
|
||||
~RequestManager() {
|
||||
CancelAll();
|
||||
}
|
||||
|
||||
std::shared_ptr<Request> StartDownload(const std::string &url, const Path &outfile, ProgressBarMode mode, const char *acceptMime = nullptr);
|
||||
|
||||
std::shared_ptr<Request> StartDownloadWithCallback(
|
||||
const std::string &url,
|
||||
const Path &outfile,
|
||||
ProgressBarMode mode,
|
||||
std::function<void(Request &)> callback,
|
||||
const std::string &name = "",
|
||||
const char *acceptMime = nullptr);
|
||||
|
||||
std::shared_ptr<Request> AsyncPostWithCallback(
|
||||
const std::string &url,
|
||||
const std::string &postData,
|
||||
const std::string &postMime, // Use postMime = "application/x-www-form-urlencoded" for standard form-style posts, such as used by retroachievements. For encoding form data manually we have MultipartFormDataEncoder.
|
||||
ProgressBarMode mode,
|
||||
std::function<void(Request &)> callback,
|
||||
const std::string &name = "");
|
||||
|
||||
// Drops finished downloads from the list.
|
||||
void Update();
|
||||
void CancelAll();
|
||||
|
||||
void WaitForAll();
|
||||
void SetUserAgent(const std::string &userAgent) {
|
||||
userAgent_ = userAgent;
|
||||
}
|
||||
|
||||
private:
|
||||
bool IsHttpsUrl(const std::string &url);
|
||||
|
||||
std::vector<std::shared_ptr<Request>> downloads_;
|
||||
// These get copied to downloads_ in Update(). It's so that callbacks can add new downloads
|
||||
// while running.
|
||||
std::vector<std::shared_ptr<Request>> newDownloads_;
|
||||
|
||||
std::string userAgent_;
|
||||
};
|
||||
|
||||
} // namespace net
|
|
@ -60,7 +60,7 @@ namespace http {
|
|||
// Note: charset here helps prevent XSS.
|
||||
const char *const DEFAULT_MIME_TYPE = "text/html; charset=utf-8";
|
||||
|
||||
Request::Request(int fd)
|
||||
ServerRequest::ServerRequest(int fd)
|
||||
: fd_(fd) {
|
||||
in_ = new net::InputSink(fd);
|
||||
out_ = new net::OutputSink(fd);
|
||||
|
@ -73,7 +73,7 @@ Request::Request(int fd)
|
|||
}
|
||||
}
|
||||
|
||||
Request::~Request() {
|
||||
ServerRequest::~ServerRequest() {
|
||||
Close();
|
||||
|
||||
if (!in_->Empty()) {
|
||||
|
@ -86,7 +86,7 @@ Request::~Request() {
|
|||
delete out_;
|
||||
}
|
||||
|
||||
void Request::WriteHttpResponseHeader(const char *ver, int status, int64_t size, const char *mimeType, const char *otherHeaders) const {
|
||||
void ServerRequest::WriteHttpResponseHeader(const char *ver, int status, int64_t size, const char *mimeType, const char *otherHeaders) const {
|
||||
const char *statusStr;
|
||||
switch (status) {
|
||||
case 200: statusStr = "OK"; break;
|
||||
|
@ -123,18 +123,18 @@ void Request::WriteHttpResponseHeader(const char *ver, int status, int64_t size,
|
|||
buffer->Push("\r\n");
|
||||
}
|
||||
|
||||
void Request::WritePartial() const {
|
||||
void ServerRequest::WritePartial() const {
|
||||
_assert_(fd_);
|
||||
out_->Flush();
|
||||
}
|
||||
|
||||
void Request::Write() {
|
||||
void ServerRequest::Write() {
|
||||
_assert_(fd_);
|
||||
WritePartial();
|
||||
Close();
|
||||
}
|
||||
|
||||
void Request::Close() {
|
||||
void ServerRequest::Close() {
|
||||
if (fd_) {
|
||||
closesocket(fd_);
|
||||
fd_ = 0;
|
||||
|
@ -317,7 +317,7 @@ void Server::Stop() {
|
|||
}
|
||||
|
||||
void Server::HandleConnection(int conn_fd) {
|
||||
Request request(conn_fd);
|
||||
ServerRequest request(conn_fd);
|
||||
if (!request.IsOK()) {
|
||||
WARN_LOG(IO, "Bad request, ignoring.");
|
||||
return;
|
||||
|
@ -331,11 +331,11 @@ void Server::HandleConnection(int conn_fd) {
|
|||
request.Write();
|
||||
}
|
||||
|
||||
void Server::HandleRequest(const Request &request) {
|
||||
void Server::HandleRequest(const ServerRequest &request) {
|
||||
HandleRequestDefault(request);
|
||||
}
|
||||
|
||||
void Server::HandleRequestDefault(const Request &request) {
|
||||
void Server::HandleRequestDefault(const ServerRequest &request) {
|
||||
if (request.resource() == nullptr) {
|
||||
fallback_(request);
|
||||
return;
|
||||
|
@ -350,14 +350,14 @@ void Server::HandleRequestDefault(const Request &request) {
|
|||
}
|
||||
}
|
||||
|
||||
void Server::Handle404(const Request &request) {
|
||||
void Server::Handle404(const ServerRequest &request) {
|
||||
INFO_LOG(IO, "No handler for '%s', falling back to 404.", request.resource());
|
||||
const char *payload = "<html><body>404 not found</body></html>\r\n";
|
||||
request.WriteHttpResponseHeader("1.0", 404, (int)strlen(payload));
|
||||
request.Out()->Push(payload);
|
||||
}
|
||||
|
||||
void Server::HandleListing(const Request &request) {
|
||||
void Server::HandleListing(const ServerRequest &request) {
|
||||
request.WriteHttpResponseHeader("1.0", 200, -1, "text/plain");
|
||||
for (auto iter = handlers_.begin(); iter != handlers_.end(); ++iter) {
|
||||
request.Out()->Printf("%s\n", iter->first.c_str());
|
||||
|
|
|
@ -25,10 +25,10 @@ class OutputSink;
|
|||
|
||||
namespace http {
|
||||
|
||||
class Request {
|
||||
class ServerRequest {
|
||||
public:
|
||||
Request(int fd);
|
||||
~Request();
|
||||
ServerRequest(int fd);
|
||||
~ServerRequest();
|
||||
|
||||
const char *resource() const {
|
||||
return header_.resource;
|
||||
|
@ -75,7 +75,7 @@ public:
|
|||
Server(NewThreadExecutor *executor);
|
||||
virtual ~Server();
|
||||
|
||||
typedef std::function<void(const Request &)> UrlHandlerFunc;
|
||||
typedef std::function<void(const ServerRequest &)> UrlHandlerFunc;
|
||||
typedef std::map<std::string, UrlHandlerFunc> UrlHandlerMap;
|
||||
|
||||
// Runs forever, serving request. If you want to do something else than serve pages,
|
||||
|
@ -94,7 +94,7 @@ public:
|
|||
// If you want to customize things at a lower level than just a simple path handler,
|
||||
// then inherit and override this. Implementations should forward to HandleRequestDefault
|
||||
// if they don't recognize the url.
|
||||
virtual void HandleRequest(const Request &request);
|
||||
virtual void HandleRequest(const ServerRequest &request);
|
||||
|
||||
int Port() {
|
||||
return port_;
|
||||
|
@ -107,11 +107,11 @@ private:
|
|||
void HandleConnection(int conn_fd);
|
||||
|
||||
// Things like default 404, etc.
|
||||
void HandleRequestDefault(const Request &request);
|
||||
void HandleRequestDefault(const ServerRequest &request);
|
||||
|
||||
// Neat built-in handlers that are tied to the server.
|
||||
void HandleListing(const Request &request);
|
||||
void Handle404(const Request &request);
|
||||
void HandleListing(const ServerRequest &request);
|
||||
void Handle404(const ServerRequest &request);
|
||||
|
||||
int listener_;
|
||||
int port_ = 0;
|
||||
|
|
|
@ -22,6 +22,18 @@
|
|||
|
||||
namespace net {
|
||||
|
||||
void RequestProgress::Update(int64_t downloaded, int64_t totalBytes, bool done) {
|
||||
if (totalBytes) {
|
||||
progress = (double)downloaded / (double)totalBytes;
|
||||
} else {
|
||||
progress = 0.01f;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(downloaded, totalBytes, done);
|
||||
}
|
||||
}
|
||||
|
||||
bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) {
|
||||
static constexpr float CANCEL_INTERVAL = 0.25f;
|
||||
for (size_t pos = 0, end = data_.size(); pos < end; ) {
|
||||
|
@ -48,7 +60,7 @@ bool Buffer::FlushSocket(uintptr_t sock, double timeout, bool *cancelled) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *kBps, bool *cancelled) {
|
||||
bool Buffer::ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress) {
|
||||
static constexpr float CANCEL_INTERVAL = 0.25f;
|
||||
std::vector<char> buf;
|
||||
// We're non-blocking and reading from an OS buffer, so try to read as much as we can at a time.
|
||||
|
@ -64,11 +76,15 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *
|
|||
int total = 0;
|
||||
while (true) {
|
||||
bool ready = false;
|
||||
while (!ready && cancelled) {
|
||||
if (*cancelled)
|
||||
|
||||
// If we might need to cancel, check on a timer for it to be ready.
|
||||
// After this, we'll block on reading so we do this while first if we have a cancel pointer.
|
||||
while (!ready && progress && progress->cancelled) {
|
||||
if (*progress->cancelled)
|
||||
return false;
|
||||
ready = fd_util::WaitUntilReady(fd, CANCEL_INTERVAL, false);
|
||||
}
|
||||
|
||||
int retval = recv(fd, &buf[0], (int)buf.size(), MSG_NOSIGNAL);
|
||||
if (retval == 0) {
|
||||
return true;
|
||||
|
@ -88,10 +104,10 @@ bool Buffer::ReadAllWithProgress(int fd, int knownSize, float *progress, float *
|
|||
char *p = Append((size_t)retval);
|
||||
memcpy(p, &buf[0], retval);
|
||||
total += retval;
|
||||
if (progress)
|
||||
*progress = (float)total / (float)knownSize;
|
||||
if (kBps)
|
||||
*kBps = (float)(total / (time_now_d() - st)) / 1024.0f;
|
||||
if (progress) {
|
||||
progress->Update(total, knownSize, false);
|
||||
progress->kBps = (float)(total / (time_now_d() - st)) / 1024.0f;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -114,4 +130,4 @@ int Buffer::Read(int fd, size_t sz) {
|
|||
return (int)received;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
|
||||
namespace net {
|
||||
|
||||
class RequestProgress {
|
||||
public:
|
||||
explicit RequestProgress(bool *c) : cancelled(c) {}
|
||||
|
||||
void Update(int64_t downloaded, int64_t totalBytes, bool done);
|
||||
|
||||
float progress = 0.0f;
|
||||
float kBps = 0.0f;
|
||||
bool *cancelled = nullptr;
|
||||
std::function<void(int64_t, int64_t, bool)> callback;
|
||||
};
|
||||
|
||||
class Buffer : public ::Buffer {
|
||||
public:
|
||||
bool FlushSocket(uintptr_t sock, double timeout, bool *cancelled = nullptr);
|
||||
|
||||
bool ReadAllWithProgress(int fd, int knownSize, float *progress, float *kBps, bool *cancelled);
|
||||
bool ReadAllWithProgress(int fd, int knownSize, RequestProgress *progress);
|
||||
|
||||
// < 0: error
|
||||
// >= 0: number of bytes read
|
||||
|
|
|
@ -30,8 +30,18 @@
|
|||
#include "Common/Log.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
#include "ext/naett/naett.h"
|
||||
#endif
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
#include <jni.h>
|
||||
extern JavaVM *gJvm;
|
||||
#endif
|
||||
|
||||
namespace net {
|
||||
|
||||
static bool g_naettInitialized;
|
||||
|
||||
void Init()
|
||||
{
|
||||
|
@ -40,6 +50,17 @@ void Init()
|
|||
WSADATA wsaData = {0};
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
#endif
|
||||
if (!g_naettInitialized) {
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
_assert_(gJvm != nullptr);
|
||||
naettInit(gJvm);
|
||||
#else
|
||||
naettInit(NULL);
|
||||
#endif
|
||||
#endif
|
||||
g_naettInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
|
|
|
@ -69,7 +69,7 @@ static bool ListContainsNoCase(const std::string &list, const std::string value)
|
|||
return false;
|
||||
}
|
||||
|
||||
WebSocketServer *WebSocketServer::CreateAsUpgrade(const http::Request &request, const std::string &protocol) {
|
||||
WebSocketServer *WebSocketServer::CreateAsUpgrade(const http::ServerRequest &request, const std::string &protocol) {
|
||||
auto requireHeader = [&](const char *name, const char *expected) {
|
||||
std::string val;
|
||||
if (!request.GetHeader(name, &val)) {
|
||||
|
|
|
@ -29,7 +29,7 @@ enum class WebSocketClose : uint16_t {
|
|||
// RFC 6455
|
||||
class WebSocketServer {
|
||||
public:
|
||||
static WebSocketServer *CreateAsUpgrade(const http::Request &request, const std::string &protocol = "");
|
||||
static WebSocketServer *CreateAsUpgrade(const http::ServerRequest &request, const std::string &protocol = "");
|
||||
|
||||
void Send(const std::string &str);
|
||||
void Send(const std::vector<uint8_t> &payload);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "Common/Render/Text/draw_text_uwp.h"
|
||||
#include "Common/Render/Text/draw_text_qt.h"
|
||||
#include "Common/Render/Text/draw_text_android.h"
|
||||
#include "Common/Render/Text/draw_text_sdl.h"
|
||||
|
||||
TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) {
|
||||
// These probably shouldn't be state.
|
||||
|
@ -92,6 +93,8 @@ TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) {
|
|||
drawer = new TextDrawerQt(draw);
|
||||
#elif PPSSPP_PLATFORM(ANDROID)
|
||||
drawer = new TextDrawerAndroid(draw);
|
||||
#elif USE_SDL2_TTF
|
||||
drawer = new TextDrawerSDL(draw);
|
||||
#endif
|
||||
if (drawer && !drawer->IsReady()) {
|
||||
delete drawer;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "ppsspp_config.h"
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/System/Display.h"
|
||||
|
|
470
Common/Render/Text/draw_text_sdl.cpp
Normal file
470
Common/Render/Text/draw_text_sdl.cpp
Normal file
|
@ -0,0 +1,470 @@
|
|||
#include "ppsspp_config.h"
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/System/Display.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Common/Data/Hash/Hash.h"
|
||||
#include "Common/Data/Text/WrapText.h"
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
#include "Common/File/VFS/VFS.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/Render/Text/draw_text.h"
|
||||
#include "Common/Render/Text/draw_text_sdl.h"
|
||||
|
||||
#if defined(USE_SDL2_TTF)
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
#include "SDL2/SDL_ttf.h"
|
||||
|
||||
TextDrawerSDL::TextDrawerSDL(Draw::DrawContext *draw): TextDrawer(draw) {
|
||||
if (TTF_Init() < 0) {
|
||||
ERROR_LOG(G3D, "Unable to initialize SDL2_ttf");
|
||||
}
|
||||
|
||||
dpiScale_ = CalculateDPIScale();
|
||||
|
||||
#if defined(USE_SDL2_TTF_FONTCONFIG)
|
||||
config = FcInitLoadConfigAndFonts();
|
||||
#endif
|
||||
|
||||
PrepareFallbackFonts();
|
||||
}
|
||||
|
||||
TextDrawerSDL::~TextDrawerSDL() {
|
||||
ClearCache();
|
||||
TTF_Quit();
|
||||
|
||||
#if defined(USE_SDL2_TTF_FONTCONFIG)
|
||||
FcConfigDestroy(config);
|
||||
FcFini();
|
||||
#endif
|
||||
}
|
||||
|
||||
// If a user complains about missing characters on SDL, re-visit this!
|
||||
void TextDrawerSDL::PrepareFallbackFonts() {
|
||||
#if defined(USE_SDL2_TTF_FONTCONFIG)
|
||||
FcObjectSet *os = FcObjectSetBuild (FC_FILE, FC_INDEX, (char *) 0);
|
||||
|
||||
FcPattern *names[] = {
|
||||
FcNameParse((const FcChar8 *) "Source Han Sans Medium"),
|
||||
FcNameParse((const FcChar8 *) "Droid Sans Bold"),
|
||||
FcNameParse((const FcChar8 *) "DejaVu Sans Condensed"),
|
||||
FcNameParse((const FcChar8 *) "Noto Sans CJK Medium"),
|
||||
FcNameParse((const FcChar8 *) "Noto Sans Hebrew Medium"),
|
||||
FcNameParse((const FcChar8 *) "Noto Sans Lao Medium"),
|
||||
FcNameParse((const FcChar8 *) "Noto Sans Thai Medium")
|
||||
};
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(names); i++) {
|
||||
FcFontSet *foundFonts = FcFontList(config, names[i], os);
|
||||
|
||||
for (int j = 0; foundFonts && j < foundFonts->nfont; ++j) {
|
||||
FcPattern* font = foundFonts->fonts[j];
|
||||
FcChar8 *path;
|
||||
int fontIndex;
|
||||
|
||||
if (FcPatternGetInteger(font, FC_INDEX, 0, &fontIndex) != FcResultMatch) {
|
||||
fontIndex = 0; // The 0th face is guaranteed to exist
|
||||
}
|
||||
|
||||
if (FcPatternGetString(font, FC_FILE, 0, &path) == FcResultMatch) {
|
||||
std::string path_str((const char*)path);
|
||||
fallbackFontPaths_.push_back(std::make_pair(path_str, fontIndex));
|
||||
}
|
||||
}
|
||||
|
||||
if (foundFonts) {
|
||||
FcFontSetDestroy(foundFonts);
|
||||
}
|
||||
|
||||
FcPatternDestroy(names[i]);
|
||||
}
|
||||
|
||||
if (os) {
|
||||
FcObjectSetDestroy(os);
|
||||
}
|
||||
#elif PPSSPP_PLATFORM(MAC)
|
||||
const char *fontDirs[] = {
|
||||
"/System/Library/Fonts/",
|
||||
"/System/Library/Fonts/Supplemental/",
|
||||
"/Library/Fonts/"
|
||||
};
|
||||
|
||||
const char *fallbackFonts[] = {
|
||||
"Hiragino Sans GB.ttc",
|
||||
"PingFang.ttc",
|
||||
"PingFang SC.ttc",
|
||||
"PingFang TC.ttc",
|
||||
"ヒラギノ角ゴシック W4.ttc",
|
||||
"AppleGothic.ttf",
|
||||
"Arial Unicode.ttf",
|
||||
};
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(fontDirs); i++) {
|
||||
for (int j = 0; j < ARRAY_SIZE(fallbackFonts); j++) {
|
||||
Path fontPath = Path(fontDirs[i]) / fallbackFonts[j];
|
||||
|
||||
if (File::Exists(fontPath)) {
|
||||
TTF_Font *openedFont = TTF_OpenFont(fontPath.ToString().c_str(), 24);
|
||||
int64_t numFaces = TTF_FontFaces(openedFont);
|
||||
|
||||
for (int k = 0; k < numFaces; k++) {
|
||||
TTF_Font *fontFace = TTF_OpenFontIndex(fontPath.ToString().c_str(), 24, k);
|
||||
std::string fontFaceName(TTF_FontFaceStyleName(fontFace));
|
||||
TTF_CloseFont(fontFace);
|
||||
|
||||
if (strstr(fontFaceName.c_str(), "Medium") ||
|
||||
strstr(fontFaceName.c_str(), "Regular"))
|
||||
{
|
||||
fallbackFontPaths_.push_back(std::make_pair(fontPath.ToString(), k));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TTF_CloseFont(openedFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// We don't have a fallback font for this platform.
|
||||
// Unsupported characters will be rendered as squares.
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t TextDrawerSDL::CheckMissingGlyph(const std::string& text) {
|
||||
TTF_Font *font = fontMap_.find(fontHash_)->second;
|
||||
UTF8 utf8Decoded(text.c_str());
|
||||
|
||||
uint32_t missingGlyph = 0;
|
||||
for (int i = 0; i < text.length(); ) {
|
||||
uint32_t glyph = utf8Decoded.next();
|
||||
i = utf8Decoded.byteIndex();
|
||||
|
||||
if (!TTF_GlyphIsProvided32(font, glyph)) {
|
||||
missingGlyph = glyph;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return missingGlyph;
|
||||
}
|
||||
|
||||
// If this returns true, the first font in fallbackFonts_ can be used as a fallback.
|
||||
bool TextDrawerSDL::FindFallbackFonts(uint32_t missingGlyph, int ptSize) {
|
||||
// If we encounter a missing glyph, try to use one of the fallback fonts.
|
||||
for (int i = 0; i < fallbackFonts_.size(); i++) {
|
||||
TTF_Font *fallbackFont = fallbackFonts_[i];
|
||||
if (TTF_GlyphIsProvided32(fallbackFont, missingGlyph)) {
|
||||
fallbackFonts_.erase(fallbackFonts_.begin() + i);
|
||||
fallbackFonts_.insert(fallbackFonts_.begin(), fallbackFont);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the loaded fonts can handle it, load more fonts.
|
||||
for (int i = 0; i < fallbackFontPaths_.size(); i++) {
|
||||
std::string& fontPath = fallbackFontPaths_[i].first;
|
||||
int faceIndex = fallbackFontPaths_[i].second;
|
||||
|
||||
TTF_Font *font = TTF_OpenFontIndex(fontPath.c_str(), ptSize, faceIndex);
|
||||
|
||||
if (TTF_GlyphIsProvided32(font, missingGlyph)) {
|
||||
fallbackFonts_.insert(fallbackFonts_.begin(), font);
|
||||
fallbackFontPaths_.erase(fallbackFontPaths_.begin() + i);
|
||||
return true;
|
||||
} else {
|
||||
TTF_CloseFont(font);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t TextDrawerSDL::SetFont(const char *fontName, int size, int flags) {
|
||||
uint32_t fontHash = fontName && strlen(fontName) ? hash::Adler32((const uint8_t *)fontName, strlen(fontName)) : 0;
|
||||
fontHash ^= size;
|
||||
fontHash ^= flags << 10;
|
||||
|
||||
auto iter = fontMap_.find(fontHash);
|
||||
if (iter != fontMap_.end()) {
|
||||
fontHash_ = fontHash;
|
||||
return fontHash;
|
||||
}
|
||||
|
||||
const char *useFont = fontName ? fontName : "Roboto-Condensed.ttf";
|
||||
const int ptSize = (int)((size + 6) / dpiScale_);
|
||||
|
||||
TTF_Font *font = TTF_OpenFont(useFont, ptSize);
|
||||
|
||||
if (!font) {
|
||||
File::FileInfo fileInfo;
|
||||
g_VFS.GetFileInfo("Roboto-Condensed.ttf", &fileInfo);
|
||||
font = TTF_OpenFont(fileInfo.fullName.c_str(), ptSize);
|
||||
}
|
||||
|
||||
fontMap_[fontHash] = font;
|
||||
fontHash_ = fontHash;
|
||||
return fontHash;
|
||||
}
|
||||
|
||||
void TextDrawerSDL::SetFont(uint32_t fontHandle) {
|
||||
uint32_t fontHash = fontHandle;
|
||||
auto iter = fontMap_.find(fontHash);
|
||||
if (iter != fontMap_.end()) {
|
||||
fontHash_ = fontHandle;
|
||||
} else {
|
||||
ERROR_LOG(G3D, "Invalid font handle %08x", fontHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerSDL::MeasureString(const char *str, size_t len, float *w, float *h) {
|
||||
CacheKey key{ std::string(str, len), fontHash_ };
|
||||
|
||||
TextMeasureEntry *entry;
|
||||
auto iter = sizeCache_.find(key);
|
||||
if (iter != sizeCache_.end()) {
|
||||
entry = iter->second.get();
|
||||
} else {
|
||||
TTF_Font *font = fontMap_.find(fontHash_)->second;
|
||||
int ptSize = TTF_FontHeight(font) / 1.35;
|
||||
|
||||
uint32_t missingGlyph = CheckMissingGlyph(key.text);
|
||||
|
||||
if (missingGlyph && FindFallbackFonts(missingGlyph, ptSize)) {
|
||||
font = fallbackFonts_[0];
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
TTF_SizeUTF8(font, key.text.c_str(), &width, &height);
|
||||
|
||||
entry = new TextMeasureEntry();
|
||||
entry->width = width;
|
||||
entry->height = height;
|
||||
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
|
||||
}
|
||||
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
*w = entry->width * fontScaleX_ * dpiScale_;
|
||||
*h = entry->height * fontScaleY_ * dpiScale_;
|
||||
}
|
||||
|
||||
void TextDrawerSDL::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) {
|
||||
std::string toMeasure = std::string(str, len);
|
||||
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
if (wrap) {
|
||||
bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0;
|
||||
WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w, wrap);
|
||||
}
|
||||
|
||||
TTF_Font *font = fontMap_.find(fontHash_)->second;
|
||||
int ptSize = TTF_FontHeight(font) / 1.35;
|
||||
uint32_t missingGlyph = CheckMissingGlyph(toMeasure);
|
||||
|
||||
if (missingGlyph && FindFallbackFonts(missingGlyph, ptSize)) {
|
||||
font = fallbackFonts_[0];
|
||||
}
|
||||
|
||||
std::vector<std::string> lines;
|
||||
SplitString(toMeasure, '\n', lines);
|
||||
|
||||
int total_w = 0;
|
||||
int total_h = 0;
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
CacheKey key{ lines[i], fontHash_ };
|
||||
|
||||
TextMeasureEntry *entry;
|
||||
auto iter = sizeCache_.find(key);
|
||||
if (iter != sizeCache_.end()) {
|
||||
entry = iter->second.get();
|
||||
} else {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
TTF_SizeUTF8(font, lines[i].c_str(), &width, &height);
|
||||
entry = new TextMeasureEntry();
|
||||
entry->width = width;
|
||||
entry->height = height;
|
||||
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
|
||||
}
|
||||
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
if (total_w < entry->width) {
|
||||
total_w = entry->width;
|
||||
}
|
||||
total_h += TTF_FontLineSkip(font);
|
||||
}
|
||||
|
||||
*w = total_w * fontScaleX_ * dpiScale_;
|
||||
*h = total_h * fontScaleY_ * dpiScale_;
|
||||
}
|
||||
|
||||
void TextDrawerSDL::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) {
|
||||
using namespace Draw;
|
||||
if (!strlen(str))
|
||||
return;
|
||||
|
||||
CacheKey key{ std::string(str), fontHash_ };
|
||||
target.Flush(true);
|
||||
|
||||
TextStringEntry *entry;
|
||||
|
||||
auto iter = cache_.find(key);
|
||||
if (iter != cache_.end()) {
|
||||
entry = iter->second.get();
|
||||
entry->lastUsedFrame = frameCount_;
|
||||
} else {
|
||||
DataFormat texFormat = Draw::DataFormat::R4G4B4A4_UNORM_PACK16;
|
||||
|
||||
entry = new TextStringEntry();
|
||||
|
||||
TextureDesc desc{};
|
||||
std::vector<uint8_t> bitmapData;
|
||||
DrawStringBitmap(bitmapData, *entry, texFormat, str, align);
|
||||
desc.initData.push_back(&bitmapData[0]);
|
||||
|
||||
desc.type = TextureType::LINEAR2D;
|
||||
desc.format = texFormat;
|
||||
desc.width = entry->bmWidth;
|
||||
desc.height = entry->bmHeight;
|
||||
desc.depth = 1;
|
||||
desc.mipLevels = 1;
|
||||
desc.tag = "TextDrawer";
|
||||
entry->texture = draw_->CreateTexture(desc);
|
||||
cache_[key] = std::unique_ptr<TextStringEntry>(entry);
|
||||
}
|
||||
|
||||
if (entry->texture) {
|
||||
draw_->BindTexture(0, entry->texture);
|
||||
}
|
||||
|
||||
float w = entry->bmWidth * fontScaleX_ * dpiScale_;
|
||||
float h = entry->bmHeight * fontScaleY_ * dpiScale_;
|
||||
DrawBuffer::DoAlign(align, &x, &y, &w, &h);
|
||||
if (entry->texture) {
|
||||
target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color);
|
||||
target.Flush(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerSDL::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align) {
|
||||
if (!strlen(str)) {
|
||||
bitmapData.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace "&&" with "&"
|
||||
std::string processedStr = ReplaceAll(str, "&&", "&");
|
||||
|
||||
// If a string includes only newlines, SDL2_ttf will refuse to render it
|
||||
// thinking it is empty. Add a space to avoid that.
|
||||
bool isAllNewline = processedStr.find_first_not_of('\n') == std::string::npos;
|
||||
|
||||
if (isAllNewline) {
|
||||
processedStr.push_back(' ');
|
||||
}
|
||||
|
||||
TTF_Font *font = fontMap_.find(fontHash_)->second;
|
||||
int ptSize = TTF_FontHeight(font) / 1.35;
|
||||
|
||||
uint32_t missingGlyph = CheckMissingGlyph(processedStr);
|
||||
|
||||
if (missingGlyph && FindFallbackFonts(missingGlyph, ptSize)) {
|
||||
font = fallbackFonts_[0];
|
||||
}
|
||||
|
||||
if (align & ALIGN_HCENTER)
|
||||
TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_CENTER);
|
||||
else if (align & ALIGN_LEFT)
|
||||
TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_LEFT);
|
||||
else if (align & ALIGN_RIGHT)
|
||||
TTF_SetFontWrappedAlign(font, TTF_WRAPPED_ALIGN_RIGHT);
|
||||
|
||||
SDL_Color fgColor = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
SDL_Surface *text = TTF_RenderUTF8_Blended_Wrapped(font, processedStr.c_str(), fgColor, 0);
|
||||
SDL_LockSurface(text);
|
||||
|
||||
entry.texture = nullptr;
|
||||
entry.bmWidth = entry.width = text->pitch / sizeof(uint32_t);
|
||||
entry.bmHeight = entry.height = text->h;
|
||||
entry.lastUsedFrame = frameCount_;
|
||||
|
||||
uint32_t *imageData = (uint32_t *)text->pixels;
|
||||
|
||||
if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) {
|
||||
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t));
|
||||
uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0];
|
||||
|
||||
for (int x = 0; x < entry.bmWidth; x++) {
|
||||
for (int y = 0; y < entry.bmHeight; y++) {
|
||||
uint64_t index = entry.bmWidth * y + x;
|
||||
bitmapData16[index] = 0xfff0 | (imageData[index] >> 28);
|
||||
}
|
||||
}
|
||||
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
|
||||
bitmapData.resize(entry.bmWidth * entry.bmHeight);
|
||||
for (int x = 0; x < entry.bmWidth; x++) {
|
||||
for (int y = 0; y < entry.bmHeight; y++) {
|
||||
uint64_t index = entry.bmWidth * y + x;
|
||||
bitmapData[index] = imageData[index] >> 24;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_assert_msg_(false, "Bad TextDrawer format");
|
||||
}
|
||||
|
||||
SDL_UnlockSurface(text);
|
||||
SDL_FreeSurface(text);
|
||||
}
|
||||
|
||||
void TextDrawerSDL::OncePerFrame() {
|
||||
// Reset everything if DPI changes
|
||||
float newDpiScale = CalculateDPIScale();
|
||||
if (newDpiScale != dpiScale_) {
|
||||
dpiScale_ = newDpiScale;
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
// Drop old strings. Use a prime number to reduce clashing with other rhythms
|
||||
if (frameCount_ % 23 == 0) {
|
||||
for (auto iter = cache_.begin(); iter != cache_.end();) {
|
||||
if (frameCount_ - iter->second->lastUsedFrame > 100) {
|
||||
if (iter->second->texture)
|
||||
iter->second->texture->Release();
|
||||
cache_.erase(iter++);
|
||||
} else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto iter = sizeCache_.begin(); iter != sizeCache_.end(); ) {
|
||||
if (frameCount_ - iter->second->lastUsedFrame > 100) {
|
||||
sizeCache_.erase(iter++);
|
||||
} else {
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextDrawerSDL::ClearCache() {
|
||||
for (auto &iter : cache_) {
|
||||
if (iter.second->texture)
|
||||
iter.second->texture->Release();
|
||||
}
|
||||
cache_.clear();
|
||||
sizeCache_.clear();
|
||||
|
||||
for (auto iter : fontMap_) {
|
||||
TTF_CloseFont(iter.second);
|
||||
}
|
||||
for (auto iter : fallbackFonts_) {
|
||||
TTF_CloseFont(iter);
|
||||
}
|
||||
fontMap_.clear();
|
||||
fallbackFonts_.clear();
|
||||
fontHash_ = 0;
|
||||
}
|
||||
|
||||
#endif
|
47
Common/Render/Text/draw_text_sdl.h
Normal file
47
Common/Render/Text/draw_text_sdl.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#include <map>
|
||||
#include "Common/Render/Text/draw_text.h"
|
||||
|
||||
#if defined(USE_SDL2_TTF_FONTCONFIG)
|
||||
#include <fontconfig/fontconfig.h>
|
||||
#endif
|
||||
|
||||
// SDL2_ttf's TTF_Font is a typedef of _TTF_Font.
|
||||
struct _TTF_Font;
|
||||
|
||||
class TextDrawerSDL : public TextDrawer {
|
||||
public:
|
||||
TextDrawerSDL(Draw::DrawContext *draw);
|
||||
~TextDrawerSDL();
|
||||
|
||||
uint32_t SetFont(const char *fontName, int size, int flags) override;
|
||||
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
|
||||
void MeasureString(const char *str, size_t len, float *w, float *h) override;
|
||||
void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override;
|
||||
void DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, const char *str, int align = ALIGN_TOPLEFT) override;
|
||||
// Use for housekeeping like throwing out old strings.
|
||||
void OncePerFrame() override;
|
||||
|
||||
protected:
|
||||
void ClearCache() override;
|
||||
void PrepareFallbackFonts();
|
||||
uint32_t CheckMissingGlyph(const std::string& text);
|
||||
bool FindFallbackFonts(uint32_t missingGlyph, int ptSize);
|
||||
|
||||
uint32_t fontHash_;
|
||||
std::map<uint32_t, _TTF_Font *> fontMap_;
|
||||
|
||||
std::map<CacheKey, std::unique_ptr<TextStringEntry>> cache_;
|
||||
std::map<CacheKey, std::unique_ptr<TextMeasureEntry>> sizeCache_;
|
||||
|
||||
std::vector<_TTF_Font *> fallbackFonts_;
|
||||
std::vector<std::pair<std::string, int>> fallbackFontPaths_; // path and font face index
|
||||
|
||||
#if defined(USE_SDL2_TTF_FONTCONFIG)
|
||||
FcConfig *config;
|
||||
#endif
|
||||
};
|
|
@ -194,7 +194,7 @@ void CPUInfo::Detect()
|
|||
|
||||
truncate_cpy(cpu_string, parser.ISAString().c_str());
|
||||
|
||||
// A number of CPUs support a limited set of B. It's not all U74, so we use SOC for now...
|
||||
// A number of CPUs support a limited set of bitmanip. It's not all U74, so we use SOC for now...
|
||||
if (parser.FirmwareMatchesCompatible("starfive,jh7110")) {
|
||||
RiscV_Zba = true;
|
||||
RiscV_Zbb = true;
|
||||
|
@ -208,7 +208,6 @@ void CPUInfo::Detect()
|
|||
RiscV_D = ExtensionSupported(hwcap, 'D');
|
||||
RiscV_C = ExtensionSupported(hwcap, 'C');
|
||||
RiscV_V = ExtensionSupported(hwcap, 'V');
|
||||
RiscV_B = ExtensionSupported(hwcap, 'B');
|
||||
// We assume as in RVA20U64 that F means Zicsr is available.
|
||||
RiscV_Zicsr = RiscV_F;
|
||||
|
||||
|
@ -221,7 +220,8 @@ void CPUInfo::Detect()
|
|||
RiscV_D = info.features.D;
|
||||
RiscV_C = info.features.C;
|
||||
RiscV_V = info.features.V;
|
||||
RiscV_Zicsr = info.features.Zicsr;
|
||||
// Seems to be wrong sometimes, assume we have it if we have F.
|
||||
RiscV_Zicsr = info.features.Zicsr || info.features.F;
|
||||
|
||||
truncate_cpy(brand_string, info.uarch);
|
||||
#endif
|
||||
|
@ -241,11 +241,10 @@ std::vector<std::string> CPUInfo::Features() {
|
|||
{ RiscV_D, "Double" },
|
||||
{ RiscV_C, "Compressed" },
|
||||
{ RiscV_V, "Vector" },
|
||||
{ RiscV_B, "Bitmanip" },
|
||||
{ RiscV_Zba, "Zba" },
|
||||
{ RiscV_Zbb, "Zbb" },
|
||||
{ RiscV_Zbc, "Zbc" },
|
||||
{ RiscV_Zbs, "Zbs" },
|
||||
{ RiscV_Zba, "Bitmanip Zba" },
|
||||
{ RiscV_Zbb, "Bitmanip Zbb" },
|
||||
{ RiscV_Zbc, "Bitmanip Zbc" },
|
||||
{ RiscV_Zbs, "Bitmanip Zbs" },
|
||||
{ RiscV_Zicsr, "Zicsr" },
|
||||
{ CPU64bit, "64-bit" },
|
||||
};
|
||||
|
|
|
@ -62,10 +62,10 @@ static inline bool SupportsVector() {
|
|||
|
||||
static inline bool SupportsBitmanip(char zbx) {
|
||||
switch (zbx) {
|
||||
case 'a': return cpu_info.RiscV_B || cpu_info.RiscV_Zba;
|
||||
case 'b': return cpu_info.RiscV_B || cpu_info.RiscV_Zbb;
|
||||
case 'c': return cpu_info.RiscV_B || cpu_info.RiscV_Zbc;
|
||||
case 's': return cpu_info.RiscV_B || cpu_info.RiscV_Zbs;
|
||||
case 'a': return cpu_info.RiscV_Zba;
|
||||
case 'b': return cpu_info.RiscV_Zbb;
|
||||
case 'c': return cpu_info.RiscV_Zbc;
|
||||
case 's': return cpu_info.RiscV_Zbs;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
@ -1180,6 +1180,19 @@ bool RiscVEmitter::CJInRange(const void *src, const void *dst) const {
|
|||
return BJInRange(src, dst, 12);
|
||||
}
|
||||
|
||||
void RiscVEmitter::QuickJAL(RiscVReg scratchreg, RiscVReg rd, const u8 *dst) {
|
||||
if (!JInRange(GetCodePointer(), dst)) {
|
||||
static_assert(sizeof(intptr_t) <= sizeof(int64_t));
|
||||
int64_t pcdelta = (int64_t)dst - (int64_t)GetCodePointer();
|
||||
int32_t lower = (int32_t)SignReduce64(pcdelta, 12);
|
||||
uintptr_t upper = ((pcdelta - lower) >> 12) << 12;
|
||||
LI(scratchreg, (uintptr_t)GetCodePointer() + upper);
|
||||
JALR(rd, scratchreg, lower);
|
||||
} else {
|
||||
JAL(rd, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVEmitter::SetRegToImmediate(RiscVReg rd, uint64_t value, RiscVReg temp) {
|
||||
int64_t svalue = (int64_t)value;
|
||||
_assert_msg_(IsGPR(rd) && IsGPR(temp), "SetRegToImmediate only supports GPRs");
|
||||
|
@ -1194,13 +1207,24 @@ void RiscVEmitter::SetRegToImmediate(RiscVReg rd, uint64_t value, RiscVReg temp)
|
|||
|
||||
auto useUpper = [&](int64_t v, void (RiscVEmitter::*upperOp)(RiscVReg, s32), bool force = false) {
|
||||
if (SignReduce64(v, 32) == v || force) {
|
||||
int32_t lower = (int32_t)SignReduce64(svalue, 12);
|
||||
int32_t lower = (int32_t)SignReduce64(v, 12);
|
||||
int32_t upper = ((v - lower) >> 12) << 12;
|
||||
_assert_msg_(force || (int64_t)upper + lower == v, "Upper + ADDI immediate math mistake?");
|
||||
bool clearUpper = v >= 0 && upper < 0;
|
||||
if (clearUpper) {
|
||||
_assert_msg_(BitsSupported() >= 64, "Shouldn't be possible on 32-bit");
|
||||
_assert_msg_(force || (((int64_t)upper + lower) & 0xFFFFFFFF) == v, "Upper + ADDI immediate math mistake?");
|
||||
// This isn't safe to do using AUIPC. We can't have the high bit set this way.
|
||||
if (upperOp == &RiscVEmitter::AUIPC)
|
||||
return false;
|
||||
} else {
|
||||
_assert_msg_(force || (int64_t)upper + lower == v, "Upper + ADDI immediate math mistake?");
|
||||
}
|
||||
|
||||
// Should be fused on some processors.
|
||||
(this->*upperOp)(rd, upper);
|
||||
if (lower != 0)
|
||||
if (clearUpper)
|
||||
ADDIW(rd, rd, lower);
|
||||
else if (lower != 0)
|
||||
ADDI(rd, rd, lower);
|
||||
return true;
|
||||
}
|
||||
|
@ -2124,6 +2148,7 @@ void RiscVEmitter::FCVT(FConv to, FConv from, RiscVReg rd, RiscVReg rs1, Round r
|
|||
_assert_msg_(rm == Round::DYNAMIC || rm == Round::NEAREST_EVEN, "Invalid rounding mode for widening FCVT");
|
||||
rm = Round::NEAREST_EVEN;
|
||||
}
|
||||
_assert_msg_(fromFmt != toFmt, "FCVT cannot convert to same float type");
|
||||
Write32(EncodeR(Opcode32::OP_FP, rd, (Funct3)rm, rs1, (RiscVReg)fromFmt, toFmt, Funct5::FCVT_SZ));
|
||||
} else {
|
||||
Funct5 funct5 = FConvToIntegerBits(to) == 0 ? Funct5::FCVT_FROMX : Funct5::FCVT_TOX;
|
||||
|
|
|
@ -213,6 +213,19 @@ public:
|
|||
bool BInRange(const void *func) const;
|
||||
bool JInRange(const void *func) const;
|
||||
|
||||
void QuickJAL(RiscVReg scratchreg, RiscVReg rd, const u8 *dst);
|
||||
void QuickJ(RiscVReg scratchreg, const u8 *dst) {
|
||||
QuickJAL(scratchreg, R_ZERO, dst);
|
||||
}
|
||||
void QuickCallFunction(const u8 *func) {
|
||||
QuickJAL(R_RA, R_RA, func);
|
||||
}
|
||||
template <typename T>
|
||||
void QuickCallFunction(T *func) {
|
||||
static_assert(std::is_function<T>::value, "QuickCallFunction without function");
|
||||
QuickCallFunction((const u8 *)func);
|
||||
}
|
||||
|
||||
void LUI(RiscVReg rd, s32 simm32);
|
||||
void AUIPC(RiscVReg rd, s32 simm32);
|
||||
|
||||
|
|
|
@ -128,25 +128,36 @@ void OnScreenDisplay::ShowAchievementUnlocked(int achievementID) {
|
|||
entries_.insert(entries_.begin(), msg);
|
||||
}
|
||||
|
||||
void OnScreenDisplay::ShowAchievementProgress(int achievementID, float duration_s) {
|
||||
void OnScreenDisplay::ShowAchievementProgress(int achievementID, bool show) {
|
||||
double now = time_now_d();
|
||||
|
||||
// There can only be one of these at a time.
|
||||
for (auto &entry : sideEntries_) {
|
||||
if (entry.numericID == achievementID && entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
|
||||
// Duplicate, let's just bump the timer.
|
||||
entry.startTime = now;
|
||||
entry.endTime = now + (double)duration_s;
|
||||
// We're done.
|
||||
if (entry.type == OSDType::ACHIEVEMENT_PROGRESS) {
|
||||
if (!show) {
|
||||
// Hide and eventually delete it.
|
||||
entry.endTime = now + (double)FadeoutTime();
|
||||
// Found it, we're done.
|
||||
return;
|
||||
}
|
||||
// Else update it.
|
||||
entry.numericID = achievementID;
|
||||
entry.endTime = now + forever_s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!show) {
|
||||
// Sanity check
|
||||
return;
|
||||
}
|
||||
|
||||
// OK, let's make a new side-entry.
|
||||
Entry entry;
|
||||
entry.numericID = achievementID;
|
||||
entry.type = OSDType::ACHIEVEMENT_PROGRESS;
|
||||
entry.startTime = now;
|
||||
entry.endTime = now + (double)duration_s;
|
||||
entry.endTime = now + forever_s;
|
||||
sideEntries_.insert(sideEntries_.begin(), entry);
|
||||
}
|
||||
|
||||
|
@ -215,10 +226,15 @@ void OnScreenDisplay::ShowOnOff(const std::string &message, bool on, float durat
|
|||
Show(OSDType::MESSAGE_INFO, message + ": " + (on ? "on" : "off"), duration_s);
|
||||
}
|
||||
|
||||
void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay) {
|
||||
_dbg_assert_(!my_isnanorinf(progress));
|
||||
_dbg_assert_(!my_isnanorinf(minValue));
|
||||
_dbg_assert_(!my_isnanorinf(maxValue));
|
||||
|
||||
double now = time_now_d();
|
||||
bool found = false;
|
||||
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
for (auto &bar : bars_) {
|
||||
if (bar.id == id) {
|
||||
bar.minValue = minValue;
|
||||
|
@ -236,16 +252,27 @@ void OnScreenDisplay::SetProgressBar(std::string id, std::string &&message, int
|
|||
bar.minValue = minValue;
|
||||
bar.maxValue = maxValue;
|
||||
bar.progress = progress;
|
||||
bar.startTime = now + delay;
|
||||
bar.endTime = now + 60.0; // Show the progress bar for 60 seconds, then fade it out.
|
||||
bars_.push_back(bar);
|
||||
}
|
||||
|
||||
void OnScreenDisplay::RemoveProgressBar(std::string id) {
|
||||
void OnScreenDisplay::RemoveProgressBar(std::string id, bool success, float delay_s) {
|
||||
std::lock_guard<std::mutex> guard(mutex_);
|
||||
for (auto iter = bars_.begin(); iter != bars_.end(); iter++) {
|
||||
if (iter->id == id) {
|
||||
iter->progress = iter->maxValue;
|
||||
iter->endTime = time_now_d() + FadeoutTime();
|
||||
if (success) {
|
||||
// Quickly shoot up to max, if we weren't there.
|
||||
if (iter->maxValue != 0.0f) {
|
||||
iter->progress = iter->maxValue;
|
||||
} else {
|
||||
// Fake a full progress
|
||||
iter->minValue = 0;
|
||||
iter->maxValue = 1;
|
||||
iter->progress = 1;
|
||||
}
|
||||
}
|
||||
iter->endTime = time_now_d() + delay_s + FadeoutTime();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ enum class OSDType {
|
|||
// Side entries
|
||||
ACHIEVEMENT_PROGRESS, // Achievement icon + "measured_progress" text, auto-hide after 2s
|
||||
ACHIEVEMENT_CHALLENGE_INDICATOR, // Achievement icon ONLY, no auto-hide
|
||||
|
||||
LEADERBOARD_TRACKER,
|
||||
};
|
||||
|
||||
|
@ -43,15 +44,15 @@ public:
|
|||
|
||||
// Specialized achievement-related types. These go to the side notifications, not the top-middle.
|
||||
void ShowAchievementUnlocked(int achievementID);
|
||||
void ShowAchievementProgress(int achievementID, float duration_s);
|
||||
void ShowAchievementProgress(int achievementID, bool show); // call with show=false to hide. There can only be one of these. When hiding it's ok to not pass a valid achievementID.
|
||||
void ShowChallengeIndicator(int achievementID, bool show); // call with show=false to hide.
|
||||
void ShowLeaderboardTracker(int leaderboardTrackerID, const char *trackerText, bool show); // show=true is used both for create and update.
|
||||
|
||||
// Progress bar controls
|
||||
// Set is both create and update. If you set maxValue <= minValue, you'll create an "indeterminate" progress
|
||||
// bar that doesn't show a specific amount of progress.
|
||||
void SetProgressBar(std::string id, std::string &&message, int minValue, int maxValue, int progress);
|
||||
void RemoveProgressBar(std::string id);
|
||||
void SetProgressBar(std::string id, std::string &&message, float minValue, float maxValue, float progress, float delay_s);
|
||||
void RemoveProgressBar(std::string id, bool success, float delay_s);
|
||||
|
||||
// Call every frame to keep the sidebar visible. Otherwise it'll fade out.
|
||||
void NudgeSidebar();
|
||||
|
@ -74,9 +75,10 @@ public:
|
|||
struct ProgressBar {
|
||||
std::string id;
|
||||
std::string message;
|
||||
int minValue;
|
||||
int maxValue;
|
||||
int progress;
|
||||
float minValue;
|
||||
float maxValue;
|
||||
float progress;
|
||||
double startTime;
|
||||
double endTime;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include "ppsspp_config.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "Common/System/Request.h"
|
||||
|
@ -6,6 +8,17 @@
|
|||
#include "Common/File/Path.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
|
||||
// Maybe not the most natural place for this, but not sure what would be. It needs to be in the Common project
|
||||
// unless we want to make another System_ function to retrieve it.
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
JavaVM *gJvm = nullptr;
|
||||
|
||||
#endif
|
||||
|
||||
RequestManager g_requestManager;
|
||||
|
||||
const char *RequestTypeAsString(SystemRequestType type) {
|
||||
|
|
|
@ -117,6 +117,7 @@ enum SystemProperty {
|
|||
SYSPROP_BOARDNAME,
|
||||
SYSPROP_CLIPBOARD_TEXT,
|
||||
SYSPROP_GPUDRIVER_VERSION,
|
||||
SYSPROP_BUILD_VERSION,
|
||||
|
||||
// Separate SD cards or similar.
|
||||
// Need hacky solutions to get at this.
|
||||
|
@ -135,6 +136,8 @@ enum SystemProperty {
|
|||
|
||||
SYSPROP_CAN_CREATE_SHORTCUT,
|
||||
|
||||
SYSPROP_SUPPORTS_HTTPS,
|
||||
|
||||
// Available as Int:
|
||||
SYSPROP_SYSTEMVERSION,
|
||||
SYSPROP_DISPLAY_XRES,
|
||||
|
|
|
@ -239,6 +239,7 @@ bool IconCache::InsertIcon(const std::string &key, IconFormat format, std::strin
|
|||
}
|
||||
|
||||
if (data.empty()) {
|
||||
_dbg_assert_(false);
|
||||
ERROR_LOG(G3D, "Can't insert empty data into icon cache");
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,9 @@ class MessagePopupScreen : public PopupScreen {
|
|||
public:
|
||||
MessagePopupScreen(std::string title, std::string message, std::string button1, std::string button2, std::function<void(bool)> callback)
|
||||
: PopupScreen(title, button1, button2), message_(message), callback_(callback) {}
|
||||
|
||||
const char *tag() const override { return "MessagePopupScreen"; }
|
||||
|
||||
UI::Event OnChoice;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <algorithm>
|
||||
#include "Common/System/Display.h"
|
||||
#include "Common/System/System.h"
|
||||
#include "Common/Input/InputState.h"
|
||||
#include "Common/Input/KeyCodes.h"
|
||||
#include "Common/Math/curves.h"
|
||||
|
@ -9,7 +10,6 @@
|
|||
#include "Common/UI/Root.h"
|
||||
#include "Common/Data/Text/I18n.h"
|
||||
#include "Common/Render/DrawBuffer.h"
|
||||
|
||||
#include "Common/Log.h"
|
||||
|
||||
static const bool ClickDebug = false;
|
||||
|
@ -426,7 +426,6 @@ void PopupScreen::CreateViews() {
|
|||
|
||||
CreatePopupContents(box_);
|
||||
root_->SetDefaultFocusView(box_);
|
||||
|
||||
if (ShowButtons() && !button1_.empty()) {
|
||||
// And the two buttons at the bottom.
|
||||
LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(200, WRAP_CONTENT));
|
||||
|
|
|
@ -607,6 +607,7 @@ CollapsibleHeader::CollapsibleHeader(bool *toggle, const std::string &text, Layo
|
|||
|
||||
void CollapsibleHeader::Draw(UIContext &dc) {
|
||||
Style style = dc.theme->itemStyle;
|
||||
style.background.color = 0;
|
||||
if (HasFocus()) style = dc.theme->itemFocusedStyle;
|
||||
if (down_) style = dc.theme->itemDownStyle;
|
||||
if (!IsEnabled()) style = dc.theme->itemDisabledStyle;
|
||||
|
@ -618,7 +619,9 @@ void CollapsibleHeader::Draw(UIContext &dc) {
|
|||
dc.SetFontStyle(dc.theme->uiFontSmall);
|
||||
dc.DrawText(text_.c_str(), bounds_.x + 4 + xoff, bounds_.centerY(), dc.theme->headerStyle.fgColor, ALIGN_LEFT | ALIGN_VCENTER);
|
||||
dc.Draw()->DrawImageCenterTexel(dc.theme->whiteImage, bounds_.x, bounds_.y2() - 2, bounds_.x2(), bounds_.y2(), dc.theme->headerStyle.fgColor);
|
||||
dc.Draw()->DrawImageRotated(ImageID("I_ARROW"), bounds_.x + 20.0f, bounds_.y + 20.0f, 1.0f, *toggle_ ? -M_PI / 2 : M_PI);
|
||||
if (hasSubItems_) {
|
||||
dc.Draw()->DrawImageRotated(ImageID("I_ARROW"), bounds_.x + 20.0f, bounds_.y + 20.0f, 1.0f, *toggle_ ? -M_PI / 2 : M_PI);
|
||||
}
|
||||
}
|
||||
|
||||
void CollapsibleHeader::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const {
|
||||
|
|
|
@ -870,6 +870,10 @@ public:
|
|||
void Draw(UIContext &dc) override;
|
||||
void GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const override;
|
||||
void GetContentDimensions(const UIContext &dc, float &w, float &h) const override;
|
||||
|
||||
void SetHasSubitems(bool hasSubItems) { hasSubItems_ = hasSubItems; }
|
||||
private:
|
||||
bool hasSubItems_ = true;
|
||||
};
|
||||
|
||||
class BitCheckBox : public CheckBox {
|
||||
|
|
|
@ -1183,9 +1183,9 @@ StickyChoice *ChoiceStrip::Choice(int index) {
|
|||
CollapsibleSection::CollapsibleSection(const std::string &title, LayoutParams *layoutParams) : LinearLayout(ORIENT_VERTICAL, layoutParams) {
|
||||
SetSpacing(0.0f);
|
||||
|
||||
CollapsibleHeader *heading = new CollapsibleHeader(&open_, title);
|
||||
views_.push_back(heading);
|
||||
heading->OnClick.Add([=](UI::EventParams &) {
|
||||
heading_ = new CollapsibleHeader(&open_, title);
|
||||
views_.push_back(heading_);
|
||||
heading_->OnClick.Add([=](UI::EventParams &) {
|
||||
// Change the visibility of all children except the first one.
|
||||
// Later maybe try something more ambitious.
|
||||
for (size_t i = 1; i < views_.size(); i++) {
|
||||
|
@ -1195,4 +1195,9 @@ CollapsibleSection::CollapsibleSection(const std::string &title, LayoutParams *l
|
|||
});
|
||||
}
|
||||
|
||||
void CollapsibleSection::Update() {
|
||||
ViewGroup::Update();
|
||||
heading_->SetHasSubitems(views_.size() > 1);
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
|
|
|
@ -324,12 +324,17 @@ private:
|
|||
std::vector<AnchorTranslateTween *> tabTweens_;
|
||||
};
|
||||
|
||||
class CollapsibleHeader;
|
||||
|
||||
class CollapsibleSection : public LinearLayout {
|
||||
public:
|
||||
CollapsibleSection(const std::string &title, LayoutParams *layoutParams = nullptr);
|
||||
|
||||
void Update() override;
|
||||
|
||||
private:
|
||||
bool open_ = true;
|
||||
CollapsibleHeader *heading_;
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
|
|
|
@ -907,7 +907,7 @@ void UpdateVRViewMatrices() {
|
|||
XrQuaternionf_ToMatrix4f(&invView.orientation, M);
|
||||
|
||||
// Apply 6Dof head movement
|
||||
if (!flatScreen && g_Config.bEnable6DoF && !g_Config.bHeadRotationEnabled && (g_Config.iCameraPitch == 0)) {
|
||||
if (g_Config.bEnable6DoF && !g_Config.bHeadRotationEnabled && (g_Config.iCameraPitch == 0)) {
|
||||
M[3] -= vrView[0].pose.position.x * (vrMirroring[VR_MIRRORING_AXIS_X] ? -1.0f : 1.0f) * scale;
|
||||
M[7] -= vrView[0].pose.position.y * (vrMirroring[VR_MIRRORING_AXIS_Y] ? -1.0f : 1.0f) * scale;
|
||||
M[11] -= vrView[0].pose.position.z * (vrMirroring[VR_MIRRORING_AXIS_Z] ? -1.0f : 1.0f) * scale;
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
#include "GPU/Common/FramebufferManagerCommon.h"
|
||||
|
||||
// TODO: Find a better place for this.
|
||||
http::Downloader g_DownloadManager;
|
||||
http::RequestManager g_DownloadManager;
|
||||
|
||||
Config g_Config;
|
||||
|
||||
|
@ -129,7 +129,7 @@ std::string CreateRandMAC() {
|
|||
}
|
||||
|
||||
static int DefaultCpuCore() {
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64)
|
||||
if (System_GetPropertyBool(SYSPROP_CAN_JIT))
|
||||
return (int)CPUCore::JIT;
|
||||
return (int)CPUCore::IR_JIT;
|
||||
|
@ -139,7 +139,7 @@ static int DefaultCpuCore() {
|
|||
}
|
||||
|
||||
static bool DefaultCodeGen() {
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
|
||||
#if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(RISCV64)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
|
@ -611,6 +611,8 @@ static const ConfigSetting graphicsSettings[] = {
|
|||
ConfigSetting("InflightFrames", &g_Config.iInflightFrames, 3, CfgFlag::DEFAULT),
|
||||
ConfigSetting("RenderDuplicateFrames", &g_Config.bRenderDuplicateFrames, false, CfgFlag::PER_GAME),
|
||||
|
||||
ConfigSetting("MultiThreading", &g_Config.bRenderMultiThreading, true, CfgFlag::DEFAULT),
|
||||
|
||||
ConfigSetting("ShaderCache", &g_Config.bShaderCache, true, CfgFlag::DONT_SAVE), // Doesn't save. Ini-only.
|
||||
ConfigSetting("GpuLogProfiler", &g_Config.bGpuLogProfiler, false, CfgFlag::DEFAULT),
|
||||
};
|
||||
|
@ -773,6 +775,7 @@ static const ConfigSetting controlSettings[] = {
|
|||
ConfigSetting("MouseSmoothing", &g_Config.fMouseSmoothing, 0.9f, CfgFlag::PER_GAME),
|
||||
|
||||
ConfigSetting("SystemControls", &g_Config.bSystemControls, true, CfgFlag::DEFAULT),
|
||||
ConfigSetting("RapidFileInterval", &g_Config.iRapidFireInterval, 5, CfgFlag::DEFAULT),
|
||||
};
|
||||
|
||||
static const ConfigSetting networkSettings[] = {
|
||||
|
@ -796,25 +799,12 @@ static const ConfigSetting networkSettings[] = {
|
|||
ConfigSetting("QuickChat5", &g_Config.sQuickChat4, "Quick Chat 5", CfgFlag::PER_GAME),
|
||||
};
|
||||
|
||||
static int DefaultSystemParamLanguage() {
|
||||
int defaultLang = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
|
||||
if (g_Config.bFirstRun) {
|
||||
// TODO: Be smart about same language, different country
|
||||
auto &langValuesMapping = g_Config.GetLangValuesMapping();
|
||||
auto iter = langValuesMapping.find(g_Config.sLanguageIni);
|
||||
if (iter != langValuesMapping.end()) {
|
||||
defaultLang = iter->second.second;
|
||||
}
|
||||
}
|
||||
return defaultLang;
|
||||
}
|
||||
|
||||
static const ConfigSetting systemParamSettings[] = {
|
||||
ConfigSetting("PSPModel", &g_Config.iPSPModel, PSP_MODEL_SLIM, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("PSPFirmwareVersion", &g_Config.iFirmwareVersion, PSP_DEFAULT_FIRMWARE, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("NickName", &g_Config.sNickName, "PPSSPP", CfgFlag::PER_GAME),
|
||||
ConfigSetting("MacAddress", &g_Config.sMACAddress, "", CfgFlag::PER_GAME),
|
||||
ConfigSetting("Language", &g_Config.iLanguage, &DefaultSystemParamLanguage, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("GameLanguage", &g_Config.iLanguage, -1, CfgFlag::PER_GAME | CfgFlag::REPORT),
|
||||
ConfigSetting("ParamTimeFormat", &g_Config.iTimeFormat, PSP_SYSTEMPARAM_TIME_FORMAT_24HR, CfgFlag::PER_GAME),
|
||||
ConfigSetting("ParamDateFormat", &g_Config.iDateFormat, PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD, CfgFlag::PER_GAME),
|
||||
ConfigSetting("TimeZone", &g_Config.iTimeZone, 0, CfgFlag::PER_GAME),
|
||||
|
@ -1174,8 +1164,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) {
|
|||
if (iRunCount % 10 == 0 && bCheckForNewVersion) {
|
||||
const char *versionUrl = "http://www.ppsspp.org/version.json";
|
||||
const char *acceptMime = "application/json, text/*; q=0.9, */*; q=0.8";
|
||||
auto dl = g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), &DownloadCompletedCallback, acceptMime);
|
||||
dl->SetHidden(true);
|
||||
g_DownloadManager.StartDownloadWithCallback(versionUrl, Path(), http::ProgressBarMode::NONE, &DownloadCompletedCallback, "version", acceptMime);
|
||||
}
|
||||
|
||||
INFO_LOG(LOADER, "Loading controller config: %s", controllerIniFilename_.c_str());
|
||||
|
@ -1364,7 +1353,7 @@ void Config::NotifyUpdatedCpuCore() {
|
|||
#define PPSSPP_GIT_VERSION "v0.0.1-gaaaaaaaaa"
|
||||
#endif
|
||||
|
||||
void Config::DownloadCompletedCallback(http::Download &download) {
|
||||
void Config::DownloadCompletedCallback(http::Request &download) {
|
||||
if (download.ResultCode() != 200) {
|
||||
ERROR_LOG(LOADER, "Failed to download %s: %d", download.url().c_str(), download.ResultCode());
|
||||
return;
|
||||
|
@ -1772,3 +1761,18 @@ void Config::GetReportingInfo(UrlEncoder &data) {
|
|||
bool Config::IsPortrait() const {
|
||||
return (iInternalScreenRotation == ROTATION_LOCKED_VERTICAL || iInternalScreenRotation == ROTATION_LOCKED_VERTICAL180) && !bSkipBufferEffects;
|
||||
}
|
||||
|
||||
int Config::GetPSPLanguage() {
|
||||
if (g_Config.iLanguage == -1) {
|
||||
const auto &langValuesMapping = GetLangValuesMapping();
|
||||
auto iter = langValuesMapping.find(g_Config.sLanguageIni);
|
||||
if (iter != langValuesMapping.end()) {
|
||||
return iter->second.second;
|
||||
} else {
|
||||
// Fallback to English
|
||||
return PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
|
||||
}
|
||||
} else {
|
||||
return g_Config.iLanguage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,8 +41,8 @@ enum ChatPositions {
|
|||
};
|
||||
|
||||
namespace http {
|
||||
class Download;
|
||||
class Downloader;
|
||||
class Request;
|
||||
class RequestManager;
|
||||
}
|
||||
|
||||
struct UrlEncoder;
|
||||
|
@ -238,6 +238,7 @@ public:
|
|||
bool bGfxDebugOutput;
|
||||
int iInflightFrames;
|
||||
bool bRenderDuplicateFrames;
|
||||
bool bRenderMultiThreading;
|
||||
|
||||
// Sound
|
||||
bool bEnableSound;
|
||||
|
@ -386,6 +387,7 @@ public:
|
|||
float fMouseSmoothing;
|
||||
|
||||
bool bSystemControls;
|
||||
int iRapidFireInterval;
|
||||
|
||||
// Use the hardware scaler to scale up the image to save fillrate. Similar to Windows' window size, really.
|
||||
int iAndroidHwScale; // 0 = device resolution. 1 = 480x272 (extended to correct aspect), 2 = 960x544 etc.
|
||||
|
@ -543,7 +545,7 @@ public:
|
|||
void RemoveRecent(const std::string &file);
|
||||
void CleanRecent();
|
||||
|
||||
static void DownloadCompletedCallback(http::Download &download);
|
||||
static void DownloadCompletedCallback(http::Request &download);
|
||||
void DismissUpgrade();
|
||||
|
||||
void ResetControlLayout();
|
||||
|
@ -569,6 +571,10 @@ public:
|
|||
void SetAppendedConfigIni(const Path &path);
|
||||
void UpdateAfterSettingAutoFrameSkip();
|
||||
void NotifyUpdatedCpuCore();
|
||||
|
||||
// Applies the Auto setting if set. Returns an enum value from PSP_SYSTEMPARAM_LANGUAGE_*.
|
||||
int GetPSPLanguage();
|
||||
|
||||
protected:
|
||||
void LoadStandardControllerIni();
|
||||
void LoadLangValuesMapping();
|
||||
|
@ -595,6 +601,6 @@ private:
|
|||
std::string CreateRandMAC();
|
||||
|
||||
// TODO: Find a better place for this.
|
||||
extern http::Downloader g_DownloadManager;
|
||||
extern http::RequestManager g_DownloadManager;
|
||||
extern Config g_Config;
|
||||
|
||||
|
|
|
@ -580,6 +580,7 @@
|
|||
<ClCompile Include="KeyMapDefaults.cpp" />
|
||||
<ClCompile Include="MemFault.cpp" />
|
||||
<ClCompile Include="MIPS\fake\FakeJit.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRAnalysis.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRAsm.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRCompALU.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRCompBranch.cpp" />
|
||||
|
@ -593,6 +594,16 @@
|
|||
<ClCompile Include="MIPS\IR\IRPassSimplify.cpp" />
|
||||
<ClCompile Include="MIPS\IR\IRRegCache.cpp" />
|
||||
<ClCompile Include="MIPS\MIPSVFPUFallbacks.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVAsm.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompALU.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompBranch.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompSystem.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompFPU.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompLoadStore.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompVec.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVJit.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCache.cpp" />
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCacheFPU.cpp" />
|
||||
<ClCompile Include="Replay.cpp" />
|
||||
<ClCompile Include="Compatibility.cpp" />
|
||||
<ClCompile Include="Config.cpp" />
|
||||
|
@ -1154,6 +1165,7 @@
|
|||
<ClInclude Include="KeyMapDefaults.h" />
|
||||
<ClInclude Include="MemFault.h" />
|
||||
<ClInclude Include="MIPS\fake\FakeJit.h" />
|
||||
<ClInclude Include="MIPS\IR\IRAnalysis.h" />
|
||||
<ClInclude Include="MIPS\IR\IRFrontend.h" />
|
||||
<ClInclude Include="MIPS\IR\IRInst.h" />
|
||||
<ClInclude Include="MIPS\IR\IRInterpreter.h" />
|
||||
|
@ -1161,6 +1173,9 @@
|
|||
<ClInclude Include="MIPS\IR\IRPassSimplify.h" />
|
||||
<ClInclude Include="MIPS\IR\IRRegCache.h" />
|
||||
<ClInclude Include="MIPS\MIPSVFPUFallbacks.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVJit.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCache.h" />
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCacheFPU.h" />
|
||||
<ClInclude Include="Replay.h" />
|
||||
<ClInclude Include="Compatibility.h" />
|
||||
<ClInclude Include="Config.h" />
|
||||
|
|
|
@ -91,6 +91,9 @@
|
|||
<Filter Include="MIPS\fake">
|
||||
<UniqueIdentifier>{678fa299-0ff7-4983-982d-2da47b52e238}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="MIPS\RiscV">
|
||||
<UniqueIdentifier>{067e3128-3aaf-4ed1-b19e-bab11606abe7}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ELF\ElfReader.cpp">
|
||||
|
@ -1204,6 +1207,39 @@
|
|||
<ClCompile Include="RetroAchievements.cpp">
|
||||
<Filter>Core</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVJit.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVAsm.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCache.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVRegCacheFPU.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompFPU.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompLoadStore.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompVec.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompALU.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompBranch.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\RiscV\RiscVCompSystem.cpp">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MIPS\IR\IRAnalysis.cpp">
|
||||
<Filter>MIPS\IR</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ELF\ElfReader.h">
|
||||
|
@ -1950,6 +1986,18 @@
|
|||
<ClInclude Include="RetroAchievements.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\RiscV\RiscVJit.h">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCache.h">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\RiscV\RiscVRegCacheFPU.h">
|
||||
<Filter>MIPS\RiscV</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MIPS\IR\IRAnalysis.h">
|
||||
<Filter>MIPS\IR</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE.TXT" />
|
||||
|
|
|
@ -131,7 +131,7 @@ static void SetupDebuggerLock() {
|
|||
}
|
||||
}
|
||||
|
||||
void HandleDebuggerRequest(const http::Request &request) {
|
||||
void HandleDebuggerRequest(const http::ServerRequest &request) {
|
||||
net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org");
|
||||
if (!ws)
|
||||
return;
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
#pragma once
|
||||
|
||||
namespace http {
|
||||
class Request;
|
||||
class ServerRequest;
|
||||
}
|
||||
|
||||
void HandleDebuggerRequest(const http::Request &request);
|
||||
void HandleDebuggerRequest(const http::ServerRequest &request);
|
||||
// Note: blocks.
|
||||
void StopAllDebuggers();
|
||||
|
|
|
@ -42,7 +42,7 @@ PSPDialog::~PSPDialog() {
|
|||
void PSPDialog::InitCommon() {
|
||||
UpdateCommon();
|
||||
|
||||
if (GetCommonParam() && GetCommonParam()->language != g_Config.iLanguage) {
|
||||
if (GetCommonParam() && GetCommonParam()->language != g_Config.GetPSPLanguage()) {
|
||||
WARN_LOG(SCEUTILITY, "Game requested language %d, ignoring and using user language", GetCommonParam()->language);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ private:
|
|||
s64 filepos_ = 0;
|
||||
Url url_;
|
||||
http::Client client_;
|
||||
http::RequestProgress progress_;
|
||||
net::RequestProgress progress_;
|
||||
::Path filename_;
|
||||
bool connected_ = false;
|
||||
bool cancel_ = false;
|
||||
|
|
|
@ -540,6 +540,9 @@ void HLEReturnFromMipsCall() {
|
|||
const static u32 deadbeefRegs[12] = {0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF, 0xDEADBEEF};
|
||||
inline static void SetDeadbeefRegs()
|
||||
{
|
||||
// Not exactly the same, but any time a syscall happens, it should clear ll.
|
||||
currentMIPS->llBit = 0;
|
||||
|
||||
if (g_Config.bSkipDeadbeefFilling)
|
||||
return;
|
||||
|
||||
|
|
|
@ -103,6 +103,8 @@ static u8 vibrationRightDropout = 160;
|
|||
// Not related to sceCtrl*RapidFire(), although it may do the same thing.
|
||||
static bool emuRapidFire = false;
|
||||
static u32 emuRapidFireFrames = 0;
|
||||
static bool emuRapidFireToggle = true;
|
||||
static u32 emuRapidFireInterval = 5;
|
||||
|
||||
// These buttons are not affected by rapid fire (neither is analog.)
|
||||
const u32 CTRL_EMU_RAPIDFIRE_MASK = CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT;
|
||||
|
@ -113,7 +115,7 @@ static void __CtrlUpdateLatch()
|
|||
u64 t = CoreTiming::GetGlobalTimeUs();
|
||||
|
||||
u32 buttons = ctrlCurrent.buttons;
|
||||
if (emuRapidFire && (emuRapidFireFrames % 10) < 5)
|
||||
if (emuRapidFire && emuRapidFireToggle)
|
||||
buttons &= CTRL_EMU_RAPIDFIRE_MASK;
|
||||
|
||||
ReplayApplyCtrl(buttons, ctrlCurrent.analog, t);
|
||||
|
@ -173,6 +175,19 @@ u32 __CtrlPeekButtons()
|
|||
return ctrlCurrent.buttons;
|
||||
}
|
||||
|
||||
u32 __CtrlPeekButtonsVisual()
|
||||
{
|
||||
u32 buttons;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||
buttons = ctrlCurrent.buttons;
|
||||
}
|
||||
|
||||
if (emuRapidFire && emuRapidFireToggle)
|
||||
buttons &= CTRL_EMU_RAPIDFIRE_MASK;
|
||||
return buttons;
|
||||
}
|
||||
|
||||
void __CtrlPeekAnalog(int stick, float *x, float *y)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(ctrlMutex);
|
||||
|
@ -206,9 +221,11 @@ void __CtrlSetAnalogXY(int stick, float x, float y)
|
|||
ctrlCurrent.analog[stick][CTRL_ANALOG_Y] = scaledY;
|
||||
}
|
||||
|
||||
void __CtrlSetRapidFire(bool state)
|
||||
void __CtrlSetRapidFire(bool state, int interval)
|
||||
{
|
||||
emuRapidFire = state;
|
||||
emuRapidFireToggle = true;
|
||||
emuRapidFireInterval = interval;
|
||||
}
|
||||
|
||||
bool __CtrlGetRapidFire()
|
||||
|
@ -298,6 +315,10 @@ retry:
|
|||
static void __CtrlVblank()
|
||||
{
|
||||
emuRapidFireFrames++;
|
||||
if (emuRapidFireFrames >= emuRapidFireInterval) {
|
||||
emuRapidFireFrames = 0;
|
||||
emuRapidFireToggle = !emuRapidFireToggle;
|
||||
}
|
||||
|
||||
// Reduce gamepad Vibration by set % each frame
|
||||
leftVibration *= (float)vibrationLeftDropout / 256.0f;
|
||||
|
|
|
@ -73,11 +73,12 @@ void __CtrlUpdateButtons(u32 bitsToSet, u32 bitsToClear);
|
|||
void __CtrlSetAnalogXY(int stick, float x, float y);
|
||||
|
||||
// Call this to enable rapid-fire. This will cause buttons other than arrows to alternate.
|
||||
void __CtrlSetRapidFire(bool state);
|
||||
void __CtrlSetRapidFire(bool state, int interval);
|
||||
bool __CtrlGetRapidFire();
|
||||
|
||||
// For use by internal UI like MsgDialog
|
||||
u32 __CtrlPeekButtons();
|
||||
u32 __CtrlPeekButtonsVisual(); // also incorporates rapid-fire
|
||||
void __CtrlPeekAnalog(int stick, float *x, float *y);
|
||||
u32 __CtrlReadLatch();
|
||||
|
||||
|
|
|
@ -594,7 +594,7 @@ void __DisplayFlip(int cyclesLate) {
|
|||
#ifndef _DEBUG
|
||||
auto err = GetI18NCategory(I18NCat::ERRORS);
|
||||
if (g_Config.bSoftwareRendering) {
|
||||
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"));
|
||||
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"), 5.0f);
|
||||
} else {
|
||||
g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: try frameskip, sound is choppy when slow"));
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ static u32 backlightOffTime;
|
|||
|
||||
void __ImposeInit()
|
||||
{
|
||||
language = g_Config.iLanguage;
|
||||
language = g_Config.GetPSPLanguage();
|
||||
if (PSP_CoreParameter().compat.flags().EnglishOrJapaneseOnly) {
|
||||
if (language != PSP_SYSTEMPARAM_LANGUAGE_ENGLISH && language != PSP_SYSTEMPARAM_LANGUAGE_JAPANESE) {
|
||||
language = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
|
||||
|
@ -76,7 +76,7 @@ static u32 sceImposeGetBatteryIconStatus(u32 chargingPtr, u32 iconStatusPtr)
|
|||
static u32 sceImposeSetLanguageMode(u32 languageVal, u32 buttonVal) {
|
||||
language = languageVal;
|
||||
buttonValue = buttonVal;
|
||||
if (language != g_Config.iLanguage) {
|
||||
if (language != g_Config.GetPSPLanguage()) {
|
||||
return hleLogWarning(SCEUTILITY, 0, "ignoring requested language");
|
||||
}
|
||||
return hleLogSuccessI(SCEUTILITY, 0);
|
||||
|
|
|
@ -131,7 +131,8 @@ static int sceNpMatching2ContextStart(int ctxId)
|
|||
//npMatching2Ctx.started = true;
|
||||
Url url("http://static-resource.np.community.playstation.net/np/resource/psp-title/" + std::string(npTitleId.data) + "_00/matching/" + std::string(npTitleId.data) + "_00-matching.xml");
|
||||
http::Client client;
|
||||
http::RequestProgress progress;
|
||||
bool cancelled = false;
|
||||
net::RequestProgress progress(&cancelled);
|
||||
if (!client.Resolve(url.Host().c_str(), url.Port())) {
|
||||
return hleLogError(SCENET, SCE_NP_COMMUNITY_SERVER_ERROR_NO_SUCH_TITLE, "HTTP failed to resolve %s", url.Resource().c_str());
|
||||
}
|
||||
|
|
|
@ -898,7 +898,7 @@ static u32 sceUtilityGetSystemParamInt(u32 id, u32 destaddr)
|
|||
param = g_Config.bDayLightSavings?PSP_SYSTEMPARAM_DAYLIGHTSAVINGS_SAVING:PSP_SYSTEMPARAM_DAYLIGHTSAVINGS_STD;
|
||||
break;
|
||||
case PSP_SYSTEMPARAM_ID_INT_LANGUAGE:
|
||||
param = g_Config.iLanguage;
|
||||
param = g_Config.GetPSPLanguage();
|
||||
if (PSP_CoreParameter().compat.flags().EnglishOrJapaneseOnly) {
|
||||
if (param != PSP_SYSTEMPARAM_LANGUAGE_ENGLISH && param != PSP_SYSTEMPARAM_LANGUAGE_JAPANESE) {
|
||||
param = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
|
||||
|
|
|
@ -385,6 +385,12 @@ namespace MIPSComp
|
|||
}
|
||||
}
|
||||
|
||||
void ArmJit::Comp_StoreSync(MIPSOpcode op) {
|
||||
CONDITIONAL_DISABLE(LSU);
|
||||
|
||||
DISABLE;
|
||||
}
|
||||
|
||||
void ArmJit::Comp_Cache(MIPSOpcode op) {
|
||||
CONDITIONAL_DISABLE(LSU);
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ public:
|
|||
|
||||
// Ops
|
||||
void Comp_ITypeMem(MIPSOpcode op) override;
|
||||
void Comp_StoreSync(MIPSOpcode op) override;
|
||||
void Comp_Cache(MIPSOpcode op) override;
|
||||
|
||||
void Comp_RelBranch(MIPSOpcode op) override;
|
||||
|
|
|
@ -312,7 +312,7 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) {
|
|||
|
||||
// Leave this at the end, add more stuff above.
|
||||
if (enableDisasm) {
|
||||
std::vector<std::string> lines = DisassembleArm64(start, GetCodePtr() - start);
|
||||
std::vector<std::string> lines = DisassembleArm64(start, (int)(GetCodePtr() - start));
|
||||
for (auto s : lines) {
|
||||
INFO_LOG(JIT, "%s", s.c_str());
|
||||
}
|
||||
|
|
|
@ -292,7 +292,7 @@ void Arm64Jit::Comp_FPU2op(MIPSOpcode op) {
|
|||
fp.FMOV(fpr.R(fd), S0);
|
||||
} else {
|
||||
fp.FCMP(fpr.R(fs), fpr.R(fs));
|
||||
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_Z);
|
||||
fp.FCVTS(fpr.R(fd), fpr.R(fs), ROUND_N);
|
||||
FixupBranch skip_nan = B(CC_VC);
|
||||
MOVI2R(SCRATCH1, 0x7FFFFFFF);
|
||||
fp.FMOV(fpr.R(fd), SCRATCH1);
|
||||
|
|
|
@ -434,6 +434,12 @@ namespace MIPSComp {
|
|||
}
|
||||
}
|
||||
|
||||
void Arm64Jit::Comp_StoreSync(MIPSOpcode op) {
|
||||
CONDITIONAL_DISABLE(LSU);
|
||||
|
||||
DISABLE;
|
||||
}
|
||||
|
||||
void Arm64Jit::Comp_Cache(MIPSOpcode op) {
|
||||
CONDITIONAL_DISABLE(LSU);
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ public:
|
|||
|
||||
// Ops
|
||||
void Comp_ITypeMem(MIPSOpcode op) override;
|
||||
void Comp_StoreSync(MIPSOpcode op) override;
|
||||
void Comp_Cache(MIPSOpcode op) override;
|
||||
|
||||
void Comp_RelBranch(MIPSOpcode op) override;
|
||||
|
@ -213,7 +214,9 @@ private:
|
|||
|
||||
bool ReplaceJalTo(u32 dest);
|
||||
|
||||
// Clobbers SCRATCH2.
|
||||
void SaveStaticRegisters();
|
||||
// Clobbers SCRATCH2.
|
||||
void LoadStaticRegisters();
|
||||
|
||||
void WriteExit(u32 destination, int exit_num);
|
||||
|
|
|
@ -180,7 +180,7 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) {
|
|||
ar[reg].isDirty = (mapFlags & MAP_DIRTY) ? true : false;
|
||||
if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) {
|
||||
if (mipsReg == MIPS_REG_ZERO) {
|
||||
// If we get a request to load the zero register, at least we won't spend
|
||||
// If we get a request to map the zero register, at least we won't spend
|
||||
// time on a memory access...
|
||||
emit_->MOVI2R(reg, 0);
|
||||
|
||||
|
@ -777,7 +777,7 @@ void Arm64RegCache::FlushAll() {
|
|||
// Re-pointerify
|
||||
emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32);
|
||||
ar[allocs[i].ar].pointerified = true;
|
||||
} else {
|
||||
} else if (!allocs[i].pointerified) {
|
||||
// If this register got pointerified on the way, mark it as not, so that after save/reload (like in an interpreter fallback), it won't be regarded as such, as it simply won't be.
|
||||
ar[allocs[i].ar].pointerified = false;
|
||||
}
|
||||
|
|
|
@ -319,6 +319,7 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) {
|
|||
if (mr[r].reg == INVALID_REG) {
|
||||
ERROR_LOG(JIT, "FlushR: MipsReg had bad ArmReg");
|
||||
}
|
||||
FlushArmReg((ARM64Reg)(S0 + mr[r].reg));
|
||||
break;
|
||||
|
||||
case ML_MEM:
|
||||
|
@ -329,8 +330,6 @@ void Arm64RegCacheFPU::FlushR(MIPSReg r) {
|
|||
//BAD
|
||||
break;
|
||||
}
|
||||
mr[r].loc = ML_MEM;
|
||||
mr[r].reg = (int)INVALID_REG;
|
||||
}
|
||||
|
||||
Arm64Gen::ARM64Reg Arm64RegCacheFPU::ARM64RegForFlush(int r) {
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/ARM64/Arm64RegCache.h"
|
||||
#include "Core/MIPS/MIPSVFPUUtils.h"
|
||||
|
@ -165,7 +163,6 @@ private:
|
|||
MIPSComp::JitOptions *jo_;
|
||||
|
||||
int numARMFpuReg_;
|
||||
int qTime_;
|
||||
|
||||
enum {
|
||||
// On ARM64, each of the 32 registers are full 128-bit. No sharing of components!
|
||||
|
|
135
Core/MIPS/IR/IRAnalysis.cpp
Normal file
135
Core/MIPS/IR/IRAnalysis.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2016- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MIPS/IR/IRAnalysis.h"
|
||||
|
||||
static bool IRReadsFrom(const IRInst &inst, int reg, char type, bool directly = false) {
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
||||
if (m->types[1] == type && inst.src1 == reg) {
|
||||
return true;
|
||||
}
|
||||
if (m->types[2] == type && inst.src2 == reg) {
|
||||
return true;
|
||||
}
|
||||
if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == type && inst.src3 == reg) {
|
||||
return true;
|
||||
}
|
||||
if (!directly) {
|
||||
if (inst.op == IROp::Interpret || inst.op == IROp::CallReplacement || inst.op == IROp::Syscall || inst.op == IROp::Break)
|
||||
return true;
|
||||
if (inst.op == IROp::Breakpoint || inst.op == IROp::MemoryCheck)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRReadsFromFPR(const IRInst &inst, int reg, bool directly) {
|
||||
if (IRReadsFrom(inst, reg, 'F', directly))
|
||||
return true;
|
||||
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
||||
// We also need to check V and 2. Indirect reads already checked, don't check again.
|
||||
if (m->types[1] == 'V' && reg >= inst.src1 && reg < inst.src1 + 4)
|
||||
return true;
|
||||
if (m->types[1] == '2' && reg >= inst.src1 && reg < inst.src1 + 2)
|
||||
return true;
|
||||
if (m->types[2] == 'V' && reg >= inst.src2 && reg < inst.src2 + 4)
|
||||
return true;
|
||||
if (m->types[2] == '2' && reg >= inst.src2 && reg < inst.src2 + 2)
|
||||
return true;
|
||||
if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0) {
|
||||
if (m->types[0] == 'V' && reg >= inst.src3 && reg <= inst.src3 + 4)
|
||||
return true;
|
||||
if (m->types[0] == '2' && reg >= inst.src3 && reg <= inst.src3 + 2)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly) {
|
||||
return IRReadsFrom(inst, reg, 'G', directly);
|
||||
}
|
||||
|
||||
int IRDestGPR(const IRInst &inst) {
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
||||
if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G') {
|
||||
return inst.dest;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool IRWritesToGPR(const IRInst &inst, int reg) {
|
||||
return IRDestGPR(inst) == reg;
|
||||
}
|
||||
|
||||
bool IRWritesToFPR(const IRInst &inst, int reg) {
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
||||
// Doesn't write to anything.
|
||||
if ((m->flags & IRFLAG_SRC3) != 0)
|
||||
return false;
|
||||
|
||||
if (m->types[0] == 'F' && reg == inst.dest)
|
||||
return true;
|
||||
if (m->types[0] == 'V' && reg >= inst.dest && reg < inst.dest + 4)
|
||||
return true;
|
||||
if (m->types[0] == '2' && reg >= inst.dest && reg < inst.dest + 2)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
IRUsage IRNextGPRUsage(int gpr, const IRSituation &info) {
|
||||
// Exclude any "special" regs from this logic for now.
|
||||
if (gpr >= 32)
|
||||
return IRUsage::UNKNOWN;
|
||||
|
||||
int count = std::min(info.numInstructions - info.currentIndex, info.lookaheadCount);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const IRInst inst = info.instructions[info.currentIndex + i];
|
||||
if (IRReadsFromGPR(inst, gpr))
|
||||
return IRUsage::READ;
|
||||
// We say WRITE when the current instruction writes. It's not useful for spilling.
|
||||
if (IRDestGPR(inst) == gpr)
|
||||
return i == 0 ? IRUsage::WRITE : IRUsage::CLOBBERED;
|
||||
}
|
||||
|
||||
return IRUsage::UNUSED;
|
||||
}
|
||||
|
||||
IRUsage IRNextFPRUsage(int fpr, const IRSituation &info) {
|
||||
// Let's only pay attention to standard FP regs and temps.
|
||||
// See MIPS.h for these offsets.
|
||||
if (fpr < 0 || (fpr >= 160 && fpr < 192) || fpr >= 208)
|
||||
return IRUsage::UNKNOWN;
|
||||
|
||||
int count = std::min(info.numInstructions - info.currentIndex, info.lookaheadCount);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const IRInst inst = info.instructions[info.currentIndex + i];
|
||||
|
||||
if (IRReadsFromFPR(inst, fpr))
|
||||
return IRUsage::READ;
|
||||
// We say WRITE when the current instruction writes. It's not useful for spilling.
|
||||
if (IRWritesToFPR(inst, fpr)) {
|
||||
return i == 0 ? IRUsage::WRITE : IRUsage::CLOBBERED;
|
||||
}
|
||||
}
|
||||
|
||||
return IRUsage::UNUSED;
|
||||
}
|
44
Core/MIPS/IR/IRAnalysis.h
Normal file
44
Core/MIPS/IR/IRAnalysis.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) 2016- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
|
||||
bool IRReadsFromFPR(const IRInst &inst, int reg, bool directly = false);
|
||||
bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly = false);
|
||||
bool IRWritesToGPR(const IRInst &inst, int reg);
|
||||
bool IRWritesToFPR(const IRInst &inst, int reg);
|
||||
int IRDestGPR(const IRInst &inst);
|
||||
|
||||
struct IRSituation {
|
||||
int lookaheadCount;
|
||||
int currentIndex;
|
||||
const IRInst *instructions;
|
||||
int numInstructions;
|
||||
};
|
||||
|
||||
enum class IRUsage {
|
||||
UNKNOWN,
|
||||
UNUSED,
|
||||
READ,
|
||||
WRITE,
|
||||
CLOBBERED,
|
||||
};
|
||||
|
||||
IRUsage IRNextGPRUsage(int gpr, const IRSituation &info);
|
||||
IRUsage IRNextFPRUsage(int fpr, const IRSituation &info);
|
|
@ -455,6 +455,7 @@ void IRFrontend::Comp_Syscall(MIPSOpcode op) {
|
|||
}
|
||||
|
||||
void IRFrontend::Comp_Break(MIPSOpcode op) {
|
||||
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));
|
||||
ir.Write(IROp::Break);
|
||||
js.compiling = false;
|
||||
}
|
||||
|
|
|
@ -152,7 +152,8 @@ void IRFrontend::Comp_FPU2op(MIPSOpcode op) {
|
|||
ir.Write(IROp::FAbs, fd, fs);
|
||||
break;
|
||||
case 6: //F(fd) = F(fs); break; //mov
|
||||
ir.Write(IROp::FMov, fd, fs);
|
||||
if (fd != fs)
|
||||
ir.Write(IROp::FMov, fd, fs);
|
||||
break;
|
||||
case 7: //F(fd) = -F(fs); break; //neg
|
||||
ir.Write(IROp::FNeg, fd, fs);
|
||||
|
@ -203,7 +204,8 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) {
|
|||
return;
|
||||
}
|
||||
if (fs == 31) {
|
||||
DISABLE; // TODO: Add a new op
|
||||
// This needs to insert fpcond.
|
||||
ir.Write(IROp::FpCtrlToReg, rt);
|
||||
} else if (fs == 0) {
|
||||
ir.Write(IROp::SetConst, rt, ir.AddConstant(MIPSState::FCR0_VALUE));
|
||||
} else {
|
||||
|
@ -219,7 +221,10 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) {
|
|||
case 6: //ctc1
|
||||
if (fs == 31) {
|
||||
// Set rounding mode
|
||||
DISABLE;
|
||||
RestoreRoundingMode();
|
||||
ir.Write(IROp::FpCtrlFromReg, 0, rt);
|
||||
UpdateRoundingMode();
|
||||
ApplyRoundingMode();
|
||||
} else {
|
||||
Comp_Generic(op);
|
||||
}
|
||||
|
|
|
@ -105,6 +105,30 @@ namespace MIPSComp {
|
|||
}
|
||||
}
|
||||
|
||||
void IRFrontend::Comp_StoreSync(MIPSOpcode op) {
|
||||
CONDITIONAL_DISABLE(LSU);
|
||||
|
||||
int offset = _IMM16;
|
||||
MIPSGPReg rt = _RT;
|
||||
MIPSGPReg rs = _RS;
|
||||
// Note: still does something even if loading to zero.
|
||||
|
||||
CheckMemoryBreakpoint(rs, offset);
|
||||
|
||||
switch (op >> 26) {
|
||||
case 48: // ll
|
||||
ir.Write(IROp::Load32Linked, rt, rs, ir.AddConstant(offset));
|
||||
break;
|
||||
|
||||
case 56: // sc
|
||||
ir.Write(IROp::Store32Conditional, rt, rs, ir.AddConstant(offset));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
}
|
||||
}
|
||||
|
||||
void IRFrontend::Comp_Cache(MIPSOpcode op) {
|
||||
CONDITIONAL_DISABLE(LSU);
|
||||
|
||||
|
|
|
@ -238,7 +238,7 @@ namespace MIPSComp {
|
|||
} else {
|
||||
if (negate)
|
||||
ir.Write(IROp::FNeg, vregs[i], origV[regnum]);
|
||||
else
|
||||
else if (vregs[i] != origV[regnum])
|
||||
ir.Write(IROp::FMov, vregs[i], origV[regnum]);
|
||||
}
|
||||
} else {
|
||||
|
@ -855,7 +855,8 @@ namespace MIPSComp {
|
|||
switch (optype) {
|
||||
case 0: // d[i] = s[i]; break; //vmov
|
||||
// Probably for swizzle.
|
||||
ir.Write(IROp::FMov, tempregs[i], sregs[i]);
|
||||
if (tempregs[i] != sregs[i])
|
||||
ir.Write(IROp::FMov, tempregs[i], sregs[i]);
|
||||
break;
|
||||
case 1: // d[i] = fabsf(s[i]); break; //vabs
|
||||
ir.Write(IROp::FAbs, tempregs[i], sregs[i]);
|
||||
|
@ -929,37 +930,17 @@ namespace MIPSComp {
|
|||
VectorSize sz = GetVecSize(op);
|
||||
int n = GetNumVectorElements(sz);
|
||||
|
||||
int imm = (op >> 16) & 0x1f;
|
||||
const float mult = 1.0f / (float)(1UL << imm);
|
||||
uint8_t imm = (op >> 16) & 0x1f;
|
||||
|
||||
u8 sregs[4], dregs[4];
|
||||
GetVectorRegsPrefixS(sregs, sz, _VS);
|
||||
GetVectorRegsPrefixD(dregs, sz, _VD);
|
||||
|
||||
u8 tempregs[4];
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (!IsOverlapSafe(dregs[i], n, sregs)) {
|
||||
tempregs[i] = IRVTEMP_PFX_T + i; // Need IRVTEMP_0 for the scaling factor
|
||||
} else {
|
||||
tempregs[i] = dregs[i];
|
||||
}
|
||||
}
|
||||
if (mult != 1.0f)
|
||||
ir.Write(IROp::SetConstF, IRVTEMP_0, ir.AddConstantFloat(mult));
|
||||
// TODO: Use the SCVTF with builtin scaling where possible.
|
||||
for (int i = 0; i < n; i++) {
|
||||
ir.Write(IROp::FCvtSW, tempregs[i], sregs[i]);
|
||||
}
|
||||
if (mult != 1.0f) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
ir.Write(IROp::FMul, tempregs[i], tempregs[i], IRVTEMP_0);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (dregs[i] != tempregs[i]) {
|
||||
ir.Write(IROp::FMov, dregs[i], tempregs[i]);
|
||||
}
|
||||
if (imm == 0)
|
||||
ir.Write(IROp::FCvtSW, dregs[i], sregs[i]);
|
||||
else
|
||||
ir.Write(IROp::FCvtScaledSW, dregs[i], sregs[i], imm);
|
||||
}
|
||||
ApplyPrefixD(dregs, sz);
|
||||
}
|
||||
|
@ -986,7 +967,47 @@ namespace MIPSComp {
|
|||
// d[N] = int(S[N] * mult)
|
||||
// Note: saturates on overflow.
|
||||
|
||||
DISABLE;
|
||||
VectorSize sz = GetVecSize(op);
|
||||
int n = GetNumVectorElements(sz);
|
||||
|
||||
uint8_t imm = (op >> 16) & 0x1f;
|
||||
|
||||
u8 sregs[4], dregs[4];
|
||||
GetVectorRegsPrefixS(sregs, sz, _VS);
|
||||
GetVectorRegsPrefixD(dregs, sz, _VD);
|
||||
|
||||
// Same values as FCR31.
|
||||
uint8_t rmode = (op >> 21) & 3;
|
||||
if (((op >> 21) & 0x1C) != 0x10)
|
||||
INVALIDOP;
|
||||
|
||||
if (imm != 0) {
|
||||
for (int i = 0; i < n; i++)
|
||||
ir.Write(IROp::FCvtScaledWS, dregs[i], sregs[i], imm | (rmode << 6));
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
switch (rmode) {
|
||||
case 0: // vf2in
|
||||
ir.Write(IROp::FRound, dregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
case 1: // vf2iz
|
||||
ir.Write(IROp::FTrunc, dregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
case 2: // vf2iu
|
||||
ir.Write(IROp::FCeil, dregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
case 3: // vf2id
|
||||
ir.Write(IROp::FFloor, dregs[i], sregs[i]);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IRFrontend::Comp_Mftv(MIPSOpcode op) {
|
||||
|
@ -1151,7 +1172,8 @@ namespace MIPSComp {
|
|||
}
|
||||
for (int a = 0; a < n; a++) {
|
||||
for (int b = 0; b < n; b++) {
|
||||
ir.Write(IROp::FMov, dregs[a * 4 + b], sregs[a * 4 + b]);
|
||||
if (dregs[a * 4 + b] != sregs[a * 4 + b])
|
||||
ir.Write(IROp::FMov, dregs[a * 4 + b], sregs[a * 4 + b]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ public:
|
|||
|
||||
// Ops
|
||||
void Comp_ITypeMem(MIPSOpcode op) override;
|
||||
void Comp_StoreSync(MIPSOpcode op) override;
|
||||
void Comp_Cache(MIPSOpcode op) override;
|
||||
|
||||
void Comp_RelBranch(MIPSOpcode op) override;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "Common/CommonFuncs.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
#include "Core/MIPS/IR/IRPassSimplify.h"
|
||||
#include "Core/MIPS/MIPSDebugInterface.h"
|
||||
|
||||
// Legend
|
||||
|
@ -73,6 +72,7 @@ static const IRMeta irMeta[] = {
|
|||
{ IROp::Load32, "Load32", "GGC" },
|
||||
{ IROp::Load32Left, "Load32Left", "GGC", IRFLAG_SRC3DST },
|
||||
{ IROp::Load32Right, "Load32Right", "GGC", IRFLAG_SRC3DST },
|
||||
{ IROp::Load32Linked, "Load32Linked", "GGC" },
|
||||
{ IROp::LoadFloat, "LoadFloat", "FGC" },
|
||||
{ IROp::LoadVec4, "LoadVec4", "VGC" },
|
||||
{ IROp::Store8, "Store8", "GGC", IRFLAG_SRC3 },
|
||||
|
@ -80,6 +80,7 @@ static const IRMeta irMeta[] = {
|
|||
{ IROp::Store32, "Store32", "GGC", IRFLAG_SRC3 },
|
||||
{ IROp::Store32Left, "Store32Left", "GGC", IRFLAG_SRC3 },
|
||||
{ IROp::Store32Right, "Store32Right", "GGC", IRFLAG_SRC3 },
|
||||
{ IROp::Store32Conditional, "Store32Conditional", "GGC", IRFLAG_SRC3DST },
|
||||
{ IROp::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 },
|
||||
{ IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 },
|
||||
{ IROp::FAdd, "FAdd", "FFF" },
|
||||
|
@ -105,6 +106,8 @@ static const IRMeta irMeta[] = {
|
|||
{ IROp::FFloor, "FFloor", "FF" },
|
||||
{ IROp::FCvtWS, "FCvtWS", "FF" },
|
||||
{ IROp::FCvtSW, "FCvtSW", "FF" },
|
||||
{ IROp::FCvtScaledWS, "FCvtScaledWS", "FFI" },
|
||||
{ IROp::FCvtScaledSW, "FCvtScaledSW", "FFI" },
|
||||
{ IROp::FCmp, "FCmp", "mFF" },
|
||||
{ IROp::FSat0_1, "FSat(0 - 1)", "FF" },
|
||||
{ IROp::FSatMinus1_1, "FSat(-1 - 1)", "FF" },
|
||||
|
@ -112,11 +115,13 @@ static const IRMeta irMeta[] = {
|
|||
{ IROp::FMovToGPR, "FMovToGPR", "GF" },
|
||||
{ IROp::ZeroFpCond, "ZeroFpCond", "" },
|
||||
{ IROp::FpCondToReg, "FpCondToReg", "G" },
|
||||
{ IROp::FpCtrlFromReg, "FpCtrlFromReg", "_G" },
|
||||
{ IROp::FpCtrlToReg, "FpCtrlToReg", "G" },
|
||||
{ IROp::VfpuCtrlToReg, "VfpuCtrlToReg", "GI" },
|
||||
{ IROp::SetCtrlVFPU, "SetCtrlVFPU", "TC" },
|
||||
{ IROp::SetCtrlVFPUReg, "SetCtrlVFPUReg", "TG" },
|
||||
{ IROp::SetCtrlVFPUFReg, "SetCtrlVFPUFReg", "TF" },
|
||||
{ IROp::FCmovVfpuCC, "FCmovVfpuCC", "FFI" },
|
||||
{ IROp::FCmovVfpuCC, "FCmovVfpuCC", "FFI", IRFLAG_SRC3DST },
|
||||
{ IROp::FCmpVfpuBit, "FCmpVfpuBit", "IFF" },
|
||||
{ IROp::FCmpVfpuAggregate, "FCmpVfpuAggregate", "I" },
|
||||
{ IROp::Vec4Init, "Vec4Init", "Vv" },
|
||||
|
@ -263,21 +268,21 @@ void DisassembleParam(char *buf, int bufSize, u8 param, char type, u32 constant)
|
|||
break;
|
||||
case 'F':
|
||||
if (param >= 32) {
|
||||
snprintf(buf, bufSize, "v%d", param - 32);
|
||||
snprintf(buf, bufSize, "vf%d", param - 32);
|
||||
} else {
|
||||
snprintf(buf, bufSize, "f%d", param);
|
||||
}
|
||||
break;
|
||||
case 'V':
|
||||
if (param >= 32) {
|
||||
snprintf(buf, bufSize, "v%d..v%d", param - 32, param - 32 + 3);
|
||||
snprintf(buf, bufSize, "vf%d..vf%d", param - 32, param - 32 + 3);
|
||||
} else {
|
||||
snprintf(buf, bufSize, "f%d..f%d", param, param + 3);
|
||||
}
|
||||
break;
|
||||
case '2':
|
||||
if (param >= 32) {
|
||||
snprintf(buf, bufSize, "v%d,v%d", param - 32, param - 32 + 1);
|
||||
snprintf(buf, bufSize, "vf%d,vf%d", param - 32, param - 32 + 1);
|
||||
} else {
|
||||
snprintf(buf, bufSize, "f%d,f%d", param, param + 1);
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ enum class IROp : u8 {
|
|||
Load32,
|
||||
Load32Left,
|
||||
Load32Right,
|
||||
Load32Linked,
|
||||
LoadFloat,
|
||||
LoadVec4,
|
||||
|
||||
|
@ -100,6 +101,7 @@ enum class IROp : u8 {
|
|||
Store32,
|
||||
Store32Left,
|
||||
Store32Right,
|
||||
Store32Conditional,
|
||||
StoreFloat,
|
||||
StoreVec4,
|
||||
|
||||
|
@ -127,6 +129,8 @@ enum class IROp : u8 {
|
|||
|
||||
FCvtWS,
|
||||
FCvtSW,
|
||||
FCvtScaledWS,
|
||||
FCvtScaledSW,
|
||||
|
||||
FMovFromGPR,
|
||||
FMovToGPR,
|
||||
|
@ -135,6 +139,8 @@ enum class IROp : u8 {
|
|||
FSatMinus1_1,
|
||||
|
||||
FpCondToReg,
|
||||
FpCtrlFromReg,
|
||||
FpCtrlToReg,
|
||||
VfpuCtrlToReg,
|
||||
|
||||
ZeroFpCond,
|
||||
|
@ -283,7 +289,9 @@ enum IRFpCompareMode {
|
|||
LessEqualUnordered, // ule, ngt (less equal, unordered)
|
||||
};
|
||||
|
||||
enum {
|
||||
typedef u8 IRReg;
|
||||
|
||||
enum : IRReg {
|
||||
IRTEMP_0 = 192,
|
||||
IRTEMP_1,
|
||||
IRTEMP_2,
|
||||
|
@ -300,9 +308,6 @@ enum {
|
|||
IRVTEMP_PFX_D = 232 - 32,
|
||||
IRVTEMP_0 = 236 - 32,
|
||||
|
||||
// 16 float temps for vector S and T prefixes and things like that.
|
||||
// IRVTEMP_0 = 208 - 64, // -64 to be relative to v[0]
|
||||
|
||||
// Hacky way to get to other state
|
||||
IRREG_VFPU_CTRL_BASE = 208,
|
||||
IRREG_VFPU_CC = 211,
|
||||
|
@ -310,6 +315,7 @@ enum {
|
|||
IRREG_HI = 243,
|
||||
IRREG_FCR31 = 244,
|
||||
IRREG_FPCOND = 245,
|
||||
IRREG_LLBIT = 250,
|
||||
};
|
||||
|
||||
enum IRFlags {
|
||||
|
@ -332,11 +338,11 @@ struct IRMeta {
|
|||
struct IRInst {
|
||||
IROp op;
|
||||
union {
|
||||
u8 dest;
|
||||
u8 src3;
|
||||
IRReg dest;
|
||||
IRReg src3;
|
||||
};
|
||||
u8 src1;
|
||||
u8 src2;
|
||||
IRReg src1;
|
||||
IRReg src2;
|
||||
u32 constant;
|
||||
};
|
||||
|
||||
|
|
|
@ -218,6 +218,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
|||
mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem >> shift);
|
||||
break;
|
||||
}
|
||||
case IROp::Load32Linked:
|
||||
if (inst->dest != MIPS_REG_ZERO)
|
||||
mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant);
|
||||
mips->llBit = 1;
|
||||
break;
|
||||
case IROp::LoadFloat:
|
||||
mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant);
|
||||
break;
|
||||
|
@ -251,6 +256,16 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
|||
Memory::WriteUnchecked_U32(result, addr & 0xfffffffc);
|
||||
break;
|
||||
}
|
||||
case IROp::Store32Conditional:
|
||||
if (mips->llBit) {
|
||||
Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant);
|
||||
if (inst->dest != MIPS_REG_ZERO) {
|
||||
mips->r[inst->dest] = 1;
|
||||
}
|
||||
} else if (inst->dest != MIPS_REG_ZERO) {
|
||||
mips->r[inst->dest] = 0;
|
||||
}
|
||||
break;
|
||||
case IROp::StoreFloat:
|
||||
Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant);
|
||||
break;
|
||||
|
@ -768,10 +783,28 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
|||
mips->f[inst->dest] = mips->f[inst->src1] / mips->f[inst->src2];
|
||||
break;
|
||||
case IROp::FMin:
|
||||
mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]);
|
||||
if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) {
|
||||
// See interpreter for this logic: this is for vmin, we're comparing mantissa+exp.
|
||||
if (mips->fi[inst->src1] < 0 && mips->fi[inst->src2] < 0) {
|
||||
mips->fi[inst->dest] = std::max(mips->fi[inst->src1], mips->fi[inst->src2]);
|
||||
} else {
|
||||
mips->fi[inst->dest] = std::min(mips->fi[inst->src1], mips->fi[inst->src2]);
|
||||
}
|
||||
} else {
|
||||
mips->f[inst->dest] = std::min(mips->f[inst->src1], mips->f[inst->src2]);
|
||||
}
|
||||
break;
|
||||
case IROp::FMax:
|
||||
mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]);
|
||||
if (my_isnan(mips->f[inst->src1]) || my_isnan(mips->f[inst->src2])) {
|
||||
// See interpreter for this logic: this is for vmax, we're comparing mantissa+exp.
|
||||
if (mips->fi[inst->src1] < 0 && mips->fi[inst->src2] < 0) {
|
||||
mips->fi[inst->dest] = std::min(mips->fi[inst->src1], mips->fi[inst->src2]);
|
||||
} else {
|
||||
mips->fi[inst->dest] = std::max(mips->fi[inst->src1], mips->fi[inst->src2]);
|
||||
}
|
||||
} else {
|
||||
mips->f[inst->dest] = std::max(mips->f[inst->src1], mips->f[inst->src2]);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FMov:
|
||||
|
@ -811,6 +844,17 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
|||
case IROp::FpCondToReg:
|
||||
mips->r[inst->dest] = mips->fpcond;
|
||||
break;
|
||||
case IROp::FpCtrlFromReg:
|
||||
mips->fcr31 = mips->r[inst->src1] & 0x0181FFFF;
|
||||
// Extract the new fpcond value.
|
||||
// TODO: Is it really helping us to keep it separate?
|
||||
mips->fpcond = (mips->fcr31 >> 23) & 1;
|
||||
break;
|
||||
case IROp::FpCtrlToReg:
|
||||
// Update the fpcond bit first.
|
||||
mips->fcr31 = (mips->fcr31 & ~(1 << 23)) | ((mips->fpcond & 1) << 23);
|
||||
mips->r[inst->dest] = mips->fcr31;
|
||||
break;
|
||||
case IROp::VfpuCtrlToReg:
|
||||
mips->r[inst->dest] = mips->vfpuCtrl[inst->src1];
|
||||
break;
|
||||
|
@ -821,7 +865,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
|||
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
|
||||
break;
|
||||
} else {
|
||||
mips->fs[inst->dest] = (int)floorf(value + 0.5f);
|
||||
mips->fs[inst->dest] = (int)round_ieee_754(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -918,6 +962,35 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
|
|||
}
|
||||
break; //cvt.w.s
|
||||
}
|
||||
case IROp::FCvtScaledSW:
|
||||
mips->f[inst->dest] = (float)mips->fs[inst->src1] * (1.0f / (1UL << (inst->src2 & 0x1F)));
|
||||
break;
|
||||
case IROp::FCvtScaledWS:
|
||||
{
|
||||
float src = mips->f[inst->src1];
|
||||
if (my_isnan(src)) {
|
||||
// TODO: True for negatives too?
|
||||
mips->fs[inst->dest] = 2147483647L;
|
||||
break;
|
||||
}
|
||||
|
||||
float mult = (float)(1UL << (inst->src2 & 0x1F));
|
||||
double sv = src * mult; // (float)0x7fffffff == (float)0x80000000
|
||||
// Cap/floor it to 0x7fffffff / 0x80000000
|
||||
if (sv > (double)0x7fffffff) {
|
||||
mips->fs[inst->dest] = 0x7fffffff;
|
||||
} else if (sv <= (double)(int)0x80000000) {
|
||||
mips->fs[inst->dest] = 0x80000000;
|
||||
} else {
|
||||
switch (inst->src2 >> 6) {
|
||||
case 0: mips->fs[inst->dest] = (int)round_ieee_754(sv); break;
|
||||
case 1: mips->fs[inst->dest] = src >= 0 ? (int)floor(sv) : (int)ceil(sv); break;
|
||||
case 2: mips->fs[inst->dest] = (int)ceil(sv); break;
|
||||
case 3: mips->fs[inst->dest] = (int)floor(sv); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IROp::ZeroFpCond:
|
||||
mips->fpcond = 0;
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
#include "Core/MIPS/MIPSTables.h"
|
||||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
#include "Core/MIPS/IR/IRJit.h"
|
||||
#include "Core/MIPS/IR/IRPassSimplify.h"
|
||||
#include "Core/MIPS/IR/IRInterpreter.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/Reporting.h"
|
||||
|
@ -85,7 +84,8 @@ void IRJit::Compile(u32 em_address) {
|
|||
if (block_num != -1) {
|
||||
IRBlock *b = blocks_.GetBlock(block_num);
|
||||
// Okay, let's link and finalize the block now.
|
||||
b->Finalize(block_num);
|
||||
int cookie = b->GetTargetOffset() < 0 ? block_num : b->GetTargetOffset();
|
||||
b->Finalize(cookie);
|
||||
if (b->IsValid()) {
|
||||
// Success, we're done.
|
||||
return;
|
||||
|
@ -128,13 +128,13 @@ bool IRJit::CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32
|
|||
b->SetOriginalSize(mipsBytes);
|
||||
if (preload) {
|
||||
// Hash, then only update page stats, don't link yet.
|
||||
b->UpdateHash();
|
||||
blocks_.FinalizeBlock(block_num, true);
|
||||
} else {
|
||||
// Overwrites the first instruction, and also updates stats.
|
||||
// TODO: Should we always hash? Then we can reuse blocks.
|
||||
blocks_.FinalizeBlock(block_num);
|
||||
b->UpdateHash();
|
||||
}
|
||||
if (!CompileTargetBlock(b, block_num, preload))
|
||||
return false;
|
||||
// Overwrites the first instruction, and also updates stats.
|
||||
blocks_.FinalizeBlock(block_num, preload);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -262,14 +262,10 @@ void IRJit::UnlinkBlock(u8 *checkedEntry, u32 originalAddress) {
|
|||
Crash();
|
||||
}
|
||||
|
||||
bool IRJit::ReplaceJalTo(u32 dest) {
|
||||
Crash();
|
||||
return false;
|
||||
}
|
||||
|
||||
void IRBlockCache::Clear() {
|
||||
for (int i = 0; i < (int)blocks_.size(); ++i) {
|
||||
blocks_[i].Destroy(i);
|
||||
int cookie = blocks_[i].GetTargetOffset() < 0 ? i : blocks_[i].GetTargetOffset();
|
||||
blocks_[i].Destroy(cookie);
|
||||
}
|
||||
blocks_.clear();
|
||||
byPage_.clear();
|
||||
|
@ -288,7 +284,8 @@ void IRBlockCache::InvalidateICache(u32 address, u32 length) {
|
|||
for (int i : blocksInPage) {
|
||||
if (blocks_[i].OverlapsRange(address, length)) {
|
||||
// Not removing from the page, hopefully doesn't build up with small recompiles.
|
||||
blocks_[i].Destroy(i);
|
||||
int cookie = blocks_[i].GetTargetOffset() < 0 ? i : blocks_[i].GetTargetOffset();
|
||||
blocks_[i].Destroy(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +293,8 @@ void IRBlockCache::InvalidateICache(u32 address, u32 length) {
|
|||
|
||||
void IRBlockCache::FinalizeBlock(int i, bool preload) {
|
||||
if (!preload) {
|
||||
blocks_[i].Finalize(i);
|
||||
int cookie = blocks_[i].GetTargetOffset() < 0 ? i : blocks_[i].GetTargetOffset();
|
||||
blocks_[i].Finalize(cookie);
|
||||
}
|
||||
|
||||
u32 startAddr, size;
|
||||
|
@ -336,13 +334,30 @@ int IRBlockCache::FindPreloadBlock(u32 em_address) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
int IRBlockCache::FindByCookie(int cookie) {
|
||||
if (blocks_.empty())
|
||||
return -1;
|
||||
// TODO: Maybe a flag to determine target offset mode?
|
||||
if (blocks_[0].GetTargetOffset() < 0)
|
||||
return cookie;
|
||||
|
||||
for (int i = 0; i < GetNumBlocks(); ++i) {
|
||||
int offset = blocks_[i].GetTargetOffset();
|
||||
if (offset == cookie)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<u32> IRBlockCache::SaveAndClearEmuHackOps() {
|
||||
std::vector<u32> result;
|
||||
result.resize(blocks_.size());
|
||||
|
||||
for (int number = 0; number < (int)blocks_.size(); ++number) {
|
||||
IRBlock &b = blocks_[number];
|
||||
if (b.IsValid() && b.RestoreOriginalFirstOp(number)) {
|
||||
int cookie = b.GetTargetOffset() < 0 ? number : b.GetTargetOffset();
|
||||
if (b.IsValid() && b.RestoreOriginalFirstOp(cookie)) {
|
||||
result[number] = number;
|
||||
} else {
|
||||
result[number] = 0;
|
||||
|
@ -362,7 +377,8 @@ void IRBlockCache::RestoreSavedEmuHackOps(std::vector<u32> saved) {
|
|||
IRBlock &b = blocks_[number];
|
||||
// Only if we restored it, write it back.
|
||||
if (b.IsValid() && saved[number] != 0 && b.HasOriginalFirstOp()) {
|
||||
b.Finalize(number);
|
||||
int cookie = b.GetTargetOffset() < 0 ? number : b.GetTargetOffset();
|
||||
b.Finalize(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -446,8 +462,8 @@ bool IRBlock::HasOriginalFirstOp() const {
|
|||
return Memory::ReadUnchecked_U32(origAddr_) == origFirstOpcode_.encoding;
|
||||
}
|
||||
|
||||
bool IRBlock::RestoreOriginalFirstOp(int number) {
|
||||
const u32 emuhack = MIPS_EMUHACK_OPCODE | number;
|
||||
bool IRBlock::RestoreOriginalFirstOp(int cookie) {
|
||||
const u32 emuhack = MIPS_EMUHACK_OPCODE | cookie;
|
||||
if (Memory::ReadUnchecked_U32(origAddr_) == emuhack) {
|
||||
Memory::Write_Opcode_JIT(origAddr_, origFirstOpcode_);
|
||||
return true;
|
||||
|
@ -455,19 +471,19 @@ bool IRBlock::RestoreOriginalFirstOp(int number) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void IRBlock::Finalize(int number) {
|
||||
void IRBlock::Finalize(int cookie) {
|
||||
// Check it wasn't invalidated, in case this is after preload.
|
||||
// TODO: Allow reusing blocks when the code matches hash_ again, instead.
|
||||
if (origAddr_) {
|
||||
origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_);
|
||||
MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number);
|
||||
MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | cookie);
|
||||
Memory::Write_Opcode_JIT(origAddr_, opcode);
|
||||
}
|
||||
}
|
||||
|
||||
void IRBlock::Destroy(int number) {
|
||||
void IRBlock::Destroy(int cookie) {
|
||||
if (origAddr_) {
|
||||
MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number);
|
||||
MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | cookie);
|
||||
if (Memory::ReadUnchecked_U32(origAddr_) == opcode.encoding)
|
||||
Memory::Write_Opcode_JIT(origAddr_, origFirstOpcode_);
|
||||
|
||||
|
@ -501,7 +517,7 @@ bool IRBlock::OverlapsRange(u32 addr, u32 size) const {
|
|||
}
|
||||
|
||||
MIPSOpcode IRJit::GetOriginalOp(MIPSOpcode op) {
|
||||
IRBlock *b = blocks_.GetBlock(op.encoding & 0xFFFFFF);
|
||||
IRBlock *b = blocks_.GetBlock(blocks_.FindByCookie(op.encoding & 0xFFFFFF));
|
||||
if (b) {
|
||||
return b->GetOriginalFirstOp();
|
||||
}
|
||||
|
|
|
@ -38,15 +38,16 @@ namespace MIPSComp {
|
|||
// TODO : Use arena allocators. For now let's just malloc.
|
||||
class IRBlock {
|
||||
public:
|
||||
IRBlock() : instr_(nullptr), numInstructions_(0), origAddr_(0), origSize_(0) {}
|
||||
IRBlock(u32 emAddr) : instr_(nullptr), numInstructions_(0), origAddr_(emAddr), origSize_(0) {}
|
||||
IRBlock() {}
|
||||
IRBlock(u32 emAddr) : origAddr_(emAddr) {}
|
||||
IRBlock(IRBlock &&b) {
|
||||
instr_ = b.instr_;
|
||||
numInstructions_ = b.numInstructions_;
|
||||
hash_ = b.hash_;
|
||||
origAddr_ = b.origAddr_;
|
||||
origSize_ = b.origSize_;
|
||||
origFirstOpcode_ = b.origFirstOpcode_;
|
||||
hash_ = b.hash_;
|
||||
targetOffset_ = b.targetOffset_;
|
||||
numInstructions_ = b.numInstructions_;
|
||||
b.instr_ = nullptr;
|
||||
}
|
||||
|
||||
|
@ -71,6 +72,12 @@ public:
|
|||
void SetOriginalSize(u32 size) {
|
||||
origSize_ = size;
|
||||
}
|
||||
void SetTargetOffset(int offset) {
|
||||
targetOffset_ = offset;
|
||||
}
|
||||
int GetTargetOffset() const {
|
||||
return targetOffset_;
|
||||
}
|
||||
void UpdateHash() {
|
||||
hash_ = CalculateHash();
|
||||
}
|
||||
|
@ -90,12 +97,13 @@ public:
|
|||
private:
|
||||
u64 CalculateHash() const;
|
||||
|
||||
IRInst *instr_;
|
||||
u16 numInstructions_;
|
||||
u32 origAddr_;
|
||||
u32 origSize_;
|
||||
IRInst *instr_ = nullptr;
|
||||
u64 hash_ = 0;
|
||||
u32 origAddr_ = 0;
|
||||
u32 origSize_ = 0;
|
||||
MIPSOpcode origFirstOpcode_ = MIPSOpcode(0x68FFFFFF);
|
||||
int targetOffset_ = -1;
|
||||
u16 numInstructions_ = 0;
|
||||
};
|
||||
|
||||
class IRBlockCache : public JitBlockCacheDebugInterface {
|
||||
|
@ -118,6 +126,7 @@ public:
|
|||
}
|
||||
|
||||
int FindPreloadBlock(u32 em_address);
|
||||
int FindByCookie(int cookie);
|
||||
|
||||
std::vector<u32> SaveAndClearEmuHackOps();
|
||||
void RestoreSavedEmuHackOps(std::vector<u32> saved);
|
||||
|
@ -170,9 +179,9 @@ public:
|
|||
void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override;
|
||||
void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override;
|
||||
|
||||
private:
|
||||
bool CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload);
|
||||
bool ReplaceJalTo(u32 dest);
|
||||
protected:
|
||||
virtual bool CompileBlock(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload);
|
||||
virtual bool CompileTargetBlock(IRBlock *block, int block_num, bool preload) { return true; }
|
||||
|
||||
JitOptions jo;
|
||||
|
||||
|
@ -187,4 +196,3 @@ private:
|
|||
};
|
||||
|
||||
} // namespace MIPSComp
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Common/Data/Convert/SmallDataConvert.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Core/Config.h"
|
||||
#include "Core/MIPS/IR/IRAnalysis.h"
|
||||
#include "Core/MIPS/IR/IRInterpreter.h"
|
||||
#include "Core/MIPS/IR/IRPassSimplify.h"
|
||||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
|
@ -128,7 +129,9 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) {
|
|||
if (prev.op == IROp::FMovToGPR && prev.dest == inst.src1) {
|
||||
inst.op = IROp::FMov;
|
||||
inst.src1 = prev.src1;
|
||||
out.Write(inst);
|
||||
// Skip it entirely if it's just a copy to and back.
|
||||
if (inst.dest != inst.src1)
|
||||
out.Write(inst);
|
||||
} else {
|
||||
out.Write(inst);
|
||||
}
|
||||
|
@ -228,7 +231,7 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions
|
|||
};
|
||||
|
||||
auto combineOpposite = [&](IROp matchOp, int matchOff, IROp replaceOp, int replaceOff) {
|
||||
if (!opts.unalignedLoadStore || i + 1 >= n)
|
||||
if (i + 1 >= n)
|
||||
return false;
|
||||
const IRInst &next = nextOp();
|
||||
if (next.op != matchOp || next.dest != inst.dest || next.src1 != inst.src1)
|
||||
|
@ -236,8 +239,40 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions
|
|||
if (inst.constant + matchOff != next.constant)
|
||||
return false;
|
||||
|
||||
// Write out one unaligned op.
|
||||
out.Write(replaceOp, inst.dest, inst.src1, out.AddConstant(inst.constant + replaceOff));
|
||||
if (opts.unalignedLoadStore) {
|
||||
// Write out one unaligned op.
|
||||
out.Write(replaceOp, inst.dest, inst.src1, out.AddConstant(inst.constant + replaceOff));
|
||||
} else if (replaceOp == IROp::Load32) {
|
||||
// We can still combine to a simpler set of two loads.
|
||||
// We start by isolating the address and shift amount.
|
||||
|
||||
// IRTEMP_LR_ADDR = rs + imm
|
||||
out.Write(IROp::AddConst, IRTEMP_LR_ADDR, inst.src1, out.AddConstant(inst.constant + replaceOff));
|
||||
// IRTEMP_LR_SHIFT = (addr & 3) * 8
|
||||
out.Write(IROp::AndConst, IRTEMP_LR_SHIFT, IRTEMP_LR_ADDR, out.AddConstant(3));
|
||||
out.Write(IROp::ShlImm, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, 3);
|
||||
// IRTEMP_LR_ADDR = addr & 0xfffffffc
|
||||
out.Write(IROp::AndConst, IRTEMP_LR_ADDR, IRTEMP_LR_ADDR, out.AddConstant(0xFFFFFFFC));
|
||||
// IRTEMP_LR_VALUE = low_word, dest = high_word
|
||||
out.Write(IROp::Load32, inst.dest, IRTEMP_LR_ADDR, out.AddConstant(0));
|
||||
out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(4));
|
||||
|
||||
// Now we just need to adjust and combine dest and IRTEMP_LR_VALUE.
|
||||
// inst.dest >>= shift (putting its bits in the right spot.)
|
||||
out.Write(IROp::Shr, inst.dest, inst.dest, IRTEMP_LR_SHIFT);
|
||||
// We can't shift by 32, so we compromise by shifting twice.
|
||||
out.Write(IROp::ShlImm, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, 8);
|
||||
// IRTEMP_LR_SHIFT = 24 - shift
|
||||
out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT);
|
||||
out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24));
|
||||
// IRTEMP_LR_VALUE <<= (24 - shift)
|
||||
out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT);
|
||||
|
||||
// At this point the values are aligned, and we just merge.
|
||||
out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// Skip the next one, replaced.
|
||||
i++;
|
||||
return true;
|
||||
|
@ -572,6 +607,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
|
|||
case IROp::Store32:
|
||||
case IROp::Store32Left:
|
||||
case IROp::Store32Right:
|
||||
case IROp::Store32Conditional:
|
||||
if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) {
|
||||
gpr.MapIn(inst.dest);
|
||||
out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant));
|
||||
|
@ -595,6 +631,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
|
|||
case IROp::Load16:
|
||||
case IROp::Load16Ext:
|
||||
case IROp::Load32:
|
||||
case IROp::Load32Linked:
|
||||
if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) {
|
||||
gpr.MapDirty(inst.dest);
|
||||
out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant));
|
||||
|
@ -667,6 +704,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
|
|||
case IROp::FCeil:
|
||||
case IROp::FFloor:
|
||||
case IROp::FCvtSW:
|
||||
case IROp::FCvtScaledWS:
|
||||
case IROp::FCvtScaledSW:
|
||||
case IROp::FSin:
|
||||
case IROp::FCos:
|
||||
case IROp::FSqrt:
|
||||
|
@ -694,6 +733,13 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
|
|||
out.Write(inst);
|
||||
}
|
||||
break;
|
||||
case IROp::FpCtrlFromReg:
|
||||
gpr.MapDirtyIn(IRREG_FCR31, inst.src1);
|
||||
gpr.MapDirty(IRREG_FPCOND);
|
||||
goto doDefault;
|
||||
case IROp::FpCtrlToReg:
|
||||
gpr.MapDirtyInIn(inst.dest, IRREG_FPCOND, IRREG_FCR31);
|
||||
goto doDefault;
|
||||
|
||||
case IROp::Vec4Init:
|
||||
case IROp::Vec4Mov:
|
||||
|
@ -760,27 +806,6 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts
|
|||
return logBlocks;
|
||||
}
|
||||
|
||||
bool IRReadsFromGPR(const IRInst &inst, int reg, bool directly = false) {
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
||||
if (m->types[1] == 'G' && inst.src1 == reg) {
|
||||
return true;
|
||||
}
|
||||
if (m->types[2] == 'G' && inst.src2 == reg) {
|
||||
return true;
|
||||
}
|
||||
if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == 'G' && inst.src3 == reg) {
|
||||
return true;
|
||||
}
|
||||
if (!directly) {
|
||||
if (inst.op == IROp::Interpret || inst.op == IROp::CallReplacement || inst.op == IROp::Syscall || inst.op == IROp::Break)
|
||||
return true;
|
||||
if (inst.op == IROp::Breakpoint || inst.op == IROp::MemoryCheck)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) {
|
||||
IRInst newInst = inst;
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
@ -797,15 +822,6 @@ IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) {
|
|||
return newInst;
|
||||
}
|
||||
|
||||
int IRDestGPR(const IRInst &inst) {
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
||||
if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G') {
|
||||
return inst.dest;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
IRInst IRReplaceDestGPR(const IRInst &inst, int fromReg, int toReg) {
|
||||
IRInst newInst = inst;
|
||||
const IRMeta *m = GetIRMeta(inst.op);
|
||||
|
@ -1468,10 +1484,12 @@ bool ApplyMemoryValidation(const IRWriter &in, IRWriter &out, const IROptions &o
|
|||
break;
|
||||
|
||||
case IROp::Load32:
|
||||
case IROp::Load32Linked:
|
||||
case IROp::LoadFloat:
|
||||
case IROp::Store32:
|
||||
case IROp::Store32Conditional:
|
||||
case IROp::StoreFloat:
|
||||
addValidate(IROp::ValidateAddress32, inst, inst.op == IROp::Store32 || inst.op == IROp::StoreFloat);
|
||||
addValidate(IROp::ValidateAddress32, inst, inst.op == IROp::Store32 || inst.op == IROp::Store32Conditional || inst.op == IROp::StoreFloat);
|
||||
break;
|
||||
|
||||
case IROp::LoadVec4:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#include "Core/MIPS/IR/IRRegCache.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
|
||||
void IRRegCache::Flush(int rd) {
|
||||
void IRRegCache::Flush(IRReg rd) {
|
||||
if (rd == 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ void IRRegCache::Flush(int rd) {
|
|||
}
|
||||
}
|
||||
|
||||
void IRRegCache::Discard(int rd) {
|
||||
void IRRegCache::Discard(IRReg rd) {
|
||||
if (rd == 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -31,33 +31,33 @@ void IRRegCache::FlushAll() {
|
|||
}
|
||||
}
|
||||
|
||||
void IRRegCache::MapIn(int rd) {
|
||||
void IRRegCache::MapIn(IRReg rd) {
|
||||
Flush(rd);
|
||||
}
|
||||
|
||||
void IRRegCache::MapDirty(int rd) {
|
||||
void IRRegCache::MapDirty(IRReg rd) {
|
||||
Discard(rd);
|
||||
}
|
||||
|
||||
void IRRegCache::MapInIn(int rs, int rt) {
|
||||
void IRRegCache::MapInIn(IRReg rs, IRReg rt) {
|
||||
Flush(rs);
|
||||
Flush(rt);
|
||||
}
|
||||
|
||||
void IRRegCache::MapInInIn(int rd, int rs, int rt) {
|
||||
void IRRegCache::MapInInIn(IRReg rd, IRReg rs, IRReg rt) {
|
||||
Flush(rd);
|
||||
Flush(rs);
|
||||
Flush(rt);
|
||||
}
|
||||
|
||||
void IRRegCache::MapDirtyIn(int rd, int rs) {
|
||||
void IRRegCache::MapDirtyIn(IRReg rd, IRReg rs) {
|
||||
if (rs != rd) {
|
||||
Discard(rd);
|
||||
}
|
||||
Flush(rs);
|
||||
}
|
||||
|
||||
void IRRegCache::MapDirtyInIn(int rd, int rs, int rt) {
|
||||
void IRRegCache::MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt) {
|
||||
if (rs != rd && rt != rd) {
|
||||
Discard(rd);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Core/MIPS/MIPS.h"
|
||||
#include "Core/MIPS/IR/IRInst.h"
|
||||
|
||||
enum {
|
||||
TOTAL_MAPPABLE_MIPSREGS = 256,
|
||||
|
@ -22,26 +23,26 @@ class IRRegCache {
|
|||
public:
|
||||
IRRegCache(IRWriter *ir);
|
||||
|
||||
void SetImm(int r, u32 immVal) {
|
||||
void SetImm(IRReg r, u32 immVal) {
|
||||
reg_[r].isImm = true;
|
||||
reg_[r].immVal = immVal;
|
||||
}
|
||||
|
||||
bool IsImm(int r) const { return reg_[r].isImm; }
|
||||
u32 GetImm(int r) const { return reg_[r].immVal; }
|
||||
bool IsImm(IRReg r) const { return reg_[r].isImm; }
|
||||
u32 GetImm(IRReg r) const { return reg_[r].immVal; }
|
||||
|
||||
void FlushAll();
|
||||
|
||||
void MapDirty(int rd);
|
||||
void MapIn(int rd);
|
||||
void MapInIn(int rs, int rt);
|
||||
void MapInInIn(int rd, int rs, int rt);
|
||||
void MapDirtyIn(int rd, int rs);
|
||||
void MapDirtyInIn(int rd, int rs, int rt);
|
||||
void MapDirty(IRReg rd);
|
||||
void MapIn(IRReg rd);
|
||||
void MapInIn(IRReg rs, IRReg rt);
|
||||
void MapInInIn(IRReg rd, IRReg rs, IRReg rt);
|
||||
void MapDirtyIn(IRReg rd, IRReg rs);
|
||||
void MapDirtyInIn(IRReg rd, IRReg rs, IRReg rt);
|
||||
|
||||
private:
|
||||
void Flush(int rd);
|
||||
void Discard(int rd);
|
||||
void Flush(IRReg rd);
|
||||
void Discard(IRReg rd);
|
||||
RegIR reg_[TOTAL_MAPPABLE_MIPSREGS];
|
||||
IRWriter *ir_;
|
||||
};
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
#include "../x86/Jit.h"
|
||||
#elif PPSSPP_ARCH(MIPS)
|
||||
#include "../MIPS/MipsJit.h"
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
#include "../RiscV/RiscVJit.h"
|
||||
#else
|
||||
#include "../fake/FakeJit.h"
|
||||
#endif
|
||||
|
@ -108,6 +110,8 @@ namespace MIPSComp {
|
|||
return new MIPSComp::Jit(mipsState);
|
||||
#elif PPSSPP_ARCH(MIPS)
|
||||
return new MIPSComp::MipsJit(mipsState);
|
||||
#elif PPSSPP_ARCH(RISCV64)
|
||||
return new MIPSComp::RiscVJit(mipsState);
|
||||
#else
|
||||
return new MIPSComp::FakeJit(mipsState);
|
||||
#endif
|
||||
|
@ -331,12 +335,12 @@ std::vector<std::string> DisassembleRV64(const u8 *data, int size) {
|
|||
// Force align in case we're somehow unaligned.
|
||||
len = 2 - ((uintptr_t)data & 1);
|
||||
invalid_count += (int)len;
|
||||
i +=(int) len;
|
||||
i += (int)len;
|
||||
continue;
|
||||
}
|
||||
|
||||
invalid_flush();
|
||||
riscv_disasm_inst(temp, sizeof(temp), rv64, i * 4, inst);
|
||||
riscv_disasm_inst(temp, sizeof(temp), rv64, (uintptr_t)data + i, inst);
|
||||
lines.push_back(ReplaceAll(temp, "\t", " "));
|
||||
|
||||
i += (int)len;
|
||||
|
|
|
@ -54,6 +54,7 @@ namespace MIPSComp {
|
|||
virtual void Comp_RunBlock(MIPSOpcode op) = 0;
|
||||
virtual void Comp_ReplacementFunc(MIPSOpcode op) = 0;
|
||||
virtual void Comp_ITypeMem(MIPSOpcode op) = 0;
|
||||
virtual void Comp_StoreSync(MIPSOpcode op) = 0;
|
||||
virtual void Comp_Cache(MIPSOpcode op) = 0;
|
||||
virtual void Comp_RelBranch(MIPSOpcode op) = 0;
|
||||
virtual void Comp_RelBranchRI(MIPSOpcode op) = 0;
|
||||
|
|
|
@ -62,6 +62,7 @@ public:
|
|||
|
||||
// Ops
|
||||
void Comp_ITypeMem(MIPSOpcode op) override {}
|
||||
void Comp_StoreSync(MIPSOpcode op) override {}
|
||||
void Comp_Cache(MIPSOpcode op) override {}
|
||||
|
||||
void Comp_RelBranch(MIPSOpcode op) override {}
|
||||
|
|
|
@ -813,7 +813,7 @@ namespace MIPSAnalyst {
|
|||
break;
|
||||
}
|
||||
|
||||
if (reg > 32) {
|
||||
if (reg >= 32) {
|
||||
return USAGE_UNKNOWN;
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@ static const MIPSInstruction tableImmediate[64] = // xxxxxx ..... ..... ........
|
|||
INSTR("swr", JITFUNC(Comp_ITypeMem), Dis_ITypeMem, Int_ITypeMem, IN_IMM16|IN_RS_ADDR|IN_RT|OUT_MEM|MEMTYPE_WORD),
|
||||
INSTR("cache", JITFUNC(Comp_Cache), Dis_Cache, Int_Cache, IN_MEM|IN_IMM16|IN_RS_ADDR),
|
||||
//48
|
||||
INSTR("ll", JITFUNC(Comp_Generic), Dis_Generic, Int_StoreSync, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_RT|OUT_OTHER|MEMTYPE_WORD),
|
||||
INSTR("ll", JITFUNC(Comp_StoreSync), Dis_ITypeMem, Int_StoreSync, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_RT|OUT_OTHER|MEMTYPE_WORD),
|
||||
INSTR("lwc1", JITFUNC(Comp_FPULS), Dis_FPULS, Int_FPULS, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_FT|MEMTYPE_FLOAT|IS_FPU),
|
||||
INSTR("lv.s", JITFUNC(Comp_SV), Dis_SV, Int_SV, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_OTHER|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_FLOAT),
|
||||
INVALID,
|
||||
|
@ -159,7 +159,7 @@ static const MIPSInstruction tableImmediate[64] = // xxxxxx ..... ..... ........
|
|||
INSTR("lv.q", JITFUNC(Comp_SVQ), Dis_SVQ, Int_SVQ, IN_MEM|IN_IMM16|IN_RS_ADDR|OUT_OTHER|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_VQUAD), //copU
|
||||
ENCODING(VFPU5),
|
||||
//56
|
||||
INSTR("sc", JITFUNC(Comp_Generic), Dis_Generic, Int_StoreSync, IN_IMM16|IN_RS_ADDR|IN_OTHER|IN_RT|OUT_RT|OUT_MEM|MEMTYPE_WORD),
|
||||
INSTR("sc", JITFUNC(Comp_StoreSync), Dis_ITypeMem, Int_StoreSync, IN_IMM16|IN_RS_ADDR|IN_OTHER|IN_RT|OUT_RT|OUT_MEM|MEMTYPE_WORD),
|
||||
INSTR("swc1", JITFUNC(Comp_FPULS), Dis_FPULS, Int_FPULS, IN_IMM16|IN_RS_ADDR|IN_FT|OUT_MEM|MEMTYPE_FLOAT|IS_FPU), //copU
|
||||
INSTR("sv.s", JITFUNC(Comp_SV), Dis_SV, Int_SV, IN_IMM16|IN_RS_ADDR|IN_OTHER|OUT_MEM|IS_VFPU|VFPU_NO_PREFIX|MEMTYPE_FLOAT),
|
||||
INVALID,
|
||||
|
|
251
Core/MIPS/RiscV/RiscVAsm.cpp
Normal file
251
Core/MIPS/RiscV/RiscVAsm.cpp
Normal file
|
@ -0,0 +1,251 @@
|
|||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
#include "Core/MIPS/JitCommon/JitCommon.h"
|
||||
#include "Core/MIPS/JitCommon/JitState.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
static const bool enableDebug = false;
|
||||
static const bool enableDisasm = false;
|
||||
|
||||
static void ShowPC(u32 downcount, void *membase, void *jitbase) {
|
||||
static int count = 0;
|
||||
if (currentMIPS) {
|
||||
ERROR_LOG(JIT, "[%08x] ShowPC Downcount : %08x %d %p %p", currentMIPS->pc, downcount, count, membase, jitbase);
|
||||
} else {
|
||||
ERROR_LOG(JIT, "Universe corrupt?");
|
||||
}
|
||||
//if (count > 2000)
|
||||
// exit(0);
|
||||
count++;
|
||||
}
|
||||
|
||||
static void ShowBlockError(int type) {
|
||||
if (type == 1) {
|
||||
ERROR_LOG(JIT, "[%08x] ShowBlockError: block num was out of range in emuhack", currentMIPS->pc);
|
||||
} else if (type == 2) {
|
||||
ERROR_LOG(JIT, "[%08x] ShowBlockError: block num pointed to null jitblock", currentMIPS->pc);
|
||||
} else {
|
||||
ERROR_LOG(JIT, "[%08x] ShowBlockError: invalid error type", currentMIPS->pc);
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::GenerateFixedCode(const JitOptions &jo) {
|
||||
BeginWrite(GetMemoryProtectPageSize());
|
||||
const u8 *start = AlignCodePage();
|
||||
|
||||
if (jo.useStaticAlloc) {
|
||||
saveStaticRegisters_ = AlignCode16();
|
||||
SW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
gpr.EmitSaveStaticRegisters();
|
||||
RET();
|
||||
|
||||
loadStaticRegisters_ = AlignCode16();
|
||||
gpr.EmitLoadStaticRegisters();
|
||||
LW(DOWNCOUNTREG, CTXREG, offsetof(MIPSState, downcount));
|
||||
RET();
|
||||
|
||||
start = saveStaticRegisters_;
|
||||
} else {
|
||||
saveStaticRegisters_ = nullptr;
|
||||
loadStaticRegisters_ = nullptr;
|
||||
}
|
||||
|
||||
applyRoundingMode_ = AlignCode16();
|
||||
{
|
||||
// Not sure if RISC-V has any flush to zero capability? Leaving it off for now...
|
||||
LWU(SCRATCH2, CTXREG, offsetof(MIPSState, fcr31));
|
||||
|
||||
// We can skip if the rounding mode is nearest (0) and flush is not set.
|
||||
// (as restoreRoundingMode cleared it out anyway)
|
||||
FixupBranch skip = BEQ(SCRATCH2, R_ZERO);
|
||||
|
||||
// MIPS Rounding Mode: RISC-V
|
||||
// 0: Round nearest 0
|
||||
// 1: Round to zero 1
|
||||
// 2: Round up (ceil) 3
|
||||
// 3: Round down (floor) 2
|
||||
if (cpu_info.RiscV_Zbs) {
|
||||
BEXTI(SCRATCH1, SCRATCH2, 1);
|
||||
} else {
|
||||
ANDI(SCRATCH1, SCRATCH2, 2);
|
||||
SRLI(SCRATCH1, SCRATCH1, 1);
|
||||
}
|
||||
// Swap the lowest bit by the second bit.
|
||||
XOR(SCRATCH2, SCRATCH2, SCRATCH1);
|
||||
|
||||
FSRM(SCRATCH2);
|
||||
|
||||
SetJumpTarget(skip);
|
||||
RET();
|
||||
}
|
||||
|
||||
enterDispatcher_ = AlignCode16();
|
||||
|
||||
// Start by saving some regs on the stack. There are 12 GPs and 12 FPs we want.
|
||||
// Note: we leave R_SP as, well, SP, so it doesn't need to be saved.
|
||||
_assert_msg_(cpu_info.Mode64bit, "RiscVAsm currently assumes RV64, not RV32 or RV128");
|
||||
static constexpr RiscVReg regs_to_save[]{ R_RA, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27 };
|
||||
// TODO: Maybe we shouldn't regalloc all of these? Is it worth it?
|
||||
static constexpr RiscVReg regs_to_save_fp[]{ F8, F9, F18, F19, F20, F21, F22, F23, F24, F25, F26, F27 };
|
||||
int saveSize = (XLEN / 8) * (int)(ARRAY_SIZE(regs_to_save) + ARRAY_SIZE(regs_to_save_fp));
|
||||
if (saveSize & 0xF)
|
||||
saveSize += 8;
|
||||
_assert_msg_((saveSize & 0xF) == 0, "Stack must be kept aligned");
|
||||
int saveOffset = 0;
|
||||
ADDI(R_SP, R_SP, -saveSize);
|
||||
for (RiscVReg r : regs_to_save) {
|
||||
SD(r, R_SP, saveOffset);
|
||||
saveOffset += XLEN / 8;
|
||||
}
|
||||
for (RiscVReg r : regs_to_save_fp) {
|
||||
FS(64, r, R_SP, saveOffset);
|
||||
saveOffset += XLEN / 8;
|
||||
}
|
||||
_assert_(saveOffset <= saveSize);
|
||||
|
||||
// Fixed registers, these are always kept when in Jit context.
|
||||
LI(MEMBASEREG, Memory::base, SCRATCH1);
|
||||
LI(CTXREG, mips_, SCRATCH1);
|
||||
LI(JITBASEREG, GetBasePtr(), SCRATCH1);
|
||||
|
||||
LoadStaticRegisters();
|
||||
MovFromPC(SCRATCH1);
|
||||
outerLoopPCInSCRATCH1_ = GetCodePtr();
|
||||
MovToPC(SCRATCH1);
|
||||
outerLoop_ = GetCodePtr();
|
||||
// Advance can change the downcount (or thread), so must save/restore around it.
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
QuickCallFunction(&CoreTiming::Advance);
|
||||
ApplyRoundingMode(true);
|
||||
LoadStaticRegisters();
|
||||
|
||||
dispatcherCheckCoreState_ = GetCodePtr();
|
||||
LI(SCRATCH1, &coreState, SCRATCH2);
|
||||
LW(SCRATCH1, SCRATCH1, 0);
|
||||
FixupBranch badCoreState = BNE(SCRATCH1, R_ZERO);
|
||||
|
||||
// We just checked coreState, so go to advance if downcount is negative.
|
||||
BLT(DOWNCOUNTREG, R_ZERO, outerLoop_);
|
||||
FixupBranch skipToRealDispatch = J();
|
||||
|
||||
dispatcherPCInSCRATCH1_ = GetCodePtr();
|
||||
MovToPC(SCRATCH1);
|
||||
|
||||
dispatcher_ = GetCodePtr();
|
||||
FixupBranch bail = BLT(DOWNCOUNTREG, R_ZERO);
|
||||
SetJumpTarget(skipToRealDispatch);
|
||||
|
||||
dispatcherNoCheck_ = GetCodePtr();
|
||||
|
||||
// Debug
|
||||
if (enableDebug) {
|
||||
MV(X10, DOWNCOUNTREG);
|
||||
MV(X11, MEMBASEREG);
|
||||
MV(X12, JITBASEREG);
|
||||
QuickCallFunction(&ShowPC);
|
||||
}
|
||||
|
||||
LWU(SCRATCH1, CTXREG, offsetof(MIPSState, pc));
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
LI(SCRATCH2, 0x3FFFFFFF);
|
||||
AND(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
#endif
|
||||
ADD(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
dispatcherFetch_ = GetCodePtr();
|
||||
LWU(SCRATCH1, SCRATCH1, 0);
|
||||
SRLI(SCRATCH2, SCRATCH1, 24);
|
||||
// We're in other words comparing to the top 8 bits of MIPS_EMUHACK_OPCODE by subtracting.
|
||||
ADDI(SCRATCH2, SCRATCH2, -(MIPS_EMUHACK_OPCODE >> 24));
|
||||
FixupBranch needsCompile = BNE(SCRATCH2, R_ZERO);
|
||||
// Use a wall to mask by 0x00FFFFFF and extract the block jit offset.
|
||||
SLLI(SCRATCH1, SCRATCH1, XLEN - 24);
|
||||
SRLI(SCRATCH1, SCRATCH1, XLEN - 24);
|
||||
ADD(SCRATCH1, JITBASEREG, SCRATCH1);
|
||||
JR(SCRATCH1);
|
||||
SetJumpTarget(needsCompile);
|
||||
|
||||
// No block found, let's jit. We don't need to save static regs, they're all callee saved.
|
||||
RestoreRoundingMode(true);
|
||||
QuickCallFunction(&MIPSComp::JitAt);
|
||||
ApplyRoundingMode(true);
|
||||
|
||||
// Try again, the block index should be set now.
|
||||
J(dispatcherNoCheck_);
|
||||
|
||||
SetJumpTarget(bail);
|
||||
|
||||
LI(SCRATCH1, &coreState, SCRATCH2);
|
||||
LW(SCRATCH1, SCRATCH1, 0);
|
||||
BEQ(SCRATCH1, R_ZERO, outerLoop_);
|
||||
|
||||
const uint8_t *quitLoop = GetCodePtr();
|
||||
SetJumpTarget(badCoreState);
|
||||
|
||||
SaveStaticRegisters();
|
||||
RestoreRoundingMode(true);
|
||||
|
||||
_assert_msg_(cpu_info.Mode64bit, "RiscVAsm currently assumes RV64, not RV32 or RV128");
|
||||
saveOffset = 0;
|
||||
for (RiscVReg r : regs_to_save) {
|
||||
LD(r, R_SP, saveOffset);
|
||||
saveOffset += XLEN / 8;
|
||||
}
|
||||
for (RiscVReg r : regs_to_save_fp) {
|
||||
FL(64, r, R_SP, saveOffset);
|
||||
saveOffset += XLEN / 8;
|
||||
}
|
||||
ADDI(R_SP, R_SP, saveSize);
|
||||
|
||||
RET();
|
||||
|
||||
crashHandler_ = GetCodePtr();
|
||||
LI(SCRATCH1, &coreState, SCRATCH2);
|
||||
LI(SCRATCH2, CORE_RUNTIME_ERROR);
|
||||
SW(SCRATCH2, SCRATCH1, 0);
|
||||
J(quitLoop);
|
||||
|
||||
// Leave this at the end, add more stuff above.
|
||||
if (enableDisasm) {
|
||||
#if PPSSPP_ARCH(RISCV64)
|
||||
std::vector<std::string> lines = DisassembleRV64(start, GetCodePtr() - start);
|
||||
for (auto s : lines) {
|
||||
INFO_LOG(JIT, "%s", s.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Let's spare the pre-generated code from unprotect-reprotect.
|
||||
AlignCodePage();
|
||||
jitStartOffset_ = (int)(GetCodePtr() - start);
|
||||
// Don't forget to zap the instruction cache! This must stay at the end of this function.
|
||||
FlushIcache();
|
||||
EndWrite();
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
713
Core/MIPS/RiscV/RiscVCompALU.cpp
Normal file
713
Core/MIPS/RiscV/RiscVCompALU.cpp
Normal file
|
@ -0,0 +1,713 @@
|
|||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for integer / arithmetic / logic related instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_Arith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
bool allowPtrMath = true;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
// Since we modify it, we can't safely.
|
||||
allowPtrMath = false;
|
||||
#endif
|
||||
|
||||
// RISC-V only adds signed immediates, so rewrite a small enough subtract to an add.
|
||||
// We use -2047 and 2048 here because the range swaps.
|
||||
if (inst.op == IROp::SubConst && (int32_t)inst.constant >= -2047 && (int32_t)inst.constant <= 2048) {
|
||||
inst.op = IROp::AddConst;
|
||||
inst.constant = (uint32_t)-(int32_t)inst.constant;
|
||||
}
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Add:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
ADDW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Sub:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SUBW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::AddConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
// Typical of stack pointer updates.
|
||||
if (gpr.IsMappedAsPointer(inst.src1) && inst.dest == inst.src1 && allowPtrMath) {
|
||||
gpr.MarkPtrDirty(gpr.RPtr(inst.dest));
|
||||
ADDI(gpr.RPtr(inst.dest), gpr.RPtr(inst.dest), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
ADDIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
ADDW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SubConst:
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
SUBW(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::Neg:
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SUBW(gpr.R(inst.dest), R_ZERO, gpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Logic(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::And:
|
||||
if (inst.src1 != inst.src2) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
AND(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Or:
|
||||
if (inst.src1 != inst.src2) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
OR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
// If both were normalized before, the result is normalized.
|
||||
if (gpr.IsNormalized32(inst.src1) && gpr.IsNormalized32(inst.src2))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
} else if (inst.src1 != inst.dest) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Xor:
|
||||
if (inst.src1 == inst.src2) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
XOR(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::AndConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
ANDI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
AND(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
// If the sign bits aren't cleared, and it was normalized before - it still is.
|
||||
if ((inst.constant & 0x80000000) != 0 && gpr.IsNormalized32(inst.src1))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
// Otherwise, if we cleared the sign bits, it's naturally normalized.
|
||||
else if ((inst.constant & 0x80000000) == 0)
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::OrConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
ORI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
OR(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
// Since our constant is normalized, oring its bits in won't hurt normalization.
|
||||
if (gpr.IsNormalized32(inst.src1))
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
break;
|
||||
|
||||
case IROp::XorConst:
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
XORI(gpr.R(inst.dest), gpr.R(inst.src1), inst.constant);
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, (int32_t)inst.constant);
|
||||
XOR(gpr.R(inst.dest), gpr.R(inst.src1), SCRATCH1);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Not:
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
NOT(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Assign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Mov:
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Ext8to32:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SEXT_B(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 24);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 24);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Ext16to32:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SEXT_H(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLI(gpr.R(inst.dest), gpr.R(inst.src1), 16);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.dest), 16);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Bits(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ReverseBits:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::BSwap16:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::BSwap32:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
REV8(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
if (XLEN >= 64) {
|
||||
// REV8 swaps the entire register, so get the 32 highest bits.
|
||||
SRAI(gpr.R(inst.dest), gpr.R(inst.dest), XLEN - 32);
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
}
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Clz:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
// This even sets to 32 when zero, perfect.
|
||||
CLZW(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Shift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Shl:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Shr:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRLW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Sar:
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRAW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::Ror:
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
RORW(gpr.R(inst.dest), gpr.R(inst.src1), gpr.R(inst.src2));
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::ShlImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SLLIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::ShrImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SarImm:
|
||||
// Shouldn't happen, but let's be safe of any passes that modify the ops.
|
||||
if (inst.src2 >= 32) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.src1), 31);
|
||||
} else if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRAIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::RorImm:
|
||||
if (inst.src2 == 0) {
|
||||
if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
} else if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
RORIW(gpr.R(inst.dest), gpr.R(inst.src1), inst.src2 & 31);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Compare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Slt:
|
||||
gpr.SpillLock(inst.dest, inst.src1, inst.src2);
|
||||
gpr.MapReg(inst.src1);
|
||||
gpr.MapReg(inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1, inst.src2);
|
||||
|
||||
SLT(gpr.R(inst.dest), lhs, rhs);
|
||||
break;
|
||||
|
||||
case IROp::SltConst:
|
||||
if (inst.constant == 0) {
|
||||
// Basically, getting the sign bit. Let's shift instead.
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
SRLIW(gpr.R(inst.dest), gpr.R(inst.src1), 31);
|
||||
} else {
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
gpr.MapReg(inst.src1);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
SLTI(gpr.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLT(gpr.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::SltU:
|
||||
gpr.SpillLock(inst.dest, inst.src1, inst.src2);
|
||||
gpr.MapReg(inst.src1);
|
||||
gpr.MapReg(inst.src2);
|
||||
// It's still fine to sign extend, the biggest just get even bigger.
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1, inst.src2);
|
||||
|
||||
SLTU(gpr.R(inst.dest), lhs, rhs);
|
||||
break;
|
||||
|
||||
case IROp::SltUConst:
|
||||
if (inst.constant == 0) {
|
||||
gpr.SetImm(inst.dest, 0);
|
||||
} else {
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
gpr.MapReg(inst.src1);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, false);
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
// We sign extend because we're comparing against something normalized.
|
||||
// It's also the most efficient to set.
|
||||
if ((int32_t)inst.constant >= -2048 && (int32_t)inst.constant <= 2047) {
|
||||
SLTIU(gpr.R(inst.dest), lhs, (int32_t)inst.constant);
|
||||
} else {
|
||||
LI(SCRATCH2, (int32_t)inst.constant);
|
||||
SLTU(gpr.R(inst.dest), lhs, SCRATCH2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_CondAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
FixupBranch fixup;
|
||||
switch (inst.op) {
|
||||
case IROp::MovZ:
|
||||
case IROp::MovNZ:
|
||||
if (inst.dest == inst.src2)
|
||||
return;
|
||||
|
||||
// We could have a "zero" that with wrong upper due to XOR, so we have to normalize.
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH1, true);
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::MovZ:
|
||||
fixup = BNE(lhs, R_ZERO);
|
||||
break;
|
||||
case IROp::MovNZ:
|
||||
fixup = BEQ(lhs, R_ZERO);
|
||||
break;
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src2));
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::Max:
|
||||
if (inst.src1 != inst.src2) {
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MAX(gpr.R(inst.dest), lhs, rhs);
|
||||
// Because we had to normalize the inputs, the output is normalized.
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
} else if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::Min:
|
||||
if (inst.src1 != inst.src2) {
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
gpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MIN(gpr.R(inst.dest), lhs, rhs);
|
||||
// Because we had to normalize the inputs, the output is normalized.
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
} else {
|
||||
CompIR_Generic(inst);
|
||||
}
|
||||
} else if (inst.dest != inst.src1) {
|
||||
gpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
MV(gpr.R(inst.dest), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_HiLo(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::MtLo:
|
||||
gpr.MapDirtyIn(IRREG_LO, inst.src1);
|
||||
MV(gpr.R(IRREG_LO), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_LO), gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::MtHi:
|
||||
gpr.MapDirtyIn(IRREG_HI, inst.src1);
|
||||
MV(gpr.R(IRREG_HI), gpr.R(inst.src1));
|
||||
gpr.MarkDirty(gpr.R(IRREG_HI), gpr.IsNormalized32(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::MfLo:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_LO);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_LO));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_LO));
|
||||
break;
|
||||
|
||||
case IROp::MfHi:
|
||||
gpr.MapDirtyIn(inst.dest, IRREG_HI);
|
||||
MV(gpr.R(inst.dest), gpr.R(IRREG_HI));
|
||||
gpr.MarkDirty(gpr.R(inst.dest), gpr.IsNormalized32(IRREG_HI));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Mult(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
auto makeArgsUnsigned = [&](RiscVReg *lhs, RiscVReg *rhs) {
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
ZEXT_W(SCRATCH1, gpr.R(inst.src1));
|
||||
ZEXT_W(SCRATCH2, gpr.R(inst.src2));
|
||||
} else {
|
||||
SLLI(SCRATCH1, gpr.R(inst.src1), XLEN - 32);
|
||||
SRLI(SCRATCH1, SCRATCH1, XLEN - 32);
|
||||
SLLI(SCRATCH2, gpr.R(inst.src2), XLEN - 32);
|
||||
SRLI(SCRATCH2, SCRATCH2, XLEN - 32);
|
||||
}
|
||||
*lhs = SCRATCH1;
|
||||
*rhs = SCRATCH2;
|
||||
};
|
||||
auto combinePrevMulResult = [&] {
|
||||
// TODO: Using a single reg for HI/LO would make this less ugly.
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
ZEXT_W(gpr.R(IRREG_LO), gpr.R(IRREG_LO));
|
||||
} else {
|
||||
SLLI(gpr.R(IRREG_LO), gpr.R(IRREG_LO), XLEN - 32);
|
||||
SRLI(gpr.R(IRREG_LO), gpr.R(IRREG_LO), XLEN - 32);
|
||||
}
|
||||
SLLI(gpr.R(IRREG_HI), gpr.R(IRREG_HI), 32);
|
||||
OR(gpr.R(IRREG_LO), gpr.R(IRREG_LO), gpr.R(IRREG_HI));
|
||||
};
|
||||
auto splitMulResult = [&] {
|
||||
SRAI(gpr.R(IRREG_HI), gpr.R(IRREG_LO), 32);
|
||||
gpr.MarkDirty(gpr.R(IRREG_HI), true);
|
||||
};
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::Mult:
|
||||
// TODO: Maybe IR could simplify when HI is not needed or clobbered?
|
||||
// TODO: HI/LO merge optimization? Have to be careful of passes that split them...
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL(gpr.R(IRREG_LO), lhs, rhs);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::MultU:
|
||||
// This is an "anti-norm32" case. Let's just zero always.
|
||||
// TODO: If we could know that LO was only needed, we could use MULW and be done.
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2);
|
||||
makeArgsUnsigned(&lhs, &rhs);
|
||||
MUL(gpr.R(IRREG_LO), lhs, rhs);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::Madd:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
ADD(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::MaddU:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
makeArgsUnsigned(&lhs, &rhs);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
ADD(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::Msub:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
NormalizeSrc12(inst, &lhs, &rhs, SCRATCH1, SCRATCH2, true);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
SUB(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
case IROp::MsubU:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::ALWAYS_LOAD);
|
||||
makeArgsUnsigned(&lhs, &rhs);
|
||||
MUL(SCRATCH1, lhs, rhs);
|
||||
|
||||
combinePrevMulResult();
|
||||
SUB(gpr.R(IRREG_LO), gpr.R(IRREG_LO), SCRATCH1);
|
||||
splitMulResult();
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Div(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg numReg, denomReg;
|
||||
switch (inst.op) {
|
||||
case IROp::Div:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
// We have to do this because of the divide by zero and overflow checks below.
|
||||
NormalizeSrc12(inst, &numReg, &denomReg, SCRATCH1, SCRATCH2, true);
|
||||
DIVW(gpr.R(IRREG_LO), numReg, denomReg);
|
||||
REMW(gpr.R(IRREG_HI), numReg, denomReg);
|
||||
|
||||
// Now some tweaks for divide by zero and overflow.
|
||||
{
|
||||
// Start with divide by zero, remainder is fine.
|
||||
FixupBranch skipNonZero = BNE(denomReg, R_ZERO);
|
||||
FixupBranch keepNegOne = BGE(numReg, R_ZERO);
|
||||
LI(gpr.R(IRREG_LO), 1);
|
||||
SetJumpTarget(keepNegOne);
|
||||
SetJumpTarget(skipNonZero);
|
||||
|
||||
// For overflow, RISC-V sets LO right, but remainder to zero.
|
||||
// Cheating a bit by using R_RA as a temp...
|
||||
LI(R_RA, (int32_t)0x80000000);
|
||||
FixupBranch notMostNegative = BNE(numReg, R_RA);
|
||||
LI(R_RA, -1);
|
||||
FixupBranch notNegativeOne = BNE(denomReg, R_RA);
|
||||
LI(gpr.R(IRREG_HI), -1);
|
||||
SetJumpTarget(notNegativeOne);
|
||||
SetJumpTarget(notMostNegative);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::DivU:
|
||||
gpr.MapDirtyDirtyInIn(IRREG_LO, IRREG_HI, inst.src1, inst.src2, MapType::AVOID_LOAD_MARK_NORM32);
|
||||
// We have to do this because of the divide by zero check below.
|
||||
NormalizeSrc12(inst, &numReg, &denomReg, SCRATCH1, SCRATCH2, true);
|
||||
DIVUW(gpr.R(IRREG_LO), numReg, denomReg);
|
||||
REMUW(gpr.R(IRREG_HI), numReg, denomReg);
|
||||
|
||||
// On divide by zero, everything is correct already except the 0xFFFF case.
|
||||
{
|
||||
FixupBranch skipNonZero = BNE(denomReg, R_ZERO);
|
||||
// Luckily, we don't need SCRATCH2/denomReg anymore.
|
||||
LI(SCRATCH2, 0xFFFF);
|
||||
FixupBranch keepNegOne = BLTU(SCRATCH2, numReg);
|
||||
MV(gpr.R(IRREG_LO), SCRATCH2);
|
||||
SetJumpTarget(keepNegOne);
|
||||
SetJumpTarget(skipNonZero);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
147
Core/MIPS/RiscV/RiscVCompBranch.cpp
Normal file
147
Core/MIPS/RiscV/RiscVCompBranch.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for exits.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_Exit(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg exitReg = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConst:
|
||||
FlushAll();
|
||||
LI(SCRATCH1, inst.constant);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
break;
|
||||
|
||||
case IROp::ExitToReg:
|
||||
exitReg = gpr.MapReg(inst.src1);
|
||||
FlushAll();
|
||||
// TODO: If ever we don't read this back in dispatcherPCInSCRATCH1_, we should zero upper.
|
||||
MV(SCRATCH1, exitReg);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
break;
|
||||
|
||||
case IROp::ExitToPC:
|
||||
FlushAll();
|
||||
QuickJ(R_RA, dispatcherCheckCoreState_);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_ExitIf(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg lhs = INVALID_REG;
|
||||
RiscVReg rhs = INVALID_REG;
|
||||
FixupBranch fixup;
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfEq:
|
||||
case IROp::ExitToConstIfNeq:
|
||||
gpr.MapInIn(inst.src1, inst.src2);
|
||||
// We can't use SCRATCH1, which is destroyed by FlushAll()... but cheat and use R_RA.
|
||||
NormalizeSrc12(inst, &lhs, &rhs, R_RA, SCRATCH2, true);
|
||||
FlushAll();
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfEq:
|
||||
fixup = BNE(lhs, rhs);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfNeq:
|
||||
fixup = BEQ(lhs, rhs);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
LI(SCRATCH1, inst.constant);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
case IROp::ExitToConstIfGeZ:
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
gpr.MapReg(inst.src1);
|
||||
NormalizeSrc1(inst, &lhs, SCRATCH2, true);
|
||||
FlushAll();
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::ExitToConstIfGtZ:
|
||||
fixup = BGE(R_ZERO, lhs);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfGeZ:
|
||||
fixup = BLT(lhs, R_ZERO);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfLtZ:
|
||||
fixup = BGE(lhs, R_ZERO);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfLeZ:
|
||||
fixup = BLT(R_ZERO, lhs);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
LI(SCRATCH1, inst.constant);
|
||||
QuickJ(R_RA, dispatcherPCInSCRATCH1_);
|
||||
SetJumpTarget(fixup);
|
||||
break;
|
||||
|
||||
case IROp::ExitToConstIfFpTrue:
|
||||
case IROp::ExitToConstIfFpFalse:
|
||||
// Note: not used.
|
||||
DISABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
644
Core/MIPS/RiscV/RiscVCompFPU.cpp
Normal file
644
Core/MIPS/RiscV/RiscVCompFPU.cpp
Normal file
|
@ -0,0 +1,644 @@
|
|||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for floating point related instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::CompIR_FArith(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FAdd:
|
||||
fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
FADD(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FSub:
|
||||
fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
FSUB(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FMul:
|
||||
fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
// TODO: If FMUL consistently produces NAN across chip vendors, we can skip this.
|
||||
// Luckily this does match the RISC-V canonical NAN.
|
||||
if (inst.src1 != inst.src2) {
|
||||
// These will output 0x80/0x01 if infinity, 0x10/0x80 if zero.
|
||||
// We need to check if one is infinity and the other zero.
|
||||
|
||||
// First, try inf * zero.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
FCLASS(32, SCRATCH2, fpr.R(inst.src2));
|
||||
ANDI(R_RA, SCRATCH1, 0x81);
|
||||
FixupBranch lhsNotInf = BEQ(R_RA, R_ZERO);
|
||||
ANDI(R_RA, SCRATCH2, 0x18);
|
||||
FixupBranch infZero = BNE(R_RA, R_ZERO);
|
||||
|
||||
// Okay, what about the other order?
|
||||
SetJumpTarget(lhsNotInf);
|
||||
ANDI(R_RA, SCRATCH1, 0x18);
|
||||
FixupBranch lhsNotZero = BEQ(R_RA, R_ZERO);
|
||||
ANDI(R_RA, SCRATCH2, 0x81);
|
||||
FixupBranch zeroInf = BNE(R_RA, R_ZERO);
|
||||
|
||||
// Nope, all good.
|
||||
SetJumpTarget(lhsNotZero);
|
||||
FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
FixupBranch skip = J();
|
||||
|
||||
SetJumpTarget(infZero);
|
||||
SetJumpTarget(zeroInf);
|
||||
LI(SCRATCH1, 0x7FC00000);
|
||||
FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1);
|
||||
|
||||
SetJumpTarget(skip);
|
||||
} else {
|
||||
FMUL(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FDiv:
|
||||
fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
FDIV(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IROp::FSqrt:
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
FSQRT(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FNeg:
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
FNEG(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FCondAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
if (inst.op != IROp::FMin && inst.op != IROp::FMax)
|
||||
INVALIDOP;
|
||||
bool maxCondition = inst.op == IROp::FMax;
|
||||
|
||||
// FMin and FMax are used by VFPU and handle NAN/INF as just a larger exponent.
|
||||
fpr.MapDirtyInIn(inst.dest, inst.src1, inst.src2);
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
FCLASS(32, SCRATCH2, fpr.R(inst.src2));
|
||||
|
||||
// If either side is a NAN, it needs to participate in the comparison.
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
// NAN is either 0x100 or 0x200.
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x300);
|
||||
FixupBranch useNormalCond = BEQ(SCRATCH1, R_ZERO);
|
||||
|
||||
// Time to use bits... classify won't help because it ignores -NAN.
|
||||
FMV(FMv::X, FMv::W, SCRATCH1, fpr.R(inst.src1));
|
||||
FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src2));
|
||||
|
||||
// If both are negative, we flip the comparison (not two's compliment.)
|
||||
// We cheat and use RA...
|
||||
AND(R_RA, SCRATCH1, SCRATCH2);
|
||||
SRLIW(R_RA, R_RA, 31);
|
||||
|
||||
if (cpu_info.RiscV_Zbb) {
|
||||
FixupBranch swapCompare = BNE(R_RA, R_ZERO);
|
||||
if (maxCondition)
|
||||
MAX(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
else
|
||||
MIN(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
FixupBranch skipSwapCompare = J();
|
||||
SetJumpTarget(swapCompare);
|
||||
if (maxCondition)
|
||||
MIN(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
else
|
||||
MAX(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
SetJumpTarget(skipSwapCompare);
|
||||
} else {
|
||||
RiscVReg isSrc1LowerReg = gpr.GetAndLockTempR();
|
||||
gpr.ReleaseSpillLocksAndDiscardTemps();
|
||||
|
||||
SLT(isSrc1LowerReg, SCRATCH1, SCRATCH2);
|
||||
// Flip the flag (to reverse the min/max) based on if both were negative.
|
||||
XOR(isSrc1LowerReg, isSrc1LowerReg, R_RA);
|
||||
FixupBranch useSrc1;
|
||||
if (maxCondition)
|
||||
useSrc1 = BEQ(isSrc1LowerReg, R_ZERO);
|
||||
else
|
||||
useSrc1 = BNE(isSrc1LowerReg, R_ZERO);
|
||||
MV(SCRATCH1, SCRATCH2);
|
||||
SetJumpTarget(useSrc1);
|
||||
}
|
||||
|
||||
FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1);
|
||||
FixupBranch finish = J();
|
||||
|
||||
SetJumpTarget(useNormalCond);
|
||||
if (maxCondition)
|
||||
FMAX(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
else
|
||||
FMIN(32, fpr.R(inst.dest), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
SetJumpTarget(finish);
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FAssign(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FMov:
|
||||
if (inst.dest != inst.src1) {
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
FMV(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FAbs:
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
FABS(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
break;
|
||||
|
||||
case IROp::FSign:
|
||||
{
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
// Check if it's negative zero, either 0x10/0x08 is zero.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x18);
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
// Okay, it's zero if zero, 1 otherwise. Convert 1 to a constant 1.0.
|
||||
// Probably non-zero is the common case, so we make that the straight line.
|
||||
FixupBranch skipOne = BEQ(SCRATCH1, R_ZERO);
|
||||
LI(SCRATCH1, 1.0f);
|
||||
|
||||
// Now we just need the sign from it.
|
||||
FMV(FMv::X, FMv::W, SCRATCH2, fpr.R(inst.src1));
|
||||
// Use a wall to isolate the sign, and combine.
|
||||
SRAIW(SCRATCH2, SCRATCH2, 31);
|
||||
SLLIW(SCRATCH2, SCRATCH2, 31);
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
|
||||
SetJumpTarget(skipOne);
|
||||
FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FRound(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
// TODO: If this is followed by a GPR transfer, might want to combine.
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FRound:
|
||||
FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::NEAREST_EVEN);
|
||||
break;
|
||||
|
||||
case IROp::FTrunc:
|
||||
FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::TOZERO);
|
||||
break;
|
||||
|
||||
case IROp::FCeil:
|
||||
FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::UP);
|
||||
break;
|
||||
|
||||
case IROp::FFloor:
|
||||
FCVT(FConv::W, FConv::S, SCRATCH1, fpr.R(inst.src1), Round::DOWN);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
|
||||
FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1);
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FCvt(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FCvtWS:
|
||||
case IROp::FCvtScaledWS:
|
||||
case IROp::FCvtScaledSW:
|
||||
CompIR_Generic(inst);
|
||||
break;
|
||||
|
||||
case IROp::FCvtSW:
|
||||
// TODO: This is probably proceeded by a GPR transfer, might be ideal to combine.
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
FMV(FMv::X, FMv::W, SCRATCH1, fpr.R(inst.src1));
|
||||
FCVT(FConv::S, FConv::W, fpr.R(inst.dest), SCRATCH1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FSat(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg tempReg = INVALID_REG;
|
||||
FixupBranch skipLower;
|
||||
FixupBranch finishLower;
|
||||
FixupBranch skipHigher;
|
||||
switch (inst.op) {
|
||||
case IROp::FSat0_1:
|
||||
tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1);
|
||||
if (inst.dest != inst.src1)
|
||||
FMV(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
|
||||
// First, set SCRATCH1 = clamp to zero, SCRATCH2 = clamp to one.
|
||||
FCVT(FConv::S, FConv::W, tempReg, R_ZERO);
|
||||
// FLE here is intentional to convert -0.0 to +0.0.
|
||||
FLE(32, SCRATCH1, fpr.R(inst.src1), tempReg);
|
||||
LI(SCRATCH2, 1.0f);
|
||||
FMV(FMv::W, FMv::X, tempReg, SCRATCH2);
|
||||
FLT(32, SCRATCH2, tempReg, fpr.R(inst.src1));
|
||||
|
||||
skipLower = BEQ(SCRATCH1, R_ZERO);
|
||||
FCVT(FConv::S, FConv::W, fpr.R(inst.dest), R_ZERO);
|
||||
finishLower = J();
|
||||
|
||||
SetJumpTarget(skipLower);
|
||||
skipHigher = BEQ(SCRATCH2, R_ZERO);
|
||||
// Still has 1.0 in it.
|
||||
FMV(32, fpr.R(inst.dest), tempReg);
|
||||
|
||||
SetJumpTarget(finishLower);
|
||||
SetJumpTarget(skipHigher);
|
||||
break;
|
||||
|
||||
case IROp::FSatMinus1_1:
|
||||
tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1);
|
||||
if (inst.dest != inst.src1)
|
||||
FMV(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
|
||||
// First, set SCRATCH1 = clamp to negative, SCRATCH2 = clamp to positive.
|
||||
LI(SCRATCH2, -1.0f);
|
||||
FMV(FMv::W, FMv::X, tempReg, SCRATCH2);
|
||||
FLT(32, SCRATCH1, fpr.R(inst.src1), tempReg);
|
||||
FNEG(32, tempReg, tempReg);
|
||||
FLT(32, SCRATCH2, tempReg, fpr.R(inst.src1));
|
||||
|
||||
// But we can actually do one branch, using sign-injection to keep the original sign.
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
|
||||
skipLower = BEQ(SCRATCH1, R_ZERO);
|
||||
FSGNJ(32, fpr.R(inst.dest), tempReg, fpr.R(inst.dest));
|
||||
SetJumpTarget(skipLower);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FCompare(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
constexpr IRRegIndex IRREG_VFPUL_CC = IRREG_VFPU_CTRL_BASE + VFPU_CTRL_CC;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::FCmp:
|
||||
switch (inst.dest) {
|
||||
case IRFpCompareMode::False:
|
||||
gpr.SetImm(IRREG_FPCOND, 0);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EitherUnordered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
FCLASS(32, SCRATCH2, fpr.R(inst.src2));
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
// NAN is 0x100 or 0x200.
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x300);
|
||||
SNEZ(gpr.R(IRREG_FPCOND), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EqualOrdered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FEQ(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::EqualUnordered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FEQ(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
|
||||
// Now let's just OR in the unordered check.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
FCLASS(32, SCRATCH2, fpr.R(inst.src2));
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
// NAN is 0x100 or 0x200.
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x300);
|
||||
SNEZ(SCRATCH1, SCRATCH1);
|
||||
OR(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessEqualOrdered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FLE(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessEqualUnordered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FLT(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src2), fpr.R(inst.src1));
|
||||
SEQZ(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND));
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessOrdered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FLT(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
|
||||
case IRFpCompareMode::LessUnordered:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
gpr.MapReg(IRREG_FPCOND, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
FLE(32, gpr.R(IRREG_FPCOND), fpr.R(inst.src2), fpr.R(inst.src1));
|
||||
SEQZ(gpr.R(IRREG_FPCOND), gpr.R(IRREG_FPCOND));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FCmovVfpuCC:
|
||||
gpr.MapReg(IRREG_VFPUL_CC);
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1, false);
|
||||
if ((inst.src2 & 0xF) == 0) {
|
||||
ANDI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), 1);
|
||||
} else if (cpu_info.RiscV_Zbs) {
|
||||
BEXTI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), inst.src2 & 0xF);
|
||||
} else {
|
||||
SRLI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), inst.src2 & 0xF);
|
||||
ANDI(SCRATCH1, SCRATCH1, 1);
|
||||
}
|
||||
if ((inst.src2 >> 7) & 1) {
|
||||
FixupBranch skip = BEQ(SCRATCH1, R_ZERO);
|
||||
FMV(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
SetJumpTarget(skip);
|
||||
} else {
|
||||
FixupBranch skip = BNE(SCRATCH1, R_ZERO);
|
||||
FMV(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
SetJumpTarget(skip);
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FCmpVfpuBit:
|
||||
gpr.MapReg(IRREG_VFPUL_CC, MIPSMap::DIRTY);
|
||||
|
||||
switch (VCondition(inst.dest & 0xF)) {
|
||||
case VC_EQ:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
FEQ(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
case VC_NE:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
// We could almost negate FEQ, except NAN != NAN.
|
||||
// Anything != NAN is false and NAN != NAN is within that, so we only check one side.
|
||||
FCLASS(32, SCRATCH2, fpr.R(inst.src2));
|
||||
// NAN is 0x100 or 0x200.
|
||||
ANDI(SCRATCH2, SCRATCH2, 0x300);
|
||||
SNEZ(SCRATCH2, SCRATCH2);
|
||||
|
||||
FEQ(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
// Just OR in whether that side was a NAN so it's always not equal.
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
break;
|
||||
case VC_LT:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
FLT(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
case VC_LE:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
FLE(32, SCRATCH1, fpr.R(inst.src1), fpr.R(inst.src2));
|
||||
break;
|
||||
case VC_GT:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
FLT(32, SCRATCH1, fpr.R(inst.src2), fpr.R(inst.src1));
|
||||
break;
|
||||
case VC_GE:
|
||||
fpr.MapInIn(inst.src1, inst.src2);
|
||||
FLE(32, SCRATCH1, fpr.R(inst.src2), fpr.R(inst.src1));
|
||||
break;
|
||||
case VC_EZ:
|
||||
case VC_NZ:
|
||||
fpr.MapReg(inst.src1);
|
||||
// Zero is either 0x10 or 0x08.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x18);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SNEZ(SCRATCH1, SCRATCH1);
|
||||
else
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
break;
|
||||
case VC_EN:
|
||||
case VC_NN:
|
||||
fpr.MapReg(inst.src1);
|
||||
// NAN is either 0x100 or 0x200.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x300);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SNEZ(SCRATCH1, SCRATCH1);
|
||||
else
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
break;
|
||||
case VC_EI:
|
||||
case VC_NI:
|
||||
fpr.MapReg(inst.src1);
|
||||
// Infinity is either 0x80 or 0x01.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x81);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SNEZ(SCRATCH1, SCRATCH1);
|
||||
else
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
break;
|
||||
case VC_ES:
|
||||
case VC_NS:
|
||||
fpr.MapReg(inst.src1);
|
||||
// Infinity is either 0x80 or 0x01, NAN is either 0x100 or 0x200.
|
||||
FCLASS(32, SCRATCH1, fpr.R(inst.src1));
|
||||
ANDI(SCRATCH1, SCRATCH1, 0x381);
|
||||
if ((inst.dest & 4) == 0)
|
||||
SNEZ(SCRATCH1, SCRATCH1);
|
||||
else
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
break;
|
||||
case VC_TR:
|
||||
LI(SCRATCH1, 1);
|
||||
break;
|
||||
case VC_FL:
|
||||
LI(SCRATCH1, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
ANDI(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), ~(1 << (inst.dest >> 4)));
|
||||
if ((inst.dest >> 4) != 0)
|
||||
SLLI(SCRATCH1, SCRATCH1, inst.dest >> 4);
|
||||
OR(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), SCRATCH1);
|
||||
break;
|
||||
|
||||
case IROp::FCmpVfpuAggregate:
|
||||
gpr.MapReg(IRREG_VFPUL_CC, MIPSMap::DIRTY);
|
||||
ANDI(SCRATCH1, gpr.R(IRREG_VFPUL_CC), inst.dest);
|
||||
// This is the "any bit", easy.
|
||||
SNEZ(SCRATCH2, SCRATCH1);
|
||||
// To compare to inst.dest for "all", let's simply subtract it and compare to zero.
|
||||
ADDI(SCRATCH1, SCRATCH1, -inst.dest);
|
||||
SEQZ(SCRATCH1, SCRATCH1);
|
||||
// Now we combine those together.
|
||||
SLLI(SCRATCH1, SCRATCH1, 5);
|
||||
SLLI(SCRATCH2, SCRATCH2, 4);
|
||||
OR(SCRATCH1, SCRATCH1, SCRATCH2);
|
||||
|
||||
// Reject those any/all bits and replace them with our own.
|
||||
ANDI(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), ~0x30);
|
||||
OR(gpr.R(IRREG_VFPUL_CC), gpr.R(IRREG_VFPUL_CC), SCRATCH1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_RoundingMode(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::RestoreRoundingMode:
|
||||
RestoreRoundingMode();
|
||||
break;
|
||||
|
||||
case IROp::ApplyRoundingMode:
|
||||
ApplyRoundingMode();
|
||||
break;
|
||||
|
||||
case IROp::UpdateRoundingMode:
|
||||
// We don't need to do anything, instructions allow a "dynamic" rounding mode.
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FSpecial(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
#ifdef __riscv_float_abi_soft
|
||||
#error Currently hard float is required.
|
||||
#endif
|
||||
|
||||
auto callFuncF_F = [&](float (*func)(float)){
|
||||
gpr.FlushBeforeCall();
|
||||
fpr.FlushBeforeCall();
|
||||
// It might be in a non-volatile register.
|
||||
if (fpr.IsMapped(inst.src1)) {
|
||||
FMV(32, F10, fpr.R(inst.src1));
|
||||
} else {
|
||||
int offset = offsetof(MIPSState, f) + inst.src1 * 4;
|
||||
FL(32, F10, CTXREG, offset);
|
||||
}
|
||||
QuickCallFunction(func);
|
||||
|
||||
fpr.MapReg(inst.dest, MIPSMap::NOINIT);
|
||||
// If it's already F10, we're done - MapReg doesn't actually overwrite the reg in that case.
|
||||
if (fpr.R(inst.dest) != F10) {
|
||||
FMV(32, fpr.R(inst.dest), F10);
|
||||
}
|
||||
};
|
||||
|
||||
RiscVReg tempReg = INVALID_REG;
|
||||
switch (inst.op) {
|
||||
case IROp::FSin:
|
||||
callFuncF_F(&vfpu_sin);
|
||||
break;
|
||||
|
||||
case IROp::FCos:
|
||||
callFuncF_F(&vfpu_cos);
|
||||
break;
|
||||
|
||||
case IROp::FRSqrt:
|
||||
tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1);
|
||||
FSQRT(32, fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
|
||||
// Ugh, we can't really avoid a temp here. Probably not worth a permanent one.
|
||||
LI(SCRATCH1, 1.0f);
|
||||
FMV(FMv::W, FMv::X, tempReg, SCRATCH1);
|
||||
FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.dest));
|
||||
break;
|
||||
|
||||
case IROp::FRecip:
|
||||
if (inst.dest != inst.src1) {
|
||||
// This is the easy case.
|
||||
fpr.MapDirtyIn(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, 1.0f);
|
||||
FMV(FMv::W, FMv::X, fpr.R(inst.dest), SCRATCH1);
|
||||
FDIV(32, fpr.R(inst.dest), fpr.R(inst.dest), fpr.R(inst.src1));
|
||||
} else {
|
||||
tempReg = fpr.MapDirtyInTemp(inst.dest, inst.src1);
|
||||
LI(SCRATCH1, 1.0f);
|
||||
FMV(FMv::W, FMv::X, tempReg, SCRATCH1);
|
||||
FDIV(32, fpr.R(inst.dest), tempReg, fpr.R(inst.src1));
|
||||
}
|
||||
break;
|
||||
|
||||
case IROp::FAsin:
|
||||
callFuncF_F(&vfpu_asin);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
386
Core/MIPS/RiscV/RiscVCompLoadStore.cpp
Normal file
386
Core/MIPS/RiscV/RiscVCompLoadStore.cpp
Normal file
|
@ -0,0 +1,386 @@
|
|||
// Copyright (c) 2023- PPSSPP Project.
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, version 2.0 or later versions.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License 2.0 for more details.
|
||||
|
||||
// A copy of the GPL 2.0 should have been included with the program.
|
||||
// If not, see http://www.gnu.org/licenses/
|
||||
|
||||
// Official git repository and contact information can be found at
|
||||
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
||||
|
||||
#include "Core/MemMap.h"
|
||||
#include "Core/MIPS/RiscV/RiscVJit.h"
|
||||
#include "Core/MIPS/RiscV/RiscVRegCache.h"
|
||||
|
||||
// This file contains compilation for load/store instructions.
|
||||
//
|
||||
// All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly.
|
||||
// Currently known non working ones should have DISABLE. No flags because that's in IR already.
|
||||
|
||||
// #define CONDITIONAL_DISABLE { CompIR_Generic(inst); return; }
|
||||
#define CONDITIONAL_DISABLE {}
|
||||
#define DISABLE { CompIR_Generic(inst); return; }
|
||||
#define INVALIDOP { _assert_msg_(false, "Invalid IR inst %d", (int)inst.op); CompIR_Generic(inst); return; }
|
||||
|
||||
namespace MIPSComp {
|
||||
|
||||
using namespace RiscVGen;
|
||||
using namespace RiscVJitConstants;
|
||||
|
||||
void RiscVJit::SetScratch1ToSrc1Address(IRReg src1) {
|
||||
gpr.MapReg(src1);
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
SLLIW(SCRATCH1, gpr.R(src1), 2);
|
||||
SRLIW(SCRATCH1, SCRATCH1, 2);
|
||||
ADD(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
#else
|
||||
// Clear the top bits to be safe.
|
||||
if (cpu_info.RiscV_Zba) {
|
||||
ADD_UW(SCRATCH1, gpr.R(src1), MEMBASEREG);
|
||||
} else {
|
||||
_assert_(XLEN == 64);
|
||||
SLLI(SCRATCH1, gpr.R(src1), 32);
|
||||
SRLI(SCRATCH1, SCRATCH1, 32);
|
||||
ADD(SCRATCH1, SCRATCH1, MEMBASEREG);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int32_t RiscVJit::AdjustForAddressOffset(RiscVGen::RiscVReg *reg, int32_t constant, int32_t range) {
|
||||
if (constant < -2048 || constant + range > 2047) {
|
||||
LI(SCRATCH2, constant);
|
||||
ADD(SCRATCH1, *reg, SCRATCH2);
|
||||
*reg = SCRATCH1;
|
||||
return 0;
|
||||
}
|
||||
return constant;
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Load(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
gpr.SpillLock(inst.dest, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
// If they're the same, MapReg may subtract MEMBASEREG, so just mark dirty.
|
||||
if (inst.dest == inst.src1)
|
||||
gpr.MarkDirty(gpr.R(inst.dest), true);
|
||||
else
|
||||
gpr.MapReg(inst.dest, MIPSMap::NOINIT | MIPSMap::MARK_NORM32);
|
||||
gpr.ReleaseSpillLock(inst.dest, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Load8:
|
||||
LBU(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load8Ext:
|
||||
LB(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load16:
|
||||
LHU(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load16Ext:
|
||||
LH(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load32:
|
||||
LW(gpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Load32Linked:
|
||||
if (inst.dest != MIPS_REG_ZERO)
|
||||
LW(gpr.R(inst.dest), addrReg, imm);
|
||||
gpr.SetImm(IRREG_LLBIT, 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_LoadShift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Load32Left:
|
||||
case IROp::Load32Right:
|
||||
// Should not happen if the pass to split is active.
|
||||
DISABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FLoad(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::LoadFloat:
|
||||
fpr.MapReg(inst.dest, MIPSMap::NOINIT);
|
||||
FL(32, fpr.R(inst.dest), addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecLoad(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
// We need to be able to address the whole 16 bytes, so offset of 12.
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant, 12);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::LoadVec4:
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// Spilling is okay.
|
||||
fpr.MapReg(inst.dest + i, MIPSMap::NOINIT);
|
||||
FL(32, fpr.R(inst.dest + i), addrReg, imm + 4 * i);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_Store(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
gpr.SpillLock(inst.src3, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
RiscVReg valueReg = gpr.TryMapTempImm(inst.src3);
|
||||
if (valueReg == INVALID_REG)
|
||||
valueReg = gpr.MapReg(inst.src3);
|
||||
gpr.ReleaseSpillLock(inst.src3, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Store8:
|
||||
SB(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Store16:
|
||||
SH(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
case IROp::Store32:
|
||||
SW(valueReg, addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_CondStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
if (inst.op != IROp::Store32Conditional)
|
||||
INVALIDOP;
|
||||
|
||||
gpr.SpillLock(IRREG_LLBIT, inst.src3, inst.src1);
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if ((jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) && inst.src3 != inst.src1) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
gpr.MapReg(inst.src3, inst.dest == MIPS_REG_ZERO ? MIPSMap::INIT : MIPSMap::DIRTY);
|
||||
gpr.MapReg(IRREG_LLBIT);
|
||||
gpr.ReleaseSpillLock(IRREG_LLBIT, inst.src3, inst.src1);
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
FixupBranch condFailed = BEQ(gpr.R(IRREG_LLBIT), R_ZERO);
|
||||
SW(gpr.R(inst.src3), addrReg, imm);
|
||||
|
||||
if (inst.dest != MIPS_REG_ZERO) {
|
||||
LI(gpr.R(inst.dest), 1);
|
||||
FixupBranch finish = J();
|
||||
|
||||
SetJumpTarget(condFailed);
|
||||
LI(gpr.R(inst.dest), 0);
|
||||
SetJumpTarget(finish);
|
||||
} else {
|
||||
SetJumpTarget(condFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_StoreShift(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::Store32Left:
|
||||
case IROp::Store32Right:
|
||||
// Should not happen if the pass to split is active.
|
||||
DISABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_FStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::StoreFloat:
|
||||
fpr.MapReg(inst.src3);
|
||||
FS(32, fpr.R(inst.src3), addrReg, imm);
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RiscVJit::CompIR_VecStore(IRInst inst) {
|
||||
CONDITIONAL_DISABLE;
|
||||
|
||||
RiscVReg addrReg = INVALID_REG;
|
||||
if (inst.src1 == MIPS_REG_ZERO) {
|
||||
// This will get changed by AdjustForAddressOffset.
|
||||
addrReg = MEMBASEREG;
|
||||
#ifdef MASKED_PSP_MEMORY
|
||||
inst.constant &= Memory::MEMVIEW32_MASK;
|
||||
#endif
|
||||
} else if (jo.cachePointers || gpr.IsMappedAsPointer(inst.src1)) {
|
||||
addrReg = gpr.MapRegAsPointer(inst.src1);
|
||||
} else {
|
||||
SetScratch1ToSrc1Address(inst.src1);
|
||||
addrReg = SCRATCH1;
|
||||
}
|
||||
|
||||
// We need to be able to address the whole 16 bytes, so offset of 12.
|
||||
s32 imm = AdjustForAddressOffset(&addrReg, inst.constant, 12);
|
||||
|
||||
// TODO: Safe memory? Or enough to have crash handler + validate?
|
||||
|
||||
switch (inst.op) {
|
||||
case IROp::StoreVec4:
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
// Spilling is okay, though not ideal.
|
||||
fpr.MapReg(inst.src3 + i);
|
||||
FS(32, fpr.R(inst.src3 + i), addrReg, imm + 4 * i);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
INVALIDOP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MIPSComp
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue