mirror of
https://github.com/weihuoya/citra.git
synced 2026-01-25 04:18:23 +00:00
cpu usage limit
This commit is contained in:
7
.gitmodules
vendored
7
.gitmodules
vendored
@@ -44,5 +44,8 @@
|
||||
path = externals/teakra
|
||||
url = https://github.com/wwylele/teakra.git
|
||||
[submodule "externals/oboe"]
|
||||
path = externals/oboe
|
||||
url = https://github.com/google/oboe.git
|
||||
path = externals/oboe
|
||||
url = https://github.com/google/oboe.git
|
||||
[submodule "libyuv"]
|
||||
path = externals/libyuv
|
||||
url = https://github.com/lemenkov/libyuv.git
|
||||
|
||||
11
externals/CMakeLists.txt
vendored
11
externals/CMakeLists.txt
vendored
@@ -34,9 +34,11 @@ endif()
|
||||
# Glad
|
||||
add_subdirectory(glad)
|
||||
|
||||
if (ANDROID)
|
||||
add_subdirectory(oboe)
|
||||
else()
|
||||
# libyuv
|
||||
add_subdirectory(libyuv)
|
||||
target_include_directories(yuv INTERFACE ./libyuv/include)
|
||||
|
||||
if (NOT ANDROID)
|
||||
# inih
|
||||
add_subdirectory(inih)
|
||||
endif()
|
||||
@@ -52,6 +54,9 @@ target_include_directories(nihstro-headers INTERFACE ./nihstro/include)
|
||||
# Open Source Archives
|
||||
add_subdirectory(open_source_archives)
|
||||
|
||||
# lzo compress library
|
||||
add_subdirectory(minilzo)
|
||||
|
||||
# SoundTouch
|
||||
add_subdirectory(soundtouch)
|
||||
# The SoundTouch target doesn't export the necessary include paths as properties by default
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<uses-feature android:glEsVersion="0x00030001" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
0004000000190B00 = 沙漠老鼠团
|
||||
0004000000120800 = 海岛之日
|
||||
00040000001ACD00 = 勇气地牢
|
||||
0004000000154700 = 乐高都市 卧底风云 追捕
|
||||
00040000000AD600 = 乐高都市 卧底风云 追捕
|
||||
00040000000AD500 = 乐高都市 卧底风云 追捕
|
||||
000400000007C700 = 马里奥网球公开赛
|
||||
000400000007C800 = 马里奥网球公开赛
|
||||
0004000000064D00 = 马里奥网球公开赛
|
||||
00040000000B9100 = 马里奥网球公开赛
|
||||
00040000000DCD00 = 马里奥高尔夫 世界巡回赛
|
||||
00040000000A5300 = 马里奥高尔夫 世界巡回赛
|
||||
00040000000DCE00 = 马里奥高尔夫 世界巡回赛
|
||||
000400000017E200 = 马里奥&索尼克 里约2016奥运会
|
||||
000400000F700100 = 异度之刃
|
||||
000400000F700200 = 异度之刃
|
||||
000400000F700000 = 异度之刃
|
||||
0004000000187500 = 极度恐惧
|
||||
0004000000115400 = 第七龙神3 代号VFD
|
||||
0004000000053700 = 终极兵团
|
||||
|
||||
@@ -171,7 +171,7 @@ public final class NativeLibrary {
|
||||
|
||||
public static boolean isValidFile(String filename) {
|
||||
String name = filename.toLowerCase();
|
||||
return (name.endsWith(".cia") || name.endsWith(".cci") || name.endsWith(".3ds") ||
|
||||
return (name.endsWith(".cia") || name.endsWith(".cci") || name.endsWith(".3ds") || name.endsWith(".elf") ||
|
||||
name.endsWith(".cxi") || name.endsWith(".app") || name.endsWith(".3dsx"));
|
||||
}
|
||||
|
||||
|
||||
@@ -285,6 +285,10 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
||||
mActivity.setSettingChanged();
|
||||
|
||||
StringSetting setting = scSetting.setSelectedValue(value);
|
||||
if (scSetting.getSetting().getKey().equals(SettingsFile.KEY_CAMERA_TYPE) &&
|
||||
"camera".equals(value)) {
|
||||
PermissionsHandler.checkCameraPermission(mActivity);
|
||||
}
|
||||
if (setting != null) {
|
||||
mActivity.putSetting(setting);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public final class SettingsFile {
|
||||
public static final String KEY_SYSTEM_REGION = "region_value";
|
||||
public static final String KEY_SYSTEM_LANGUAGE = "language";
|
||||
public static final String KEY_USE_PRESENT_THREAD = "use_present_thread";
|
||||
public static final String KEY_CPU_USAGE_LIMIT = "cpu_usage_limit";
|
||||
// Renderer
|
||||
public static final String KEY_USE_GLES = "use_gles";
|
||||
public static final String KEY_SHOW_FPS = "show_fps";
|
||||
|
||||
@@ -130,6 +130,7 @@ public final class SettingsFragment extends Fragment {
|
||||
SettingSection debugSection = mSettings.getSection(Settings.SECTION_INI_DEBUG);
|
||||
Setting shaderType = debugSection.getSetting(SettingsFile.KEY_SHADER_TYPE);
|
||||
Setting presentThread = debugSection.getSetting(SettingsFile.KEY_USE_PRESENT_THREAD);
|
||||
Setting cpuLimit = debugSection.getSetting(SettingsFile.KEY_CPU_USAGE_LIMIT);
|
||||
Setting ocrKey = debugSection.getSetting(SettingsFile.KEY_BAIDU_OCR_KEY);
|
||||
Setting ocrSecret = debugSection.getSetting(SettingsFile.KEY_BAIDU_OCR_SECRET);
|
||||
|
||||
@@ -165,8 +166,8 @@ public final class SettingsFragment extends Fragment {
|
||||
R.string.setting_custom_textures, 0, false, customTex));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_PRELOAD_TEXTURES, Settings.SECTION_INI_RENDERER,
|
||||
R.string.setting_preload_textures, 0, false, preloadTex));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_FRAME_LIMIT, Settings.SECTION_INI_RENDERER,
|
||||
R.string.frame_limit_enable, R.string.frame_limit_enable_description, true, useFrameLimit));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_USAGE_LIMIT, Settings.SECTION_INI_DEBUG,
|
||||
R.string.cpu_usage_limit, R.string.cpu_usage_limit_description, false, cpuLimit));
|
||||
sl.add(new SliderSetting(SettingsFile.KEY_FRAME_LIMIT, Settings.SECTION_INI_RENDERER,
|
||||
R.string.frame_limit_slider, R.string.frame_limit_slider_description, 200, "",
|
||||
100, frameLimit));
|
||||
|
||||
@@ -156,6 +156,8 @@
|
||||
<string name="post_processing_shader">后处理效果</string>
|
||||
<string name="setting_use_present_thread">启用多核心</string>
|
||||
<string name="setting_use_present_thread_desc">大部分游戏启用多核心可以提升性能。</string>
|
||||
<string name="cpu_usage_limit">CPU占用率限制</string>
|
||||
<string name="cpu_usage_limit_description">启用后模拟CPU使用将被限制在较小的时间片内。</string>
|
||||
<string name="frame_limit_enable">启用速度限制</string>
|
||||
<string name="frame_limit_enable_description">启用时速度将被限制为正常速度的指定百分比。</string>
|
||||
<string name="frame_limit_slider">限制百分比</string>
|
||||
@@ -197,6 +199,7 @@
|
||||
|
||||
<string name="camera_blank">空白</string>
|
||||
<string name="camera_still_image">静止图片</string>
|
||||
<string name="camera_from_device">使用相机</string>
|
||||
<string name="shader_type_normal">普通着色器</string>
|
||||
<string name="shader_type_normal_with_cache">普通着色器+缓存</string>
|
||||
<string name="shader_type_separate">分离着色器(不稳定)</string>
|
||||
|
||||
@@ -97,10 +97,12 @@
|
||||
<string-array name="cameraEntries">
|
||||
<item>@string/camera_blank</item>
|
||||
<item>@string/camera_still_image</item>
|
||||
<item>@string/camera_from_device</item>
|
||||
</string-array>
|
||||
<string-array name="cameraValues">
|
||||
<item>blank</item>
|
||||
<item>image</item>
|
||||
<item>camera</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Mic Input Preference -->
|
||||
|
||||
@@ -156,6 +156,8 @@
|
||||
<string name="post_processing_shader">Post-Processing Effect</string>
|
||||
<string name="setting_use_present_thread">Use Dual Core</string>
|
||||
<string name="setting_use_present_thread_desc">Most games can improve performance by using dual core.</string>
|
||||
<string name="cpu_usage_limit">Enable CPU Usage Limit</string>
|
||||
<string name="cpu_usage_limit_description">When enabled, emulation cpu usage will be limited to a smaller time slice.</string>
|
||||
<string name="frame_limit_enable">Enable Speed Limit</string>
|
||||
<string name="frame_limit_enable_description">When enabled, emulation speed will be limited to a specified percentage of normal speed.</string>
|
||||
<string name="frame_limit_slider">Speed Limit Percent</string>
|
||||
@@ -197,6 +199,7 @@
|
||||
|
||||
<string name="camera_blank">Blank</string>
|
||||
<string name="camera_still_image">Still Image</string>
|
||||
<string name="camera_from_device">Use Camera</string>
|
||||
<string name="shader_type_normal">Normal Shader</string>
|
||||
<string name="shader_type_normal_with_cache">Normal Shader with Cache</string>
|
||||
<string name="shader_type_separate">Separate Shader (Unstable)</string>
|
||||
|
||||
@@ -22,10 +22,10 @@ add_library(main SHARED
|
||||
ndk_motion.h
|
||||
png_handler.h
|
||||
png_handler.cpp
|
||||
camera/camera_base.cpp
|
||||
camera/camera_base.h
|
||||
camera/camera_util.cpp
|
||||
camera/camera_util.h
|
||||
camera/ndk_camera.cpp
|
||||
camera/ndk_camera.h
|
||||
camera/still_image_camera.cpp
|
||||
camera/still_image_camera.h
|
||||
config/config.cpp
|
||||
@@ -44,5 +44,5 @@ add_library(main SHARED
|
||||
config/config_loader.h
|
||||
)
|
||||
|
||||
target_link_libraries(main android EGL log core input_common network)
|
||||
target_link_libraries(main android camera2ndk EGL log core input_common network yuv)
|
||||
target_include_directories(main PRIVATE "./" "../../../externals/glad/include/")
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <android/log.h>
|
||||
#include "camera/camera_base.h"
|
||||
#include "camera/camera_util.h"
|
||||
|
||||
namespace Camera {
|
||||
|
||||
CameraBase::CameraBase(const Service::CAM::Flip& flip) {
|
||||
using namespace Service::CAM;
|
||||
flip_horizontal = basic_flip_horizontal = (flip == Flip::Horizontal) || (flip == Flip::Reverse);
|
||||
flip_vertical = basic_flip_vertical = (flip == Flip::Vertical) || (flip == Flip::Reverse);
|
||||
}
|
||||
|
||||
void CameraBase::SetFormat(Service::CAM::OutputFormat output_format) {
|
||||
output_rgb = output_format == Service::CAM::OutputFormat::RGB565;
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "CameraBase::SetFormat output_rgb: %d", output_rgb);
|
||||
}
|
||||
|
||||
void CameraBase::SetResolution(const Service::CAM::Resolution& resolution) {
|
||||
width = resolution.width;
|
||||
height = resolution.height;
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "CameraBase::SetResolution width: %d, height: %d", width, height);
|
||||
}
|
||||
|
||||
void CameraBase::SetFlip(Service::CAM::Flip flip) {
|
||||
using namespace Service::CAM;
|
||||
flip_horizontal = basic_flip_horizontal ^ (flip == Flip::Horizontal || flip == Flip::Reverse);
|
||||
flip_vertical = basic_flip_vertical ^ (flip == Flip::Vertical || flip == Flip::Reverse);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "CameraBase::SetFlip flip_horizontal: %d, flip_vertical: %d", flip_horizontal, flip_vertical);
|
||||
}
|
||||
|
||||
void CameraBase::SetEffect(Service::CAM::Effect effect) {
|
||||
if (effect != Service::CAM::Effect::None) {
|
||||
LOG_ERROR(Service_CAM, "Unimplemented effect {}", static_cast<int>(effect));
|
||||
}
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "CameraBase::SetEffect effect: %d", effect);
|
||||
}
|
||||
|
||||
void CameraBase::SetFrameRate(Service::CAM::FrameRate frame_rate) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "CameraBase::SetFrameRate frame_rate: %d", frame_rate);
|
||||
}
|
||||
|
||||
std::vector<u16> CameraBase::ReceiveFrame() {
|
||||
return CameraUtil::ProcessImage(DoReceiveFrame(), width, height, output_rgb, flip_horizontal,
|
||||
flip_vertical);
|
||||
}
|
||||
|
||||
std::unique_ptr<CameraInterface> BaseCameraFactory::CreatePreview(const std::string& config,
|
||||
int width, int height,
|
||||
const Service::CAM::Flip& flip) {
|
||||
std::unique_ptr<CameraInterface> camera = Create(config, flip);
|
||||
|
||||
if (camera->IsPreviewAvailable()) {
|
||||
return camera;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_CAM, "Couldn't load the camera: {}", config);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Camera
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "core/frontend/camera/factory.h"
|
||||
|
||||
namespace Camera {
|
||||
|
||||
// Base class for camera interfaces of citra
|
||||
class CameraBase : public CameraInterface {
|
||||
public:
|
||||
CameraBase(const Service::CAM::Flip& flip);
|
||||
void SetResolution(const Service::CAM::Resolution&) override;
|
||||
void SetFlip(Service::CAM::Flip) override;
|
||||
void SetEffect(Service::CAM::Effect) override;
|
||||
void SetFormat(Service::CAM::OutputFormat) override;
|
||||
void SetFrameRate(Service::CAM::FrameRate frame_rate) override;
|
||||
std::vector<u16> ReceiveFrame() override;
|
||||
virtual std::vector<u32>& DoReceiveFrame() = 0;
|
||||
|
||||
protected:
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool output_rgb = false;
|
||||
bool flip_horizontal, flip_vertical;
|
||||
bool basic_flip_horizontal, basic_flip_vertical;
|
||||
};
|
||||
|
||||
// Base class for camera factories of citra_qt
|
||||
class BaseCameraFactory : public CameraFactory {
|
||||
std::unique_ptr<CameraInterface> CreatePreview(const std::string& config, int width, int height,
|
||||
const Service::CAM::Flip& flip) override;
|
||||
};
|
||||
|
||||
} // namespace Camera
|
||||
598
src/android/jni/camera/ndk_camera.cpp
Normal file
598
src/android/jni/camera/ndk_camera.cpp
Normal file
@@ -0,0 +1,598 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <media/NdkImageReader.h>
|
||||
#include <camera/NdkCameraCaptureSession.h>
|
||||
#include <camera/NdkCameraDevice.h>
|
||||
#include <camera/NdkCameraManager.h>
|
||||
#include <camera/NdkCameraMetadata.h>
|
||||
#include <camera/NdkCaptureRequest.h>
|
||||
|
||||
#include <libyuv.h>
|
||||
|
||||
#include "jni_common.h"
|
||||
#include "camera/ndk_camera.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// YUVImage
|
||||
struct YUVImage {
|
||||
int width;
|
||||
int height;
|
||||
std::vector<u8> y;
|
||||
std::vector<u8> u;
|
||||
std::vector<u8> v;
|
||||
|
||||
YUVImage() : width(0), height(0) {}
|
||||
|
||||
explicit YUVImage(int w, int h) : width(w), height(h), y(w * h), u(w * h / 4), v(w * h / 4) {}
|
||||
|
||||
void SetDimension(int w, int h) {
|
||||
width = w;
|
||||
height = h;
|
||||
y.resize(w *h);
|
||||
u.resize(w * h / 4);
|
||||
v.resize(w * h / 4);
|
||||
}
|
||||
|
||||
void Swap(YUVImage& other) {
|
||||
y.swap(other.y);
|
||||
u.swap(other.u);
|
||||
v.swap(other.v);
|
||||
std::swap(width, other.width);
|
||||
std::swap(height, other.height);
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
y.clear();
|
||||
u.clear();
|
||||
v.clear();
|
||||
width = height = 0;
|
||||
}
|
||||
};
|
||||
|
||||
#define YUV(image) image.y.data(), image.width, image.u.data(), image.width / 2, image.v.data(), image.width / 2
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// CaptureSession
|
||||
struct CaptureSession {
|
||||
|
||||
bool Create(ACameraManager* manager) {
|
||||
// init image first
|
||||
yuv_image.SetDimension(width, height);
|
||||
out_image.SetDimension(width, height);
|
||||
|
||||
ACameraDevice_StateCallbacks callback{
|
||||
this,
|
||||
&CaptureSession::OnCameraDisconnected,
|
||||
&CaptureSession::OnCameraError
|
||||
};
|
||||
camera_status_t camera_status = ACameraManager_openCamera(manager, camera_id.c_str(), &callback, &camera_device);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create camera: %d", camera_status);
|
||||
return false;
|
||||
}
|
||||
|
||||
media_status_t media_status = AImageReader_new(width, height, format, 4, &image_reader);
|
||||
if (media_status != AMEDIA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create image reader: %d", media_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
AImageReader_ImageListener image_listener{
|
||||
this,
|
||||
&CaptureSession::OnImageAvailable
|
||||
};
|
||||
media_status = AImageReader_setImageListener(image_reader, &image_listener);
|
||||
if (media_status != AMEDIA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to set image listener: %d", media_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
media_status = AImageReader_getWindow(image_reader, &native_window);
|
||||
if (media_status != AMEDIA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to get window: %d", media_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
camera_status = ACaptureSessionOutput_create(native_window, &session_output);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create output session: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
camera_status = ACaptureSessionOutputContainer_create(&output_container);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create output container: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
camera_status = ACaptureSessionOutputContainer_add(output_container, session_output);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create output container: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
ACameraCaptureSession_stateCallbacks state_callbacks{
|
||||
nullptr,
|
||||
&CaptureSession::OnSessionClosed,
|
||||
&CaptureSession::OnSessionReady,
|
||||
&CaptureSession::OnSessionActive
|
||||
};
|
||||
camera_status = ACameraDevice_createCaptureSession(camera_device, output_container, &state_callbacks, &capture_session);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create capture session: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
camera_status = ACameraDevice_createCaptureRequest(camera_device, TEMPLATE_PREVIEW, &capture_request);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create capture request: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
camera_status = ACameraOutputTarget_create(native_window, &output_target);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to create output target: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
camera_status = ACaptureRequest_addTarget(capture_request, output_target);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to add output target: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<ACaptureRequest*, 1> requests = {capture_request};
|
||||
ACameraCaptureSession_captureCallbacks capture_callbacks{
|
||||
this,
|
||||
&CaptureSession::OnCaptureStarted,
|
||||
&CaptureSession::OnCaptureProgressed,
|
||||
&CaptureSession::OnCaptureCompleted,
|
||||
&CaptureSession::OnCaptureFailed,
|
||||
&CaptureSession::OnCaptureSequenceCompleted,
|
||||
&CaptureSession::OnCaptureSequenceAborted,
|
||||
&CaptureSession::OnCaptureBufferLost
|
||||
};
|
||||
camera_status = ACameraCaptureSession_setRepeatingRequest(capture_session, &capture_callbacks, requests.size(), requests.data(), nullptr);
|
||||
if (camera_status != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to set repeating request: %d", camera_status);
|
||||
Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (GetDisplayRotation()) {
|
||||
case 0:
|
||||
rotation_mode = libyuv::RotationMode::kRotate90;
|
||||
break;
|
||||
case 1:
|
||||
rotation_mode = libyuv::RotationMode::kRotate0;
|
||||
break;
|
||||
case 2:
|
||||
rotation_mode = libyuv::RotationMode::kRotate270;
|
||||
break;
|
||||
case 3:
|
||||
rotation_mode = libyuv::RotationMode::kRotate180;
|
||||
break;
|
||||
}
|
||||
|
||||
is_session_start = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Release() {
|
||||
// release session
|
||||
if (capture_session) {
|
||||
if (is_session_start) {
|
||||
ACameraCaptureSession_stopRepeating(capture_session);
|
||||
is_session_start = false;
|
||||
}
|
||||
ACameraCaptureSession_close(capture_session);
|
||||
capture_session = nullptr;
|
||||
}
|
||||
if (capture_request) {
|
||||
ACaptureRequest_free(capture_request);
|
||||
capture_request = nullptr;
|
||||
}
|
||||
if (output_container) {
|
||||
ACaptureSessionOutputContainer_free(output_container);
|
||||
output_container = nullptr;
|
||||
}
|
||||
if (output_target) {
|
||||
ACameraOutputTarget_free(output_target);
|
||||
output_target = nullptr;
|
||||
}
|
||||
|
||||
// release image
|
||||
if (image_reader) {
|
||||
AImageReader_delete(image_reader);
|
||||
image_reader = nullptr;
|
||||
}
|
||||
// release device
|
||||
if (camera_device) {
|
||||
ACameraDevice_close(camera_device);
|
||||
camera_device = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u16> GetOutput(const Service::CAM::Resolution& resolution, bool mirror, bool invert, bool rgb565) {
|
||||
{
|
||||
std::lock_guard lock{image_mutex};
|
||||
out_image.Swap(yuv_image);
|
||||
}
|
||||
|
||||
int rotated_width = width;
|
||||
int rotated_height = height;
|
||||
if (rotation_mode == libyuv::RotationMode::kRotate90 || rotation_mode == libyuv::RotationMode::kRotate270) {
|
||||
std::swap(rotated_width, rotated_height);
|
||||
}
|
||||
// Rotate the image to get it in upright position
|
||||
YUVImage rotated(rotated_width, rotated_height);
|
||||
libyuv::I420Rotate(YUV(out_image), YUV(rotated), out_image.width, out_image.height, rotation_mode);
|
||||
|
||||
// Calculate crop coordinates
|
||||
int crop_width, crop_height;
|
||||
if (resolution.width * rotated.height > resolution.height * rotated.width) {
|
||||
crop_width = rotated.width;
|
||||
crop_height = rotated.width * resolution.height / resolution.width;
|
||||
} else {
|
||||
crop_height = rotated.height;
|
||||
crop_width = rotated.height * resolution.width / resolution.height;
|
||||
}
|
||||
const int crop_x = (rotated.width - crop_width) / 2;
|
||||
const int crop_y = (rotated.height - crop_height) / 2;
|
||||
|
||||
const int y_offset = crop_y * rotated.width + crop_x;
|
||||
const int uv_offset = crop_y / 2 * rotated.width / 2 + crop_x / 2;
|
||||
|
||||
YUVImage scaled(resolution.width, resolution.height);
|
||||
// Crop and scale
|
||||
libyuv::I420Scale(rotated.y.data() + y_offset, rotated.width, rotated.u.data() + uv_offset, rotated.width / 2,
|
||||
rotated.v.data() + uv_offset, rotated.width / 2, crop_width, crop_height, YUV(scaled),
|
||||
resolution.width, resolution.height, libyuv::kFilterBilinear);
|
||||
|
||||
if (mirror) {
|
||||
YUVImage mirrored(scaled.width, scaled.height);
|
||||
libyuv::I420Mirror(YUV(scaled), YUV(mirrored), resolution.width, resolution.height);
|
||||
scaled.Swap(mirrored);
|
||||
}
|
||||
|
||||
std::vector<u16> output(resolution.width * resolution.height);
|
||||
if (rgb565) {
|
||||
libyuv::I420ToRGB565(YUV(scaled), reinterpret_cast<u8*>(output.data()),
|
||||
resolution.width * 2, resolution.width,
|
||||
invert ? -resolution.height : resolution.height);
|
||||
} else {
|
||||
libyuv::I420ToYUY2(YUV(scaled), reinterpret_cast<u8*>(output.data()), resolution.width * 2,
|
||||
resolution.width, invert ? -resolution.height : resolution.height);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
static void OnCameraDisconnected(void* context, ACameraDevice* device) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnCameraDisconnected");
|
||||
}
|
||||
|
||||
static void OnCameraError(void* context, ACameraDevice* device, int error) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnCameraError");
|
||||
}
|
||||
|
||||
static void OnImageAvailable(void* context, AImageReader* reader) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
//__android_log_print(ANDROID_LOG_INFO, "citra", "OnImageAvailable");
|
||||
|
||||
AImage* image = nullptr;
|
||||
media_status_t media_status = AImageReader_acquireLatestImage(reader, &image);
|
||||
|
||||
// Y
|
||||
uint8_t* src_y;
|
||||
int src_size_y;
|
||||
int src_stride_y;
|
||||
AImage_getPlaneData(image, 0, &src_y, &src_size_y);
|
||||
AImage_getPlaneRowStride(image, 0, &src_stride_y);
|
||||
|
||||
// U
|
||||
uint8_t* src_u;
|
||||
int src_size_u;
|
||||
int src_stride_u;
|
||||
AImage_getPlaneData(image, 1, &src_u, &src_size_u);
|
||||
AImage_getPlaneRowStride(image, 1, &src_stride_u);
|
||||
|
||||
// V
|
||||
uint8_t* src_v;
|
||||
int src_size_v;
|
||||
int src_stride_v;
|
||||
AImage_getPlaneData(image, 2, &src_v, &src_size_v);
|
||||
AImage_getPlaneRowStride(image, 2, &src_stride_v);
|
||||
|
||||
// stride
|
||||
int src_pixel_stride_uv;
|
||||
AImage_getPlanePixelStride(image, 1, &src_pixel_stride_uv);
|
||||
|
||||
{
|
||||
int width = that->width;
|
||||
int height = that->height;
|
||||
auto& yuv_image = that->yuv_image;
|
||||
std::lock_guard lock{that->image_mutex};
|
||||
libyuv::Android420ToI420(src_y, src_stride_y, src_u, src_stride_u, src_v, src_stride_v, src_pixel_stride_uv, YUV(yuv_image), width, height);
|
||||
}
|
||||
|
||||
AImage_delete(image);
|
||||
}
|
||||
|
||||
static void OnSessionClosed(void* context, ACameraCaptureSession *session) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnSessionClosed");
|
||||
}
|
||||
|
||||
static void OnSessionReady(void* context, ACameraCaptureSession* session) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnSessionReady");
|
||||
}
|
||||
|
||||
static void OnSessionActive(void* context, ACameraCaptureSession* session) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnSessionActive");
|
||||
}
|
||||
|
||||
static void OnCaptureStarted(void* context, ACameraCaptureSession* session, const ACaptureRequest* request, int64_t timestamp) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
static void OnCaptureProgressed(void* context, ACameraCaptureSession* session, ACaptureRequest* request, const ACameraMetadata* result) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
static void OnCaptureCompleted(void* context, ACameraCaptureSession* session, ACaptureRequest* request, const ACameraMetadata* result) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
static void OnCaptureFailed(void* context, ACameraCaptureSession* session, ACaptureRequest* request, ACameraCaptureFailure* failure) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnCaptureFailed");
|
||||
}
|
||||
|
||||
static void OnCaptureSequenceCompleted(void* context, ACameraCaptureSession* session, int sequenceId, int64_t frameNumber) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnCaptureSequenceCompleted");
|
||||
}
|
||||
|
||||
static void OnCaptureSequenceAborted(void* context, ACameraCaptureSession* session, int sequenceId) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnCaptureSequenceAborted");
|
||||
}
|
||||
|
||||
static void OnCaptureBufferLost(void* context, ACameraCaptureSession* session, ACaptureRequest* request, ACameraWindowType* window, int64_t frameNumber) {
|
||||
auto* that = reinterpret_cast<CaptureSession*>(context);
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "OnCaptureBufferLost");
|
||||
}
|
||||
|
||||
std::mutex image_mutex;
|
||||
YUVImage yuv_image;
|
||||
YUVImage out_image;
|
||||
|
||||
//
|
||||
std::string camera_id;
|
||||
int format = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool is_session_start = false;
|
||||
libyuv::RotationMode rotation_mode;
|
||||
|
||||
//
|
||||
ACameraDevice* camera_device = nullptr;
|
||||
AImageReader* image_reader = nullptr;
|
||||
// managed by image reader, Do NOT call ANativeWindow_release on it
|
||||
ANativeWindow* native_window = nullptr;
|
||||
|
||||
ACaptureSessionOutputContainer* output_container = nullptr;
|
||||
ACaptureSessionOutput* session_output = nullptr;
|
||||
ACameraOutputTarget* output_target = nullptr;
|
||||
ACaptureRequest* capture_request = nullptr;
|
||||
|
||||
ACameraCaptureSession* capture_session = nullptr;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// NDKCameraInterface Impl
|
||||
class NDKCameraInterface::Impl {
|
||||
public:
|
||||
Impl(const std::string &config, const Service::CAM::Flip& flip) {
|
||||
manager = ACameraManager_create();
|
||||
mirror = base_mirror =
|
||||
flip == Service::CAM::Flip::Horizontal || flip == Service::CAM::Flip::Reverse;
|
||||
invert = base_invert =
|
||||
flip == Service::CAM::Flip::Vertical || flip == Service::CAM::Flip::Reverse;
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
if (manager) {
|
||||
capture_sessions[0].Release();
|
||||
capture_sessions[1].Release();
|
||||
ACameraManager_delete(manager);
|
||||
manager = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
ACameraIdList* id_list = nullptr;
|
||||
camera_status_t ret = ACameraManager_getCameraIdList(manager, &id_list);
|
||||
if (ret != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to get camera id list: %d", ret);
|
||||
ACameraManager_delete(manager);
|
||||
return;
|
||||
}
|
||||
|
||||
if (id_list->numCameras <= 0) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "no camera devices found");
|
||||
ACameraManager_deleteCameraIdList(id_list);
|
||||
ACameraManager_delete(manager);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < id_list->numCameras; ++i) {
|
||||
ACameraMetadata* metadata = nullptr;
|
||||
ret = ACameraManager_getCameraCharacteristics(manager, id_list->cameraIds[i], &metadata);
|
||||
if (ret != ACAMERA_OK) {
|
||||
__android_log_print(ANDROID_LOG_INFO, "citra", "failed to get camera characteristics: %d", ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
int is_front_camera = 0;
|
||||
ACameraMetadata_const_entry entry;
|
||||
ACameraMetadata_getConstEntry(metadata, ACAMERA_LENS_FACING, &entry);
|
||||
if (entry.data.i32[0] == ACAMERA_LENS_FACING_FRONT) {
|
||||
is_front_camera = 1;
|
||||
} else if (entry.data.i32[0] == ACAMERA_LENS_FACING_BACK) {
|
||||
is_front_camera = 0;
|
||||
}
|
||||
|
||||
auto& session = capture_sessions[is_front_camera];
|
||||
session.camera_id = id_list->cameraIds[i];
|
||||
ACameraMetadata_getConstEntry(metadata, ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, &entry);
|
||||
session.format = 0;
|
||||
session.width = std::numeric_limits<int>::max();
|
||||
session.height = std::numeric_limits<int>::max();
|
||||
for (int j = 0; j < entry.count; j += 4) {
|
||||
int format = entry.data.i32[j + 0];
|
||||
int width = entry.data.i32[j + 1];
|
||||
int height = entry.data.i32[j + 2];
|
||||
int stream = entry.data.i32[j + 3];
|
||||
|
||||
if (stream & ACAMERA_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT) {
|
||||
// This is an input stream
|
||||
continue;
|
||||
}
|
||||
|
||||
if (format == AIMAGE_FORMAT_YUV_420_888) {
|
||||
session.format = AIMAGE_FORMAT_YUV_420_888;
|
||||
if (width > 640 && height > 640) {
|
||||
if (session.width > width || session.height > height) {
|
||||
session.width = width;
|
||||
session.height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ACameraMetadata_free(metadata);
|
||||
}
|
||||
ACameraManager_deleteCameraIdList(id_list);
|
||||
}
|
||||
|
||||
CaptureSession& GetCurrentSession() {
|
||||
return capture_sessions[current];
|
||||
}
|
||||
|
||||
void StartCapture() {
|
||||
GetCurrentSession().Create(manager);
|
||||
}
|
||||
|
||||
void StopCapture() {
|
||||
GetCurrentSession().Release();
|
||||
}
|
||||
|
||||
void SetResolution(const Service::CAM::Resolution& r) {
|
||||
resolution = r;
|
||||
}
|
||||
|
||||
void SetFlip(Service::CAM::Flip flip) {
|
||||
mirror = base_mirror ^
|
||||
(flip == Service::CAM::Flip::Horizontal || flip == Service::CAM::Flip::Reverse);
|
||||
invert =
|
||||
base_invert ^ (flip == Service::CAM::Flip::Vertical || flip == Service::CAM::Flip::Reverse);
|
||||
}
|
||||
|
||||
void SetFormat(Service::CAM::OutputFormat f) {
|
||||
rgb565 = f == Service::CAM::OutputFormat::RGB565;
|
||||
}
|
||||
|
||||
std::vector<u16> ReceiveFrame() {
|
||||
return GetCurrentSession().GetOutput(resolution, mirror, invert, rgb565);
|
||||
}
|
||||
|
||||
bool IsPreviewAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
ACameraManager* manager = nullptr;
|
||||
u32 current = 0;
|
||||
// 0 - back camera, 1 - front camera
|
||||
std::array<CaptureSession, 2> capture_sessions;
|
||||
|
||||
// config
|
||||
Service::CAM::Resolution resolution;
|
||||
bool base_mirror;
|
||||
bool base_invert;
|
||||
bool mirror;
|
||||
bool invert;
|
||||
bool rgb565;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// NDKCameraInterface
|
||||
NDKCameraInterface::NDKCameraInterface(const std::string &config, const Service::CAM::Flip& flip)
|
||||
: impl(std::make_shared<Impl>(config, flip)) {
|
||||
impl->Initialize();
|
||||
}
|
||||
|
||||
void NDKCameraInterface::StartCapture() {
|
||||
impl->StartCapture();
|
||||
}
|
||||
|
||||
void NDKCameraInterface::StopCapture() {
|
||||
impl->StopCapture();
|
||||
}
|
||||
|
||||
NDKCameraInterface::~NDKCameraInterface() = default;
|
||||
|
||||
void NDKCameraInterface::SetResolution(const Service::CAM::Resolution& r) {
|
||||
impl->SetResolution(r);
|
||||
}
|
||||
|
||||
void NDKCameraInterface::SetFlip(Service::CAM::Flip flip) {
|
||||
impl->SetFlip(flip);
|
||||
}
|
||||
|
||||
void NDKCameraInterface::SetEffect(Service::CAM::Effect) {
|
||||
// igore
|
||||
}
|
||||
|
||||
void NDKCameraInterface::SetFormat(Service::CAM::OutputFormat f) {
|
||||
impl->SetFormat(f);
|
||||
}
|
||||
|
||||
void NDKCameraInterface::SetFrameRate(Service::CAM::FrameRate frame_rate) {
|
||||
// igore
|
||||
}
|
||||
|
||||
std::vector<u16> NDKCameraInterface::ReceiveFrame() {
|
||||
return impl->ReceiveFrame();
|
||||
}
|
||||
|
||||
bool NDKCameraInterface::IsPreviewAvailable() {
|
||||
return impl->IsPreviewAvailable();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// NDKCameraFactory
|
||||
std::unique_ptr<Camera::CameraInterface> NDKCameraFactory::Create(const std::string &config, const Service::CAM::Flip &flip) {
|
||||
return std::make_unique<NDKCameraInterface>(config, flip);;
|
||||
}
|
||||
38
src/android/jni/camera/ndk_camera.h
Normal file
38
src/android/jni/camera/ndk_camera.h
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <camera/NdkCameraManager.h>
|
||||
|
||||
#include "core/frontend/camera/factory.h"
|
||||
#include "core/frontend/camera/interface.h"
|
||||
|
||||
class NDKCameraInterface : public Camera::CameraInterface {
|
||||
public:
|
||||
NDKCameraInterface(const std::string &config, const Service::CAM::Flip& flip);
|
||||
~NDKCameraInterface() override;
|
||||
|
||||
void StartCapture() override;
|
||||
void StopCapture() override;
|
||||
void SetResolution(const Service::CAM::Resolution&) override;
|
||||
void SetFlip(Service::CAM::Flip) override;
|
||||
void SetEffect(Service::CAM::Effect) override;
|
||||
void SetFormat(Service::CAM::OutputFormat) override;
|
||||
void SetFrameRate(Service::CAM::FrameRate frame_rate) override;
|
||||
std::vector<u16> ReceiveFrame() override;
|
||||
bool IsPreviewAvailable() override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::shared_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
class NDKCameraFactory : public Camera::CameraFactory {
|
||||
public:
|
||||
std::unique_ptr<Camera::CameraInterface> Create(const std::string& config,
|
||||
const Service::CAM::Flip& flip) override;
|
||||
};
|
||||
@@ -6,10 +6,9 @@
|
||||
#include "jni_common.h"
|
||||
#include "camera/still_image_camera.h"
|
||||
|
||||
namespace Camera {
|
||||
|
||||
StillImageCamera::StillImageCamera(const std::string& config, const Service::CAM::Flip& flip)
|
||||
: CameraBase(flip) {}
|
||||
StillImageCamera::StillImageCamera(const std::string& config, const Service::CAM::Flip& flip) {
|
||||
SetFlip(flip);
|
||||
}
|
||||
|
||||
StillImageCamera::~StillImageCamera() = default;
|
||||
|
||||
@@ -31,19 +30,41 @@ void StillImageCamera::StartCapture() {
|
||||
}
|
||||
|
||||
void StillImageCamera::StopCapture() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
std::vector<u32>& StillImageCamera::DoReceiveFrame() {
|
||||
return pixels;
|
||||
void StillImageCamera::SetFormat(Service::CAM::OutputFormat output_format) {
|
||||
output_rgb = output_format == Service::CAM::OutputFormat::RGB565;
|
||||
}
|
||||
|
||||
void StillImageCamera::SetResolution(const Service::CAM::Resolution& resolution) {
|
||||
width = resolution.width;
|
||||
height = resolution.height;
|
||||
}
|
||||
|
||||
void StillImageCamera::SetFlip(Service::CAM::Flip flip) {
|
||||
using namespace Service::CAM;
|
||||
flip_horizontal = basic_flip_horizontal ^ (flip == Flip::Horizontal || flip == Flip::Reverse);
|
||||
flip_vertical = basic_flip_vertical ^ (flip == Flip::Vertical || flip == Flip::Reverse);
|
||||
}
|
||||
|
||||
void StillImageCamera::SetEffect(Service::CAM::Effect effect) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
void StillImageCamera::SetFrameRate(Service::CAM::FrameRate frame_rate) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
std::vector<u16> StillImageCamera::ReceiveFrame() {
|
||||
return CameraUtil::ProcessImage(pixels, width, height, output_rgb, flip_horizontal, flip_vertical);
|
||||
}
|
||||
|
||||
bool StillImageCamera::IsPreviewAvailable() {
|
||||
return !pixels.empty();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<CameraInterface> StillImageCameraFactory::Create(const std::string& config,
|
||||
std::unique_ptr<Camera::CameraInterface> StillImageCameraFactory::Create(const std::string& config,
|
||||
const Service::CAM::Flip& flip) {
|
||||
return std::make_unique<StillImageCamera>(config, flip);
|
||||
}
|
||||
|
||||
} // namespace Camera
|
||||
|
||||
@@ -4,33 +4,39 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "camera/camera_base.h"
|
||||
#include <string>
|
||||
#include "core/frontend/camera/factory.h"
|
||||
#include "camera/camera_util.h"
|
||||
#include "core/frontend/camera/interface.h"
|
||||
#include "core/hle/service/cam/cam.h"
|
||||
|
||||
namespace Camera {
|
||||
|
||||
class StillImageCamera final : public CameraBase {
|
||||
class StillImageCamera : public Camera::CameraInterface {
|
||||
public:
|
||||
StillImageCamera(const std::string& config, const Service::CAM::Flip& flip);
|
||||
~StillImageCamera();
|
||||
void StartCapture() override;
|
||||
void StopCapture() override;
|
||||
std::vector<u32>& DoReceiveFrame() override;
|
||||
void SetResolution(const Service::CAM::Resolution&) override;
|
||||
void SetFlip(Service::CAM::Flip) override;
|
||||
void SetEffect(Service::CAM::Effect) override;
|
||||
void SetFormat(Service::CAM::OutputFormat) override;
|
||||
void SetFrameRate(Service::CAM::FrameRate frame_rate) override;
|
||||
std::vector<u16> ReceiveFrame() override;
|
||||
bool IsPreviewAvailable() override;
|
||||
|
||||
private:
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool output_rgb = false;
|
||||
bool flip_horizontal, flip_vertical;
|
||||
bool basic_flip_horizontal, basic_flip_vertical;
|
||||
std::vector<u32> pixels;
|
||||
};
|
||||
|
||||
class StillImageCameraFactory final : public BaseCameraFactory {
|
||||
class StillImageCameraFactory : public Camera::CameraFactory {
|
||||
public:
|
||||
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
||||
std::unique_ptr<Camera::CameraInterface> Create(const std::string& config,
|
||||
const Service::CAM::Flip& flip) override;
|
||||
|
||||
private:
|
||||
friend class StillImageCamera;
|
||||
};
|
||||
|
||||
} // namespace Camera
|
||||
|
||||
@@ -57,6 +57,8 @@ void SaveDefault() {
|
||||
s_layer.Set(ALLOW_SHADOW, ALLOW_SHADOW.default_value);
|
||||
s_layer.Set(SHADER_TYPE, SHADER_TYPE.default_value);
|
||||
s_layer.Set(USE_PRESENT_THREAD, USE_PRESENT_THREAD.default_value);
|
||||
s_layer.Set(CPU_USAGE_LIMIT, CPU_USAGE_LIMIT.default_value);
|
||||
s_layer.Set(LLE_MODULES, LLE_MODULES.default_value);
|
||||
|
||||
// custom layout
|
||||
s_layer.Set(USE_CUSTOM_LAYOUT, USE_CUSTOM_LAYOUT.default_value);
|
||||
|
||||
@@ -48,6 +48,8 @@ const ConfigInfo<std::string> CAMERA_DEVICE{{"Camera", "camera_type"}, "blank"};
|
||||
const ConfigInfo<bool> ALLOW_SHADOW{{"Debug", "allow_shadow"}, false};
|
||||
const ConfigInfo<u8> SHADER_TYPE{{"Debug", "shader_type"}, 1};
|
||||
const ConfigInfo<bool> USE_PRESENT_THREAD{{"Debug", "use_present_thread"}, true};
|
||||
const ConfigInfo<bool> CPU_USAGE_LIMIT{{"Debug", "cpu_usage_limit"}, false};
|
||||
const ConfigInfo<std::string> LLE_MODULES{{"Debug", "lle_modules"}, ""};
|
||||
const ConfigInfo<std::string> BAIDU_OCR_KEY{{"Debug", "baidu_ocr_key"}, ""};
|
||||
const ConfigInfo<std::string> BAIDU_OCR_SECRET{{"Debug", "baidu_ocr_secret"}, ""};
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ extern const ConfigInfo<std::string> CAMERA_DEVICE;
|
||||
extern const ConfigInfo<bool> ALLOW_SHADOW;
|
||||
extern const ConfigInfo<u8> SHADER_TYPE;
|
||||
extern const ConfigInfo<bool> USE_PRESENT_THREAD;
|
||||
extern const ConfigInfo<bool> CPU_USAGE_LIMIT;
|
||||
extern const ConfigInfo<std::string> LLE_MODULES;
|
||||
extern const ConfigInfo<std::string> BAIDU_OCR_KEY;
|
||||
extern const ConfigInfo<std::string> BAIDU_OCR_SECRET;
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#include "camera/still_image_camera.h"
|
||||
#include "camera/ndk_camera.h"
|
||||
#include "config/main_settings.h"
|
||||
#include "egl_android.h"
|
||||
#include "input_manager.h"
|
||||
@@ -245,7 +246,8 @@ JNIEXPORT void JNICALL Java_org_citra_emu_NativeLibrary_SetUserPath(JNIEnv* env,
|
||||
s_keyboard = std::make_shared<AndroidKeyboard>();
|
||||
Core::System::GetInstance().RegisterSoftwareKeyboard(s_keyboard);
|
||||
Core::System::GetInstance().RegisterImageInterface(std::make_shared<PNGHandler>());
|
||||
Camera::RegisterFactory("image", std::make_unique<Camera::StillImageCameraFactory>());
|
||||
Camera::RegisterFactory("image", std::make_unique<StillImageCameraFactory>());
|
||||
Camera::RegisterFactory("camera", std::make_unique<NDKCameraFactory>());
|
||||
|
||||
// Register real Mic factory
|
||||
Frontend::Mic::RegisterRealMicFactory(std::make_unique<AndroidMicFactory>());
|
||||
@@ -385,6 +387,7 @@ JNIEXPORT void JNICALL Java_org_citra_emu_NativeLibrary_Run(JNIEnv* env, jclass
|
||||
// debug
|
||||
Settings::values.allow_shadow = Config::Get(Config::ALLOW_SHADOW);
|
||||
Settings::values.use_present_thread = Config::Get(Config::USE_PRESENT_THREAD);
|
||||
Settings::values.core_downcount_hack = Config::Get(Config::CPU_USAGE_LIMIT);
|
||||
u8 shaderType = Config::Get(Config::SHADER_TYPE);
|
||||
if (shaderType == 0) {
|
||||
Settings::values.use_separable_shader = false;
|
||||
@@ -396,6 +399,7 @@ JNIEXPORT void JNICALL Java_org_citra_emu_NativeLibrary_Run(JNIEnv* env, jclass
|
||||
Settings::values.use_separable_shader = true;
|
||||
Settings::values.use_shader_cache = false;
|
||||
}
|
||||
Settings::SetLLEModules(Config::Get(Config::LLE_MODULES));
|
||||
// custom layout
|
||||
Settings::values.custom_layout = Config::Get(Config::USE_CUSTOM_LAYOUT);
|
||||
Settings::values.custom_top_left = Config::Get(Config::CUSTOM_TOP_LEFT);
|
||||
|
||||
@@ -151,9 +151,8 @@ public:
|
||||
Memory::MemorySystem& memory;
|
||||
};
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, u32 id,
|
||||
std::shared_ptr<Core::Timing::Timer> timer)
|
||||
: ARM_Interface(id, timer), system(*system), memory(memory),
|
||||
ARM_Dynarmic::ARM_Dynarmic(Core::System* system, u32 id, std::shared_ptr<Core::Timing::Timer> timer)
|
||||
: ARM_Interface(id, timer), system(*system), memory(system->Memory()),
|
||||
cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
|
||||
SetPageTable(memory.GetCurrentPageTable());
|
||||
}
|
||||
@@ -287,6 +286,7 @@ void ARM_Dynarmic::ClearInstructionCache() {
|
||||
|
||||
void ARM_Dynarmic::InvalidateCacheRange(u32 start_address, std::size_t length) {
|
||||
jit->InvalidateCacheRange(start_address, length);
|
||||
LOG_DEBUG(Core_ARM11, "arm jit invalidate cache range");
|
||||
}
|
||||
|
||||
Memory::PageTable* ARM_Dynarmic::GetPageTable() const {
|
||||
|
||||
@@ -24,8 +24,7 @@ class DynarmicUserCallbacks;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, u32 id,
|
||||
std::shared_ptr<Core::Timing::Timer> timer);
|
||||
ARM_Dynarmic(Core::System* system, u32 id, std::shared_ptr<Core::Timing::Timer> timer);
|
||||
~ARM_Dynarmic() override;
|
||||
|
||||
void Run() override;
|
||||
|
||||
@@ -68,17 +68,14 @@ private:
|
||||
u32 fpexc;
|
||||
};
|
||||
|
||||
ARM_DynCom::ARM_DynCom(Core::System* system, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode, u32 id,
|
||||
std::shared_ptr<Core::Timing::Timer> timer)
|
||||
: ARM_Interface(id, timer), system(system) {
|
||||
state = std::make_unique<ARMul_State>(system, memory, initial_mode);
|
||||
ARM_DynCom::ARM_DynCom(Core::System* system, u32 id, std::shared_ptr<Core::Timing::Timer> timer)
|
||||
: ARM_Interface(id, timer) {
|
||||
state = std::make_unique<ARMul_State>(*system, system->Memory(), USER32MODE);
|
||||
}
|
||||
|
||||
ARM_DynCom::~ARM_DynCom() {}
|
||||
|
||||
void ARM_DynCom::Run() {
|
||||
DEBUG_ASSERT(system != nullptr);
|
||||
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
|
||||
}
|
||||
|
||||
@@ -155,10 +152,8 @@ void ARM_DynCom::SetCP15Register(CP15Register reg, u32 value) {
|
||||
|
||||
void ARM_DynCom::ExecuteInstructions(u64 num_instructions) {
|
||||
state->NumInstrsToExecute = num_instructions;
|
||||
unsigned ticks_executed = InterpreterMainLoop(state.get());
|
||||
if (system != nullptr) {
|
||||
timer->AddTicks(ticks_executed);
|
||||
}
|
||||
unsigned ticks_executed = InterpreterMainLoop(state.get(), timer.get());
|
||||
timer->AddTicks(ticks_executed);
|
||||
state->ServeBreak();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ class MemorySystem;
|
||||
|
||||
class ARM_DynCom final : public ARM_Interface {
|
||||
public:
|
||||
explicit ARM_DynCom(Core::System* system, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode, u32 id,
|
||||
std::shared_ptr<Core::Timing::Timer> timer);
|
||||
explicit ARM_DynCom(Core::System* system, u32 id, std::shared_ptr<Core::Timing::Timer> timer);
|
||||
~ARM_DynCom() override;
|
||||
|
||||
void Run() override;
|
||||
@@ -58,6 +56,5 @@ protected:
|
||||
private:
|
||||
void ExecuteInstructions(u64 num_instructions);
|
||||
|
||||
Core::System* system;
|
||||
std::unique_ptr<ARMul_State> state;
|
||||
};
|
||||
|
||||
@@ -921,7 +921,7 @@ static int clz(unsigned int x) {
|
||||
|
||||
MICROPROFILE_DEFINE(DynCom_Execute, "DynCom", "Execute", MP_RGB(255, 0, 0));
|
||||
|
||||
unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
||||
unsigned InterpreterMainLoop(ARMul_State* cpu, Core::Timing::Timer* timer) {
|
||||
MICROPROFILE_SCOPE(DynCom_Execute);
|
||||
|
||||
/// Nearest upcoming GDB code execution breakpoint, relative to the last dispatch's address.
|
||||
@@ -3870,14 +3870,13 @@ SUB_INST : {
|
||||
}
|
||||
SWI_INST : {
|
||||
if (inst_base->cond == ConditionCode::AL || CondPassed(cpu, inst_base->cond)) {
|
||||
DEBUG_ASSERT(cpu->system != nullptr);
|
||||
swi_inst* const inst_cream = (swi_inst*)inst_base->component;
|
||||
num_instrs = std::max(num_instrs, Settings::values.core_ticks_hack);
|
||||
cpu->system->GetRunningCore().GetTimer().AddTicks(num_instrs);
|
||||
timer->AddTicks(num_instrs);
|
||||
cpu->NumInstrsToExecute =
|
||||
num_instrs >= cpu->NumInstrsToExecute ? 0 : cpu->NumInstrsToExecute - num_instrs;
|
||||
num_instrs = 0;
|
||||
Kernel::SVCContext{*cpu->system}.CallSVC(inst_cream->num & 0xFFFF);
|
||||
Kernel::SVCContext{cpu->system}.CallSVC(inst_cream->num & 0xFFFF);
|
||||
// The kernel would call ERET to get here, which clears exclusive memory state.
|
||||
cpu->UnsetExclusiveMemoryAddress();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/core_timing.h"
|
||||
|
||||
struct ARMul_State;
|
||||
|
||||
unsigned InterpreterMainLoop(ARMul_State* state);
|
||||
unsigned InterpreterMainLoop(ARMul_State* state, Core::Timing::Timer* timer);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "core/core.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
ARMul_State::ARMul_State(Core::System* system, Memory::MemorySystem& memory,
|
||||
ARMul_State::ARMul_State(Core::System& system, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode)
|
||||
: system(system), memory(memory) {
|
||||
Reset();
|
||||
@@ -609,9 +609,8 @@ void ARMul_State::ServeBreak() {
|
||||
DEBUG_ASSERT(Reg[15] == last_bkpt.address);
|
||||
}
|
||||
|
||||
DEBUG_ASSERT(system != nullptr);
|
||||
Kernel::Thread* thread = system->Kernel().GetCurrentThreadManager().GetCurrentThread();
|
||||
system->GetRunningCore().SaveContext(thread->context);
|
||||
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
|
||||
system.Kernel().GetRunningCore().SaveContext(thread->context);
|
||||
|
||||
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
|
||||
last_bkpt_hit = false;
|
||||
|
||||
@@ -147,7 +147,7 @@ enum {
|
||||
|
||||
struct ARMul_State final {
|
||||
public:
|
||||
explicit ARMul_State(Core::System* system, Memory::MemorySystem& memory,
|
||||
explicit ARMul_State(Core::System& system, Memory::MemorySystem& memory,
|
||||
PrivilegeMode initial_mode);
|
||||
|
||||
void ChangePrivilegeMode(u32 new_mode);
|
||||
@@ -206,7 +206,7 @@ public:
|
||||
|
||||
void ServeBreak();
|
||||
|
||||
Core::System* system;
|
||||
Core::System& system;
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
std::array<u32, 16> Reg{}; // The current register file
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Core {
|
||||
System System::s_instance;
|
||||
|
||||
System::ResultStatus System::RunLoop() {
|
||||
return cpu_cores.size() > 1 ? RunLoopMultiCores() : RunLoopOneCore();
|
||||
return Settings::values.is_new_3ds ? RunLoopMultiCores() : RunLoopSingleCore();
|
||||
}
|
||||
|
||||
System::ResultStatus System::RunLoopMultiCores() {
|
||||
@@ -61,12 +61,11 @@ System::ResultStatus System::RunLoopMultiCores() {
|
||||
}
|
||||
|
||||
if (max_delay > 0) {
|
||||
running_core = current_core_to_execute;
|
||||
kernel->SetRunningCPU(running_core);
|
||||
kernel->SetRunningCPU(current_core_to_execute);
|
||||
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core_ARM11, "Core {} idling", current_core_to_execute->GetID());
|
||||
current_core_to_execute->GetTimer().Idle();
|
||||
PrepareReschedule();
|
||||
kernel->PrepareReschedule();
|
||||
} else {
|
||||
current_core_to_execute->Run();
|
||||
}
|
||||
@@ -82,14 +81,13 @@ System::ResultStatus System::RunLoopMultiCores() {
|
||||
cpu_core->GetTimer().Advance(max_slice);
|
||||
}
|
||||
for (auto& cpu_core : cpu_cores) {
|
||||
running_core = cpu_core.get();
|
||||
kernel->SetRunningCPU(running_core);
|
||||
kernel->SetRunningCPU(cpu_core.get());
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core_ARM11, "Core {} idling", cpu_core->GetID());
|
||||
cpu_core->GetTimer().Idle();
|
||||
PrepareReschedule();
|
||||
kernel->PrepareReschedule();
|
||||
} else {
|
||||
cpu_core->Run();
|
||||
}
|
||||
@@ -98,7 +96,7 @@ System::ResultStatus System::RunLoopMultiCores() {
|
||||
}
|
||||
|
||||
HW::Update();
|
||||
Reschedule();
|
||||
kernel->RescheduleMultiCores();
|
||||
|
||||
if (reset_requested.exchange(false)) {
|
||||
Reset();
|
||||
@@ -109,20 +107,20 @@ System::ResultStatus System::RunLoopMultiCores() {
|
||||
return status;
|
||||
}
|
||||
|
||||
System::ResultStatus System::RunLoopOneCore() {
|
||||
System::ResultStatus System::RunLoopSingleCore() {
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (kernel->GetCurrentThreadManager().GetCurrentThread() == nullptr) {
|
||||
running_core->GetTimer().Idle();
|
||||
running_core->GetTimer().Advance();
|
||||
PrepareReschedule();
|
||||
cpu_cores[0]->GetTimer().Idle();
|
||||
cpu_cores[0]->GetTimer().Advance();
|
||||
kernel->PrepareReschedule();
|
||||
} else {
|
||||
running_core->GetTimer().Advance();
|
||||
running_core->Run();
|
||||
cpu_cores[0]->GetTimer().Advance();
|
||||
cpu_cores[0]->Run();
|
||||
}
|
||||
|
||||
HW::Update();
|
||||
Reschedule();
|
||||
kernel->RescheduleSingleCore();
|
||||
|
||||
if (reset_requested.exchange(false)) {
|
||||
Reset();
|
||||
@@ -148,10 +146,10 @@ static void LoadOverrides(u64 title_id) {
|
||||
Settings::values.skip_slow_draw = true;
|
||||
} else if (title_id == 0x00040000001CCD00 || title_id == 0x00040000001B4500) {
|
||||
// The Alliance Alive
|
||||
Settings::SetFMVHack(true);
|
||||
Settings::SetFMVHack(!Settings::values.core_downcount_hack);
|
||||
} else if (title_id == 0x0004000000120900 || title_id == 0x0004000000164300) {
|
||||
// Lord of Magna: Maiden Heaven
|
||||
Settings::SetFMVHack(true);
|
||||
Settings::SetFMVHack(!Settings::values.core_downcount_hack);
|
||||
} else if (title_id == 0x000400000015CB00) {
|
||||
// New Atelier Rorona
|
||||
Settings::values.skip_slow_draw = true;
|
||||
@@ -181,7 +179,7 @@ static void LoadOverrides(u64 title_id) {
|
||||
} else if (title_id == 0x000400000008FE00) {
|
||||
// 1001 Spikes [USA]
|
||||
Settings::values.stream_buffer_hack = false;
|
||||
Settings::SetFMVHack(true);
|
||||
Settings::SetFMVHack(!Settings::values.core_downcount_hack);
|
||||
} else if (title_id == 0x0004000000049100 || title_id == 0x0004000000030400 ||
|
||||
title_id == 0x0004000000049000) {
|
||||
// Star Fox 64
|
||||
@@ -191,6 +189,22 @@ static void LoadOverrides(u64 title_id) {
|
||||
Settings::values.y2r_perform_hack = true;
|
||||
}
|
||||
|
||||
const std::array<u64, 7> cpu_limit_ids = {
|
||||
0x000400000007C700, // Mario Tennis Open
|
||||
0x000400000007C800, // Mario Tennis Open
|
||||
0x0004000000064D00, // Mario Tennis Open
|
||||
0x00040000000B9100, // Mario Tennis Open
|
||||
0x00040000000DCD00, // Mario Golf: World Tour
|
||||
0x00040000000A5300, // Mario Golf: World Tour
|
||||
0x00040000000DCE00, // Mario Golf: World Tour
|
||||
};
|
||||
for (auto id : cpu_limit_ids) {
|
||||
if (title_id == id) {
|
||||
Settings::values.core_downcount_hack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<u64, 10> linear_ids = {
|
||||
0x00040000001AA200, // Attack On Titan 2
|
||||
0x0004000000134500, // Attack On Titan 1 CHAIN
|
||||
@@ -201,6 +215,7 @@ static void LoadOverrides(u64 title_id) {
|
||||
for (auto id : linear_ids) {
|
||||
if (title_id == id) {
|
||||
Settings::values.use_linear_filter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,10 +236,11 @@ static void LoadOverrides(u64 title_id) {
|
||||
for (auto id : fifa_ids) {
|
||||
if (title_id == id) {
|
||||
Settings::values.y2r_event_delay = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<u64, 43> accurate_mul_ids = {
|
||||
const std::array<u64, 49> accurate_mul_ids = {
|
||||
0x0004000000134500, // Attack on Titan
|
||||
0x00040000000DF800, // Attack on Titan
|
||||
0x0004000000152000, // Attack on Titan
|
||||
@@ -268,14 +284,21 @@ static void LoadOverrides(u64 title_id) {
|
||||
0x0004000000125600, // The Legend of Zelda: Majoras Mask 3D
|
||||
0x0004000000125500, // The Legend of Zelda: Majoras Mask 3D
|
||||
0x00040000000D6E00, // The Legend of Zelda: Majoras Mask 3D
|
||||
0x0004000000154700, // Lego City Undercover
|
||||
0x00040000000AD600, // Lego City Undercover
|
||||
0x00040000000AD500, // Lego City Undercover
|
||||
0x00040000001D1800, // Luigi's Mansion
|
||||
0x00040000001D1A00, // Luigi's Mansion
|
||||
0x00040000001D1900, // Luigi's Mansion
|
||||
};
|
||||
for (auto id : accurate_mul_ids) {
|
||||
if (title_id == id) {
|
||||
Settings::values.shaders_accurate_mul = Settings::AccurateMul::FAST;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const std::array<u64, 23> new3ds_game_ids = {
|
||||
const std::array<u64, 30> new3ds_game_ids = {
|
||||
0x000400000F700000, // Xenoblade Chronicles 3D [JPN]
|
||||
0x000400000F700100, // Xenoblade Chronicles 3D [USA]
|
||||
0x000400000F700200, // Xenoblade Chronicles 3D [EUR]
|
||||
@@ -299,10 +322,18 @@ static void LoadOverrides(u64 title_id) {
|
||||
0x00040000001B8700, // Minecraft [USA]
|
||||
0x000400000F707F00, // Hyperlight EX [USA]
|
||||
0x000400000008FE00, // 1001 Spikes [USA]
|
||||
0x000400000007C700, // Mario Tennis Open
|
||||
0x000400000007C800, // Mario Tennis Open
|
||||
0x0004000000064D00, // Mario Tennis Open
|
||||
0x00040000000B9100, // Mario Tennis Open
|
||||
0x00040000000DCD00, // Mario Golf: World Tour
|
||||
0x00040000000A5300, // Mario Golf: World Tour
|
||||
0x00040000000DCE00, // Mario Golf: World Tour
|
||||
};
|
||||
for (auto id : new3ds_game_ids) {
|
||||
if (title_id == id) {
|
||||
Settings::values.is_new_3ds = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,65 +419,41 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
return status;
|
||||
}
|
||||
|
||||
void System::PrepareReschedule() {
|
||||
running_core->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
}
|
||||
|
||||
PerfStats::Results System::GetAndResetPerfStats() {
|
||||
return perf_stats->GetAndResetStats(timing->GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
void System::Reschedule() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
for (const auto& core : cpu_cores) {
|
||||
LOG_TRACE(Core_ARM11, "Reschedule core {}", core->GetID());
|
||||
kernel->GetThreadManager(core->GetID()).Reschedule();
|
||||
}
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode) {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
u32 num_cores = 1;
|
||||
if (Settings::values.is_new_3ds) {
|
||||
num_cores = 4;
|
||||
}
|
||||
|
||||
memory = std::make_unique<Memory::MemorySystem>();
|
||||
|
||||
timing = std::make_unique<Timing>(num_cores);
|
||||
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(
|
||||
*memory, *timing, [this] { PrepareReschedule(); }, system_mode, num_cores, n3ds_mode);
|
||||
timing = std::make_unique<Timing>();
|
||||
kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, system_mode, n3ds_mode);
|
||||
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_ARM64)
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
cpu_cores.push_back(
|
||||
std::make_shared<ARM_Dynarmic>(this, *memory, i, timing->GetTimer(i)));
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
cpu_cores[i] = std::make_shared<ARM_Dynarmic>(this, i, timing->GetTimer(i));
|
||||
kernel->GetThreadManager(i).SetCPU(cpu_cores[i].get());
|
||||
}
|
||||
#else
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
cpu_cores.push_back(
|
||||
std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
cpu_cores[i] = std::make_shared<ARM_DynCom>(this, i, timing->GetTimer(i));
|
||||
kernel->GetThreadManager(i).SetCPU(cpu_cores[i].get());
|
||||
}
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
cpu_cores.push_back(
|
||||
std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
cpu_cores[i] = std::make_shared<ARM_DynCom>(this, i, timing->GetTimer(i));
|
||||
kernel->GetThreadManager(i).SetCPU(cpu_cores[i].get());
|
||||
}
|
||||
}
|
||||
running_core = cpu_cores[0].get();
|
||||
|
||||
kernel->SetCPUs(cpu_cores);
|
||||
kernel->SetRunningCPU(cpu_cores[0].get());
|
||||
if (Settings::values.core_downcount_hack) {
|
||||
SetCpuUsageLimit(true);
|
||||
}
|
||||
|
||||
if (Settings::values.enable_dsp_lle) {
|
||||
dsp_core = std::make_unique<AudioCore::DspLle>(*memory,
|
||||
@@ -486,10 +493,6 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
VideoCore::RendererBase& System::Renderer() {
|
||||
return *VideoCore::g_renderer;
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& System::ServiceManager() {
|
||||
return *service_manager;
|
||||
}
|
||||
@@ -558,6 +561,19 @@ void System::RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> im
|
||||
registered_image_interface = std::move(image_interface);
|
||||
}
|
||||
|
||||
void System::SetCpuUsageLimit(bool enabled) {
|
||||
if (enabled) {
|
||||
u32 hacks[4] = {1, 4, 2, 2};
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
timing->GetTimer(i)->SetDowncountHack(hacks[i]);
|
||||
}
|
||||
} else {
|
||||
for (u32 i = 0; i < 4; ++i) {
|
||||
timing->GetTimer(i)->SetDowncountHack(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::Shutdown() {
|
||||
// Shutdown emulation session
|
||||
GDBStub::Shutdown();
|
||||
@@ -569,7 +585,7 @@ void System::Shutdown() {
|
||||
cheat_engine.reset();
|
||||
archive_manager.reset();
|
||||
service_manager.reset();
|
||||
cpu_cores.clear();
|
||||
cpu_cores = {};
|
||||
dsp_core.reset();
|
||||
kernel.reset();
|
||||
timing.reset();
|
||||
@@ -577,9 +593,6 @@ void System::Shutdown() {
|
||||
app_loader.reset();
|
||||
custom_tex_cache.reset();
|
||||
|
||||
running_core = nullptr;
|
||||
reschedule_pending = false;
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
|
||||
@@ -51,10 +51,6 @@ namespace Cheats {
|
||||
class CheatEngine;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RendererBase;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Timing;
|
||||
@@ -101,7 +97,7 @@ public:
|
||||
*/
|
||||
ResultStatus RunLoop();
|
||||
ResultStatus RunLoopMultiCores();
|
||||
ResultStatus RunLoopOneCore();
|
||||
ResultStatus RunLoopSingleCore();
|
||||
|
||||
/**
|
||||
* Step the CPU one instruction
|
||||
@@ -140,10 +136,8 @@ public:
|
||||
* @returns True if the emulated system is powered on, otherwise false.
|
||||
*/
|
||||
bool IsPoweredOn() const {
|
||||
return cpu_cores.size() > 0 &&
|
||||
std::all_of(cpu_cores.begin(), cpu_cores.end(),
|
||||
return std::all_of(cpu_cores.begin(), cpu_cores.end(),
|
||||
[](std::shared_ptr<ARM_Interface> ptr) { return ptr != nullptr; });
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,20 +148,8 @@ public:
|
||||
return *telemetry_session;
|
||||
}
|
||||
|
||||
/// Prepare the core emulation for a reschedule
|
||||
void PrepareReschedule();
|
||||
|
||||
PerfStats::Results GetAndResetPerfStats();
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated CPU.
|
||||
* @returns A reference to the emulated CPU.
|
||||
*/
|
||||
|
||||
ARM_Interface& GetRunningCore() {
|
||||
return *running_core;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a reference to the emulated CPU.
|
||||
* @param core_id The id of the core requested.
|
||||
@@ -196,8 +178,6 @@ public:
|
||||
return *dsp_core;
|
||||
}
|
||||
|
||||
VideoCore::RendererBase& Renderer();
|
||||
|
||||
/**
|
||||
* Gets a reference to the service manager.
|
||||
* @returns A reference to the service manager.
|
||||
@@ -246,9 +226,6 @@ public:
|
||||
/// Gets a const reference to the custom texture cache system
|
||||
const Core::CustomTexCache& CustomTexCache() const;
|
||||
|
||||
/// Handles loading all custom textures from disk into cache.
|
||||
void PreloadCustomTextures();
|
||||
|
||||
std::unique_ptr<PerfStats> perf_stats;
|
||||
|
||||
void SetStatus(ResultStatus new_status, const char* details = nullptr) {
|
||||
@@ -292,6 +269,9 @@ public:
|
||||
using ScanningCallback = void(bool);
|
||||
std::function<ScanningCallback> nfc_scanning_callback;
|
||||
|
||||
///
|
||||
void SetCpuUsageLimit(bool enabled);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
@@ -302,22 +282,15 @@ private:
|
||||
*/
|
||||
ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode);
|
||||
|
||||
/// Reschedule the core emulation
|
||||
void Reschedule();
|
||||
|
||||
/// AppLoader used to load the current executing application
|
||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||
|
||||
/// ARM11 CPU core
|
||||
std::vector<std::shared_ptr<ARM_Interface>> cpu_cores;
|
||||
ARM_Interface* running_core = nullptr;
|
||||
std::array<std::shared_ptr<ARM_Interface>, 4> cpu_cores;
|
||||
|
||||
/// DSP core
|
||||
std::unique_ptr<AudioCore::DspInterface> dsp_core;
|
||||
|
||||
/// When true, signals that a reschedule should happen
|
||||
bool reschedule_pending = false;
|
||||
|
||||
/// Telemetry session for this emulation session
|
||||
std::unique_ptr<Core::TelemetrySession> telemetry_session;
|
||||
|
||||
@@ -360,7 +333,7 @@ private:
|
||||
};
|
||||
|
||||
inline ARM_Interface& GetRunningCore() {
|
||||
return System::GetInstance().GetRunningCore();
|
||||
return System::GetInstance().Kernel().GetRunningCore();
|
||||
}
|
||||
|
||||
inline ARM_Interface& GetCore(u32 core_id) {
|
||||
|
||||
@@ -20,9 +20,8 @@ bool Timing::Event::operator<(const Timing::Event& right) const {
|
||||
return std::tie(time, fifo_order) < std::tie(right.time, right.fifo_order);
|
||||
}
|
||||
|
||||
Timing::Timing(std::size_t num_cores) {
|
||||
timers.resize(num_cores);
|
||||
for (std::size_t i = 0; i < num_cores; ++i) {
|
||||
Timing::Timing() {
|
||||
for (u32 i = 0; i < timers.size(); ++i) {
|
||||
timers[i] = std::make_shared<Timer>();
|
||||
}
|
||||
current_timer = timers[0].get();
|
||||
@@ -128,10 +127,6 @@ void Timing::Timer::AddTicks(u64 ticks) {
|
||||
downcount -= ticks;
|
||||
}
|
||||
|
||||
u64 Timing::Timer::GetIdleTicks() const {
|
||||
return static_cast<u64>(idled_cycles);
|
||||
}
|
||||
|
||||
void Timing::Timer::ForceExceptionCheck(s64 cycles) {
|
||||
cycles = std::max<s64>(0, cycles);
|
||||
if (downcount > cycles) {
|
||||
@@ -181,7 +176,7 @@ void Timing::Timer::Advance(s64 max_slice_length) {
|
||||
std::min<s64>(event_queue.front().time - executed_ticks, max_slice_length));
|
||||
}
|
||||
|
||||
downcount = slice_length;
|
||||
downcount = slice_length >> downcount_hack;
|
||||
}
|
||||
|
||||
void Timing::Timer::Idle() {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -149,7 +150,6 @@ public:
|
||||
void Idle();
|
||||
|
||||
u64 GetTicks() const;
|
||||
u64 GetIdleTicks() const;
|
||||
|
||||
void AddTicks(u64 ticks);
|
||||
|
||||
@@ -159,6 +159,10 @@ public:
|
||||
|
||||
void MoveEvents();
|
||||
|
||||
void SetDowncountHack(u32 hack) {
|
||||
downcount_hack = hack;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Timing;
|
||||
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
||||
@@ -183,9 +187,10 @@ public:
|
||||
s64 downcount = MAX_SLICE_LENGTH;
|
||||
s64 executed_ticks = 0;
|
||||
u64 idled_cycles = 0;
|
||||
u32 downcount_hack = 0;
|
||||
};
|
||||
|
||||
explicit Timing(std::size_t num_cores);
|
||||
explicit Timing();
|
||||
|
||||
~Timing(){};
|
||||
|
||||
@@ -223,7 +228,7 @@ private:
|
||||
// elements remain stable regardless of rehashes/resizing.
|
||||
std::unordered_map<std::string, TimingEventType> event_types;
|
||||
|
||||
std::vector<std::shared_ptr<Timer>> timers;
|
||||
std::array<std::shared_ptr<Timer>, 4> timers;
|
||||
Timer* current_timer = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -43,10 +43,6 @@ private:
|
||||
};
|
||||
|
||||
EmuWindow::EmuWindow() {
|
||||
// TODO: Find a better place to set this.
|
||||
config.min_client_area_size =
|
||||
std::make_pair(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||
active_config = config;
|
||||
touch_state = std::make_shared<TouchState>();
|
||||
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
|
||||
}
|
||||
|
||||
@@ -32,14 +32,6 @@ namespace Frontend {
|
||||
*/
|
||||
class EmuWindow {
|
||||
public:
|
||||
/// Data structure to store emuwindow configuration
|
||||
struct WindowConfig {
|
||||
bool fullscreen = false;
|
||||
int res_width = 0;
|
||||
int res_height = 0;
|
||||
std::pair<unsigned, unsigned> min_client_area_size;
|
||||
};
|
||||
|
||||
/// Polls window events
|
||||
virtual void PollEvents() = 0;
|
||||
|
||||
@@ -69,25 +61,6 @@ public:
|
||||
*/
|
||||
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
|
||||
|
||||
/**
|
||||
* Returns currently active configuration.
|
||||
* @note Accesses to the returned object need not be consistent because it may be modified in
|
||||
* another thread
|
||||
*/
|
||||
const WindowConfig& GetActiveConfig() const {
|
||||
return active_config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the internal configuration to be replaced by the specified argument at some point in
|
||||
* the future.
|
||||
* @note This method is thread-safe, because it delays configuration changes to the GUI event
|
||||
* loop. Hence there is no guarantee on when the requested configuration will be active.
|
||||
*/
|
||||
void SetConfig(const WindowConfig& val) {
|
||||
config = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the framebuffer layout (width, height, and screen regions)
|
||||
* @note This method is thread-safe
|
||||
@@ -106,23 +79,6 @@ protected:
|
||||
EmuWindow();
|
||||
virtual ~EmuWindow();
|
||||
|
||||
/**
|
||||
* Processes any pending configuration changes from the last SetConfig call.
|
||||
* This method invokes OnMinimalClientAreaChangeRequest if the corresponding configuration
|
||||
* field changed.
|
||||
* @note Implementations will usually want to call this from the GUI thread.
|
||||
* @todo Actually call this in existing implementations.
|
||||
*/
|
||||
void ProcessConfigurationChanges() {
|
||||
// TODO: For proper thread safety, we should eventually implement a proper
|
||||
// multiple-writer/single-reader queue...
|
||||
|
||||
if (config.min_client_area_size != active_config.min_client_area_size) {
|
||||
OnMinimalClientAreaChangeRequest(config.min_client_area_size);
|
||||
active_config.min_client_area_size = config.min_client_area_size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update framebuffer layout with the given parameter.
|
||||
* @note EmuWindow implementations will usually use this in window resize event handlers.
|
||||
@@ -132,21 +88,9 @@ protected:
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Handler called when the minimal client area was requested to be changed via SetConfig.
|
||||
* For the request to be honored, EmuWindow implementations will usually reimplement this
|
||||
* function.
|
||||
*/
|
||||
virtual void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
// By default, ignore this request and do nothing.
|
||||
}
|
||||
|
||||
Layout::FramebufferLayout framebuffer_layout; ///< Current framebuffer layout
|
||||
|
||||
WindowConfig config; ///< Internal configuration (changes pending for being applied in
|
||||
/// ProcessConfigurationChanges)
|
||||
WindowConfig active_config; ///< Internal active configuration
|
||||
|
||||
class TouchState;
|
||||
std::shared_ptr<TouchState> touch_state;
|
||||
|
||||
|
||||
@@ -17,20 +17,17 @@
|
||||
namespace Kernel {
|
||||
|
||||
/// Initialize the kernel
|
||||
KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
||||
std::function<void()> prepare_reschedule_callback, u32 system_mode,
|
||||
u32 num_cores, u8 n3ds_mode)
|
||||
: memory(memory), timing(timing),
|
||||
prepare_reschedule_callback(std::move(prepare_reschedule_callback)) {
|
||||
KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, u32 system_mode,
|
||||
u8 n3ds_mode)
|
||||
: memory(memory), timing(timing) {
|
||||
MemoryInit(system_mode, n3ds_mode);
|
||||
|
||||
resource_limits = std::make_unique<ResourceLimitList>(*this);
|
||||
for (u32 core_id = 0; core_id < num_cores; ++core_id) {
|
||||
thread_managers.push_back(std::make_unique<ThreadManager>(*this, core_id));
|
||||
for (u32 i = 0; i < thread_managers.size(); ++i) {
|
||||
thread_managers[i] = std::make_unique<ThreadManager>(*this, i);
|
||||
}
|
||||
timer_manager = std::make_unique<TimerManager>(timing);
|
||||
ipc_recorder = std::make_unique<IPCDebugger::Recorder>();
|
||||
stored_processes.assign(num_cores, nullptr);
|
||||
|
||||
next_thread_id = 1;
|
||||
}
|
||||
@@ -78,14 +75,6 @@ void KernelSystem::SetCurrentMemoryPageTable(Memory::PageTable* page_table) {
|
||||
}
|
||||
}
|
||||
|
||||
void KernelSystem::SetCPUs(const std::vector<std::shared_ptr<ARM_Interface>>& cpus) {
|
||||
ASSERT(cpus.size() == thread_managers.size());
|
||||
u32 i = 0;
|
||||
for (const auto& cpu : cpus) {
|
||||
thread_managers[i++]->SetCPU(*cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void KernelSystem::SetRunningCPU(ARM_Interface* cpu) {
|
||||
if (current_process) {
|
||||
stored_processes[current_cpu->GetID()] = current_process;
|
||||
@@ -141,6 +130,32 @@ void KernelSystem::AddNamedPort(std::string name, std::shared_ptr<ClientPort> po
|
||||
named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
void KernelSystem::PrepareReschedule() {
|
||||
current_cpu->PrepareReschedule();
|
||||
reschedule_pending = true;
|
||||
}
|
||||
|
||||
/// Reschedule the core emulation
|
||||
void KernelSystem::RescheduleMultiCores() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
for (const auto& manager : thread_managers) {
|
||||
manager->Reschedule();
|
||||
}
|
||||
}
|
||||
|
||||
void KernelSystem::RescheduleSingleCore() {
|
||||
if (!reschedule_pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
reschedule_pending = false;
|
||||
thread_managers[0]->Reschedule();
|
||||
}
|
||||
|
||||
u32 KernelSystem::NewThreadId() {
|
||||
return next_thread_id++;
|
||||
}
|
||||
|
||||
@@ -84,9 +84,8 @@ enum class MemoryRegion : u16 {
|
||||
|
||||
class KernelSystem {
|
||||
public:
|
||||
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
||||
std::function<void()> prepare_reschedule_callback, u32 system_mode,
|
||||
u32 num_cores, u8 n3ds_mode);
|
||||
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, u32 system_mode,
|
||||
u8 n3ds_mode);
|
||||
~KernelSystem();
|
||||
|
||||
using PortPair = std::pair<std::shared_ptr<ServerPort>, std::shared_ptr<ClientPort>>;
|
||||
@@ -217,8 +216,6 @@ public:
|
||||
|
||||
void SetCurrentMemoryPageTable(Memory::PageTable* page_table);
|
||||
|
||||
void SetCPUs(const std::vector<std::shared_ptr<ARM_Interface>>& cpu);
|
||||
|
||||
void SetRunningCPU(ARM_Interface* cpu);
|
||||
|
||||
ThreadManager& GetThreadManager(u32 core_id);
|
||||
@@ -249,9 +246,16 @@ public:
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);
|
||||
|
||||
void PrepareReschedule() {
|
||||
prepare_reschedule_callback();
|
||||
}
|
||||
void PrepareReschedule();
|
||||
|
||||
/// Reschedule the core emulation
|
||||
void RescheduleMultiCores();
|
||||
void RescheduleSingleCore();
|
||||
|
||||
/// Gets a reference to the emulated CPU
|
||||
ARM_Interface& GetRunningCore() {
|
||||
return *current_cpu;
|
||||
};
|
||||
|
||||
u32 NewThreadId();
|
||||
|
||||
@@ -269,8 +273,6 @@ public:
|
||||
private:
|
||||
void MemoryInit(u32 mem_type, u8 n3ds_mode);
|
||||
|
||||
std::function<void()> prepare_reschedule_callback;
|
||||
|
||||
std::unique_ptr<ResourceLimitList> resource_limits;
|
||||
std::atomic<u32> next_object_id{0};
|
||||
|
||||
@@ -283,6 +285,9 @@ private:
|
||||
|
||||
std::unique_ptr<TimerManager> timer_manager;
|
||||
|
||||
/// When true, signals that a reschedule should happen
|
||||
bool reschedule_pending = false;
|
||||
|
||||
// TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
|
||||
// reserved for low-level services
|
||||
u32 next_process_id = 10;
|
||||
@@ -291,9 +296,9 @@ private:
|
||||
std::vector<std::shared_ptr<Process>> process_list;
|
||||
|
||||
std::shared_ptr<Process> current_process;
|
||||
std::vector<std::shared_ptr<Process>> stored_processes;
|
||||
std::array<std::shared_ptr<Process>, 4> stored_processes;
|
||||
|
||||
std::vector<std::unique_ptr<ThreadManager>> thread_managers;
|
||||
std::array<std::unique_ptr<ThreadManager>, 4> thread_managers;
|
||||
|
||||
std::unique_ptr<ConfigMem::Handler> config_mem_handler;
|
||||
std::unique_ptr<SharedPage::Handler> shared_page_handler;
|
||||
|
||||
@@ -183,7 +183,6 @@ private:
|
||||
};
|
||||
|
||||
static const FunctionDef SVC_Table[];
|
||||
static const FunctionDef* GetSVCInfo(u32 func_num);
|
||||
};
|
||||
|
||||
/// Map application or GSP heap memory
|
||||
@@ -298,8 +297,7 @@ void SVC::ExitProcess() {
|
||||
|
||||
// Kill the current thread
|
||||
kernel.GetCurrentThreadManager().GetCurrentThread()->Stop();
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Maps a memory block to specified address
|
||||
@@ -386,7 +384,7 @@ ResultCode SVC::SendSyncRequest(Handle handle) {
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
|
||||
auto thread = SharedFrom(kernel.GetCurrentThreadManager().GetCurrentThread());
|
||||
|
||||
@@ -482,7 +480,7 @@ ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
|
||||
|
||||
thread->wakeup_callback = std::make_shared<SVC_SyncCallback>(false);
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
|
||||
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread
|
||||
// resumes due to a signal in its wait objects.
|
||||
@@ -557,7 +555,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
|
||||
|
||||
thread->wakeup_callback = std::make_shared<SVC_SyncCallback>(false);
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
|
||||
// This value gets set to -1 by default in this case, it is not modified after this.
|
||||
*out = -1;
|
||||
@@ -604,7 +602,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
|
||||
|
||||
thread->wakeup_callback = std::make_shared<SVC_SyncCallback>(true);
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
|
||||
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
|
||||
// signal in one of its wait objects.
|
||||
@@ -746,7 +744,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
|
||||
|
||||
thread->wakeup_callback = std::make_shared<SVC_IPCCallback>(system);
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
|
||||
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
|
||||
// signal in one of its wait objects, or to 0xC8A01836 if there was a translation error.
|
||||
@@ -922,7 +920,7 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
|
||||
|
||||
CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(thread)));
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
|
||||
LOG_TRACE(Kernel_SVC,
|
||||
"called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
|
||||
@@ -934,10 +932,10 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
|
||||
|
||||
/// Called when a thread exits
|
||||
void SVC::ExitThread() {
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.GetRunningCore().GetPC());
|
||||
LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", kernel.GetRunningCore().GetPC());
|
||||
|
||||
kernel.GetCurrentThreadManager().ExitCurrentThread();
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Gets the priority for the specified thread
|
||||
@@ -975,14 +973,14 @@ ResultCode SVC::SetThreadPriority(Handle handle, u32 priority) {
|
||||
for (auto& mutex : thread->pending_mutexes)
|
||||
mutex->UpdatePriority();
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
/// Create a mutex
|
||||
ResultCode SVC::CreateMutex(Handle* out_handle, u32 initial_locked) {
|
||||
std::shared_ptr<Mutex> mutex = kernel.CreateMutex(initial_locked != 0);
|
||||
mutex->name = fmt::format("mutex-{:08x}", system.GetRunningCore().GetReg(14));
|
||||
mutex->name = fmt::format("mutex-{:08x}", kernel.GetRunningCore().GetReg(14));
|
||||
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(mutex)));
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called initial_locked={} : created handle=0x{:08X}",
|
||||
@@ -1049,7 +1047,7 @@ ResultCode SVC::GetThreadId(u32* thread_id, Handle handle) {
|
||||
ResultCode SVC::CreateSemaphore(Handle* out_handle, s32 initial_count, s32 max_count) {
|
||||
CASCADE_RESULT(std::shared_ptr<Semaphore> semaphore,
|
||||
kernel.CreateSemaphore(initial_count, max_count));
|
||||
semaphore->name = fmt::format("semaphore-{:08x}", system.GetRunningCore().GetReg(14));
|
||||
semaphore->name = fmt::format("semaphore-{:08x}", kernel.GetRunningCore().GetReg(14));
|
||||
CASCADE_RESULT(*out_handle,
|
||||
kernel.GetCurrentProcess()->handle_table.Create(std::move(semaphore)));
|
||||
|
||||
@@ -1121,7 +1119,7 @@ ResultCode SVC::QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, u32 ad
|
||||
ResultCode SVC::CreateEvent(Handle* out_handle, u32 reset_type) {
|
||||
std::shared_ptr<Event> evt =
|
||||
kernel.CreateEvent(static_cast<ResetType>(reset_type),
|
||||
fmt::format("event-{:08x}", system.GetRunningCore().GetReg(14)));
|
||||
fmt::format("event-{:08x}", kernel.GetRunningCore().GetReg(14)));
|
||||
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(evt)));
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
|
||||
@@ -1165,7 +1163,7 @@ ResultCode SVC::ClearEvent(Handle handle) {
|
||||
ResultCode SVC::CreateTimer(Handle* out_handle, u32 reset_type) {
|
||||
std::shared_ptr<Timer> timer =
|
||||
kernel.CreateTimer(static_cast<ResetType>(reset_type),
|
||||
fmt ::format("timer-{:08x}", system.GetRunningCore().GetReg(14)));
|
||||
fmt ::format("timer-{:08x}", kernel.GetRunningCore().GetReg(14)));
|
||||
CASCADE_RESULT(*out_handle, kernel.GetCurrentProcess()->handle_table.Create(std::move(timer)));
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called reset_type=0x{:08X} : created handle=0x{:08X}", reset_type,
|
||||
@@ -1232,16 +1230,16 @@ void SVC::SleepThread(s64 nanoseconds) {
|
||||
// Create an event to wake the thread up after the specified nanosecond delay has passed
|
||||
thread_manager.GetCurrentThread()->WakeAfterDelay(nanoseconds);
|
||||
|
||||
system.PrepareReschedule();
|
||||
kernel.PrepareReschedule();
|
||||
}
|
||||
|
||||
/// This returns the total CPU ticks elapsed since the CPU was powered-on
|
||||
s64 SVC::GetSystemTick() {
|
||||
// TODO: Use globalTicks here?
|
||||
s64 result = system.GetRunningCore().GetTimer().GetTicks();
|
||||
s64 result = kernel.GetRunningCore().GetTimer().GetTicks();
|
||||
// Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end.
|
||||
// Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b
|
||||
system.GetRunningCore().GetTimer().AddTicks(150);
|
||||
kernel.GetRunningCore().GetTimer().AddTicks(150);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1295,7 +1293,7 @@ ResultCode SVC::CreateMemoryBlock(Handle* out_handle, u32 addr, u32 size, u32 my
|
||||
static_cast<MemoryPermission>(other_permission), addr, region));
|
||||
CASCADE_RESULT(*out_handle, current_process->handle_table.Create(std::move(shared_memory)));
|
||||
|
||||
LOG_WARNING(Kernel_SVC, "called addr=0x{:08X}", addr);
|
||||
LOG_WARNING(Kernel_SVC, "SVC::CreateMemoryBlock called addr=0x{:08X}, size={}", addr, size);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1600,43 +1598,35 @@ const SVC::FunctionDef SVC::SVC_Table[] = {
|
||||
{0x7D, &SVC::Wrap<&SVC::QueryProcessMemory>, "QueryProcessMemory"},
|
||||
};
|
||||
|
||||
const SVC::FunctionDef* SVC::GetSVCInfo(u32 func_num) {
|
||||
if (func_num >= ARRAY_SIZE(SVC_Table)) {
|
||||
LOG_ERROR(Kernel_SVC, "unknown svc=0x{:02X}", func_num);
|
||||
return nullptr;
|
||||
}
|
||||
return &SVC_Table[func_num];
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
|
||||
|
||||
void SVC::CallSVC(u32 immediate) {
|
||||
MICROPROFILE_SCOPE(Kernel_SVC);
|
||||
|
||||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard lock{HLE::g_hle_lock};
|
||||
|
||||
DEBUG_ASSERT_MSG(kernel.GetCurrentProcess()->status == ProcessStatus::Running,
|
||||
"Running threads from exiting processes is unimplemented");
|
||||
|
||||
const FunctionDef* info = GetSVCInfo(immediate);
|
||||
if (info) {
|
||||
if (info->func) {
|
||||
(this->*(info->func))();
|
||||
if (immediate < ARRAY_SIZE(SVC_Table)) {
|
||||
const auto& info = SVC_Table[immediate];
|
||||
if (info.func) {
|
||||
(this->*(info.func))();
|
||||
} else {
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented SVC function {}(..)", info->name);
|
||||
LOG_ERROR(Kernel_SVC, "unimplemented SVC function {}(..)", info.name);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Kernel_SVC, "unknown svc=0x{:02X}", immediate);
|
||||
}
|
||||
}
|
||||
|
||||
SVC::SVC(Core::System& system) : system(system), kernel(system.Kernel()), memory(system.Memory()) {}
|
||||
|
||||
u32 SVC::GetReg(std::size_t n) {
|
||||
return system.GetRunningCore().GetReg(static_cast<int>(n));
|
||||
return kernel.GetRunningCore().GetReg(static_cast<int>(n));
|
||||
}
|
||||
|
||||
void SVC::SetReg(std::size_t n, u32 value) {
|
||||
system.GetRunningCore().SetReg(static_cast<int>(n), value);
|
||||
kernel.GetRunningCore().SetReg(static_cast<int>(n), value);
|
||||
}
|
||||
|
||||
SVCContext::SVCContext(Core::System& system) : impl(std::make_unique<SVC>(system)) {}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/skyeye_common/armstate.h"
|
||||
#include "core/core.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -268,13 +269,13 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name,
|
||||
// TODO(yuriks): Other checks, returning 0xD9001BEA
|
||||
|
||||
if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) {
|
||||
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point);
|
||||
LOG_ERROR(Kernel_SVC, "(name={}): create thread invalid entry {:08x}", name, entry_point);
|
||||
// TODO: Verify error
|
||||
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
|
||||
ErrorSummary::InvalidArgument, ErrorLevel::Permanent);
|
||||
}
|
||||
|
||||
if (processor_id >= thread_managers.size()) {
|
||||
if (!Settings::values.is_new_3ds || processor_id >= thread_managers.size()) {
|
||||
processor_id = 0;
|
||||
}
|
||||
|
||||
@@ -371,20 +372,19 @@ void Thread::BoostPriority(u32 priority) {
|
||||
current_priority = priority;
|
||||
}
|
||||
|
||||
std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority,
|
||||
std::shared_ptr<Process> owner_process) {
|
||||
void SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority, Process& owner_process) {
|
||||
// Initialize new "main" thread
|
||||
auto thread_res =
|
||||
kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor,
|
||||
Memory::HEAP_VADDR_END, *owner_process);
|
||||
kernel.CreateThread("main", entry_point, priority, 0, owner_process.ideal_processor,
|
||||
Memory::HEAP_VADDR_END, owner_process);
|
||||
if (thread_res.Failed()) {
|
||||
ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
|
||||
|
||||
thread->context->SetFpscr(FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO |
|
||||
FPSCR_IXC); // 0x03C00010
|
||||
|
||||
// Note: The newly created thread will be run when the scheduler fires.
|
||||
return thread;
|
||||
}
|
||||
|
||||
bool ThreadManager::HaveReadyThreads() {
|
||||
|
||||
@@ -101,8 +101,8 @@ public:
|
||||
*/
|
||||
const std::vector<std::shared_ptr<Thread>>& GetThreadList();
|
||||
|
||||
void SetCPU(ARM_Interface& cpu) {
|
||||
this->cpu = &cpu;
|
||||
void SetCPU(ARM_Interface* cpu) {
|
||||
this->cpu = cpu;
|
||||
}
|
||||
|
||||
std::unique_ptr<ARM_Interface::ThreadContext> NewContext() {
|
||||
@@ -299,9 +299,7 @@ private:
|
||||
* @param entry_point The address at which the thread should start execution
|
||||
* @param priority The priority to give the main thread
|
||||
* @param owner_process The parent process for the main thread
|
||||
* @return A shared pointer to the main thread
|
||||
*/
|
||||
std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority,
|
||||
std::shared_ptr<Process> owner_process);
|
||||
void SetupMainThread(KernelSystem& kernel, u32 entry_point, u32 priority, Process& owner_process);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -267,7 +267,7 @@ void Module::APTInterface::GetSharedFont(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(-1); // TODO: Find the right error code
|
||||
rb.Push<u32>(0);
|
||||
rb.PushCopyObjects<Kernel::Object>(nullptr);
|
||||
apt->system.SetStatus(Core::System::ResultStatus::ErrorSystemFiles, "Shared fonts");
|
||||
apt->system.SetStatus(Core::System::ResultStatus::ErrorSystemFiles, "Shared fonts missing");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,8 +153,9 @@ void ERR_F::ThrowFatalError(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
LOG_CRITICAL(Service_ERR, "Fatal error");
|
||||
const ErrInfo errinfo = rp.PopRaw<ErrInfo>();
|
||||
LOG_CRITICAL(Service_ERR, "Fatal error type: {}", GetErrType(errinfo.errinfo_common.specifier));
|
||||
system.SetStatus(Core::System::ResultStatus::ErrorUnknown);
|
||||
const std::string errdetail = GetErrType(errinfo.errinfo_common.specifier);
|
||||
LOG_CRITICAL(Service_ERR, "Fatal error type: {}", );
|
||||
system.SetStatus(Core::System::ResultStatus::ErrorUnknown, errdetail.c_str());
|
||||
|
||||
// Generic Info
|
||||
LogGenericInfo(errinfo.errinfo_common);
|
||||
|
||||
@@ -345,4 +345,9 @@ ResultStatus AppLoader_THREEDSX::ReadIcon(std::vector<u8>& buffer) {
|
||||
return ResultStatus::ErrorNotUsed;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_THREEDSX::ReadTitle(std::string& title) {
|
||||
title = filename;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
|
||||
ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;
|
||||
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
|
||||
private:
|
||||
std::string filename;
|
||||
std::string filepath;
|
||||
|
||||
@@ -409,4 +409,9 @@ ResultStatus AppLoader_ELF::Load(std::shared_ptr<Kernel::Process>& process) {
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_ELF::ReadTitle(std::string& title) {
|
||||
title = filename;
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
@@ -33,6 +33,8 @@ public:
|
||||
|
||||
ResultStatus Load(std::shared_ptr<Kernel::Process>& process) override;
|
||||
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
|
||||
private:
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
@@ -117,6 +117,55 @@ void SetFMVHack(bool enable) {
|
||||
}
|
||||
}
|
||||
|
||||
void SetLLEModules(const std::string& modules) {
|
||||
std::size_t first = 0;
|
||||
std::size_t last = 0;
|
||||
std::size_t iter;
|
||||
Settings::values.lle_modules.clear();
|
||||
while (true) {
|
||||
iter = modules.find(',', first);
|
||||
if (iter != std::string::npos) {
|
||||
last = iter - 1;
|
||||
} else if (first < modules.size()) {
|
||||
last = modules.size() - 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
// trim spaces
|
||||
while (std::isspace(modules[first])) {
|
||||
if (first < last) {
|
||||
++first;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (std::isspace(modules[last])) {
|
||||
if (last > first) {
|
||||
--last;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// set module
|
||||
if (last > first) {
|
||||
std::string module_name;
|
||||
for (u32 i = first; i <= last; ++i) {
|
||||
module_name += std::toupper(modules[i]);
|
||||
}
|
||||
Settings::values.lle_modules[module_name] = true;
|
||||
}
|
||||
|
||||
// continue
|
||||
if (iter != std::string::npos) {
|
||||
first = iter + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadProfile(int index) {
|
||||
Settings::values.current_input_profile = Settings::values.input_profiles[index];
|
||||
Settings::values.current_input_profile_index = index;
|
||||
|
||||
@@ -196,6 +196,7 @@ struct Values {
|
||||
std::unordered_map<std::string, bool> lle_modules;
|
||||
|
||||
u32 core_ticks_hack;
|
||||
bool core_downcount_hack;
|
||||
bool allow_shadow;
|
||||
bool use_separable_shader;
|
||||
bool use_shader_cache;
|
||||
@@ -225,6 +226,7 @@ void Apply();
|
||||
void LogSettings();
|
||||
|
||||
void SetFMVHack(bool enable);
|
||||
void SetLLEModules(const std::string& modules);
|
||||
|
||||
// Input profiles
|
||||
void LoadProfile(int index);
|
||||
|
||||
@@ -159,7 +159,7 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
|
||||
while (IsConnected()) {
|
||||
std::lock_guard lock(network_mutex);
|
||||
ENetEvent event;
|
||||
if (enet_host_service(client, &event, 100) > 0) {
|
||||
if (enet_host_service(client, &event, 32) > 0) {
|
||||
switch (event.type) {
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
switch (event.packet->data[0]) {
|
||||
@@ -251,16 +251,18 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Packet> packets;
|
||||
{
|
||||
std::lock_guard lock(send_list_mutex);
|
||||
for (const auto& packet : send_list) {
|
||||
ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
|
||||
ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(server, 0, enetPacket);
|
||||
}
|
||||
enet_host_flush(client);
|
||||
send_list.clear();
|
||||
packets.swap(send_list);
|
||||
}
|
||||
for (const auto& packet : packets) {
|
||||
ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
|
||||
ENET_PACKET_FLAG_RELIABLE);
|
||||
enet_peer_send(server, 0, enetPacket);
|
||||
}
|
||||
enet_host_flush(client);
|
||||
}
|
||||
Disconnect();
|
||||
};
|
||||
|
||||
@@ -51,7 +51,12 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a
|
||||
|
||||
void OGLStreamBuffer::Unmap(GLsizeiptr size) {
|
||||
if (size > 0) {
|
||||
glFlushMappedBufferRange(gl_target, buffer_pos, size);
|
||||
// flush is relative to the start of the currently mapped range of buffer
|
||||
glFlushMappedBufferRange(gl_target, 0, size);
|
||||
GLenum error = glGetError();
|
||||
if (error != GL_NO_ERROR) {
|
||||
LOG_DEBUG(Render_OpenGL, "flush mapped buffer range error: {:04X}, target: {:04X}, offset: {}, size: {}, total: {}", error, gl_target, buffer_pos, size, buffer_size);
|
||||
}
|
||||
}
|
||||
glUnmapBuffer(gl_target);
|
||||
buffer_pos += size;
|
||||
|
||||
@@ -18,6 +18,7 @@ enum class MessageType {
|
||||
Typeless,
|
||||
HWShader,
|
||||
CPUJit,
|
||||
New3DS,
|
||||
};
|
||||
|
||||
namespace Color {
|
||||
|
||||
@@ -516,8 +516,6 @@ bool RendererOpenGL::TryPresent() {
|
||||
frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -704,6 +702,10 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
|
||||
// init
|
||||
OSD::Initialize();
|
||||
if (Settings::values.is_new_3ds) {
|
||||
OSD::AddMessage("New 3DS Model", OSD::MessageType::New3DS, OSD::Duration::NORMAL,
|
||||
OSD::Color::YELLOW);
|
||||
}
|
||||
if (!Settings::values.use_hw_shader) {
|
||||
OSD::AddMessage("HW Shader Off", OSD::MessageType::HWShader, OSD::Duration::NORMAL,
|
||||
OSD::Color::YELLOW);
|
||||
|
||||
Reference in New Issue
Block a user