Merge branch 'master' into master

This commit is contained in:
Bashar Astifan 2023-08-01 05:29:56 +04:00 committed by GitHub
commit 401377818c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
249 changed files with 11459 additions and 2236 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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" />

View file

@ -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">

View file

@ -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];

View file

@ -41,6 +41,7 @@ static const char * const g_categoryNames[(size_t)I18NCat::CATEGORY_COUNT] = {
"Upgrade",
"VR",
"Achievements",
"PSPSettings",
};
I18NRepo g_i18nrepo;

View file

@ -57,6 +57,7 @@ enum class I18NCat : uint8_t {
UPGRADE,
VR,
ACHIEVEMENTS,
PSPSETTINGS,
CATEGORY_COUNT,
NONE = CATEGORY_COUNT,
};

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -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) {

View file

@ -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);
};

View file

@ -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++;
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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
View 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
View 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

View file

@ -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());

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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)) {

View file

@ -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);

View file

@ -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;

View file

@ -1,4 +1,5 @@
#include "ppsspp_config.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Common/System/Display.h"

View 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

View 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
};

View file

@ -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" },
};

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;
};

View file

@ -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) {

View file

@ -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,

View file

@ -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;
}

View file

@ -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:

View file

@ -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));

View file

@ -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 {

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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" />

View file

@ -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" />

View file

@ -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;

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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();

View file

@ -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"));
}

View file

@ -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);

View file

@ -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());
}

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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());
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;
}

View file

@ -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) {

View file

@ -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
View 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
View 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);

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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]);
}
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
};

View file

@ -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;

View file

@ -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();
}

View file

@ -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

View file

@ -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:

View file

@ -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);
}

View file

@ -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_;
};

View file

@ -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;

View file

@ -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;

View file

@ -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 {}

View file

@ -813,7 +813,7 @@ namespace MIPSAnalyst {
break;
}
if (reg > 32) {
if (reg >= 32) {
return USAGE_UNKNOWN;
}

View file

@ -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,

View 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

View 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

View 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

View 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

View 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