mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2024-11-27 03:54:17 +01:00
Prepare frontend for multiple graphics APIs (#6347)
* externals: Update dynarmic * settings: Introduce GraphicsAPI enum * For now it's OpenGL only but will be expanded upon later * citra_qt: Introduce backend agnostic context management * Mostly a direct port from yuzu * core: Simplify context acquire * settings: Add option to create debug contexts * renderer_opengl: Abstract initialization to Driver * This commit also updates glad and adds some useful extensions which we will use in part 2 * Rasterizer construction is moved to the specific renderer instead of RendererBase. Software rendering has been disable to achieve this but will be brought back in the next commit. * video_core: Remove Init/Shutdown methods from renderer * The constructor and destructor can do the same job * In addition move opengl function loading to Qt since SDL already does this. Also remove ErrorVideoCore which is never reached * citra_qt: Decouple software renderer from opengl part 1 * citra: Decouple software renderer from opengl part 2 * android: Decouple software renderer from opengl part 3 * swrasterizer: Decouple software renderer from opengl part 4 * This commit simply enforces the renderer naming conventions in the software renderer * video_core: Move RendererBase to VideoCore * video_core: De-globalize screenshot state * video_core: Pass system to the renderers * video_core: Commonize shader uniform data * video_core: Abstract backend agnostic rasterizer operations * bootmanager: Remove references to OpenGL for macOS OpenGL macOS headers definitions clash heavily with each other * citra_qt: Proper title for api settings * video_core: Reduce boost usage * bootmanager: Fix hide mouse option Remove event handlers from RenderWidget for events that are already handled by the parent GRenderWindow. Also enable mouse tracking on the RenderWidget. * android: Remove software from graphics api list * code: Address review comments * citra: Port per-game settings read * Having to update the default value for all backends is a pain so lets centralize it * android: Rename to OpenGLES --------- Co-authored-by: MerryMage <MerryMage@users.noreply.github.com> Co-authored-by: Vitor Kiguchi <vitor-kiguchi@hotmail.com>
This commit is contained in:
parent
9ef42040af
commit
b5d6f645bd
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
@ -1 +1 @@
|
||||
Subproject commit 9af4b970d302389829448a30608c7cb4fce9b662
|
||||
Subproject commit b3a92ab54dadd26a0c2a87d2677b80249d2e1a5a
|
2
externals/glad/Readme.md
vendored
2
externals/glad/Readme.md
vendored
@ -1,5 +1,5 @@
|
||||
These files were generated by the [glad](https://github.com/Dav1dde/glad) OpenGL loader generator and have been checked in as-is. You can re-generate them using glad with the following command:
|
||||
|
||||
```
|
||||
python -m glad --profile core --out-path glad/ --api "gl=3.3,gles2=3.2" --generator=c
|
||||
python -m glad --profile core --out-path glad/ --api "gl=4.6,gles2=3.2" --generator=c
|
||||
```
|
||||
|
1614
externals/glad/include/glad/glad.h
vendored
1614
externals/glad/include/glad/glad.h
vendored
File diff suppressed because it is too large
Load Diff
776
externals/glad/src/glad.c
vendored
776
externals/glad/src/glad.c
vendored
File diff suppressed because it is too large
Load Diff
@ -496,9 +496,6 @@ public final class NativeLibrary {
|
||||
final int ErrorLoader_ErrorEncrypted = 5;
|
||||
final int ErrorLoader_ErrorInvalidFormat = 6;
|
||||
final int ErrorSystemFiles = 7;
|
||||
final int ErrorVideoCore = 8;
|
||||
final int ErrorVideoCore_ErrorGenericDrivers = 9;
|
||||
final int ErrorVideoCore_ErrorBelowGL33 = 10;
|
||||
final int ShutdownRequested = 11;
|
||||
final int ErrorUnknown = 12;
|
||||
|
||||
|
@ -355,6 +355,7 @@ public final class SettingsFragmentPresenter {
|
||||
mView.getActivity().setTitle(R.string.preferences_graphics);
|
||||
|
||||
SettingSection rendererSection = mSettings.getSection(Settings.SECTION_RENDERER);
|
||||
Setting graphicsApi = rendererSection.getSetting(SettingsFile.KEY_GRAPHICS_API);
|
||||
Setting resolutionFactor = rendererSection.getSetting(SettingsFile.KEY_RESOLUTION_FACTOR);
|
||||
Setting filterMode = rendererSection.getSetting(SettingsFile.KEY_FILTER_MODE);
|
||||
Setting shadersAccurateMul = rendererSection.getSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL);
|
||||
@ -371,6 +372,7 @@ public final class SettingsFragmentPresenter {
|
||||
//Setting preloadTextures = utilitySection.getSetting(SettingsFile.KEY_PRELOAD_TEXTURES);
|
||||
|
||||
sl.add(new HeaderSetting(null, null, R.string.renderer, 0));
|
||||
sl.add(new SingleChoiceSetting(SettingsFile.KEY_GRAPHICS_API, Settings.SECTION_RENDERER, R.string.graphics_api, 0, R.array.graphicsApiNames, R.array.graphicsApiValues, 0, graphicsApi));
|
||||
sl.add(new SliderSetting(SettingsFile.KEY_RESOLUTION_FACTOR, Settings.SECTION_RENDERER, R.string.internal_resolution, R.string.internal_resolution_description, 1, 4, "x", 1, resolutionFactor));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_FILTER_MODE, Settings.SECTION_RENDERER, R.string.linear_filtering, R.string.linear_filtering_description, true, filterMode));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL, Settings.SECTION_RENDERER, R.string.shaders_accurate_mul, R.string.shaders_accurate_mul_description, false, shadersAccurateMul));
|
||||
@ -409,14 +411,14 @@ public final class SettingsFragmentPresenter {
|
||||
SettingSection coreSection = mSettings.getSection(Settings.SECTION_CORE);
|
||||
SettingSection rendererSection = mSettings.getSection(Settings.SECTION_RENDERER);
|
||||
Setting useCpuJit = coreSection.getSetting(SettingsFile.KEY_CPU_JIT);
|
||||
Setting hardwareRenderer = rendererSection.getSetting(SettingsFile.KEY_HW_RENDERER);
|
||||
Setting hardwareShader = rendererSection.getSetting(SettingsFile.KEY_HW_SHADER);
|
||||
Setting vsyncEnable = rendererSection.getSetting(SettingsFile.KEY_USE_VSYNC);
|
||||
Setting rendererDebug = rendererSection.getSetting(SettingsFile.KEY_RENDERER_DEBUG);
|
||||
|
||||
sl.add(new HeaderSetting(null, null, R.string.debug_warning, 0));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_JIT, Settings.SECTION_CORE, R.string.cpu_jit, R.string.cpu_jit_description, true, useCpuJit, true, mView));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_RENDERER, Settings.SECTION_RENDERER, R.string.hw_renderer, R.string.hw_renderer_description, true, hardwareRenderer, true, mView));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_SHADER, Settings.SECTION_RENDERER, R.string.hw_shaders, R.string.hw_shaders_description, true, hardwareShader, true, mView));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_VSYNC, Settings.SECTION_RENDERER, R.string.vsync, R.string.vsync_description, true, vsyncEnable));
|
||||
sl.add(new CheckBoxSetting(SettingsFile.KEY_RENDERER_DEBUG, Settings.SECTION_RENDERER, R.string.renderer_debug, R.string.renderer_debug_description, false, rendererDebug));
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,8 @@ public final class SettingsFile {
|
||||
|
||||
public static final String KEY_PREMIUM = "premium";
|
||||
|
||||
public static final String KEY_HW_RENDERER = "use_hw_renderer";
|
||||
public static final String KEY_GRAPHICS_API = "graphics_api";
|
||||
public static final String KEY_RENDERER_DEBUG = "renderer_debug";
|
||||
public static final String KEY_HW_SHADER = "use_hw_shader";
|
||||
public static final String KEY_SHADERS_ACCURATE_MUL = "shaders_accurate_mul";
|
||||
public static final String KEY_USE_SHADER_JIT = "use_shader_jit";
|
||||
|
@ -82,6 +82,30 @@ void Config::UpdateCFG() {
|
||||
cfg->UpdateConfigNANDSavegame();
|
||||
}
|
||||
|
||||
template <>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
||||
std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
|
||||
if (setting_value.empty()) {
|
||||
setting_value = setting.GetDefault();
|
||||
}
|
||||
setting = std::move(setting_value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
||||
setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
|
||||
}
|
||||
|
||||
template <typename Type, bool ranged>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
||||
if constexpr (std::is_floating_point_v<Type>) {
|
||||
setting = sdl2_config->GetReal(group, setting.GetLabel(), setting.GetDefault());
|
||||
} else {
|
||||
setting = static_cast<Type>(sdl2_config->GetInteger(
|
||||
group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
|
||||
}
|
||||
}
|
||||
|
||||
void Config::ReadValues() {
|
||||
// Controls
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
@ -112,39 +136,32 @@ void Config::ReadValues() {
|
||||
InputCommon::CemuhookUDP::DEFAULT_PORT));
|
||||
|
||||
// Core
|
||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||
Settings::values.cpu_clock_percentage =
|
||||
static_cast<int>(sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100));
|
||||
ReadSetting("Core", Settings::values.use_cpu_jit);
|
||||
ReadSetting("Core", Settings::values.cpu_clock_percentage);
|
||||
|
||||
// Premium
|
||||
Settings::values.texture_filter_name =
|
||||
sdl2_config->GetString("Premium", "texture_filter_name", "none");
|
||||
ReadSetting("Premium", Settings::values.texture_filter_name);
|
||||
|
||||
// Renderer
|
||||
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", true);
|
||||
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
||||
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
|
||||
Settings::values.shaders_accurate_mul =
|
||||
sdl2_config->GetBoolean("Renderer", "shaders_accurate_mul", false);
|
||||
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
|
||||
Settings::values.resolution_factor =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "resolution_factor", 1));
|
||||
Settings::values.use_disk_shader_cache =
|
||||
sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", true);
|
||||
Settings::values.use_vsync_new = sdl2_config->GetBoolean("Renderer", "use_vsync_new", true);
|
||||
ReadSetting("Renderer", Settings::values.graphics_api);
|
||||
ReadSetting("Renderer", Settings::values.use_hw_shader);
|
||||
ReadSetting("Renderer", Settings::values.use_shader_jit);
|
||||
ReadSetting("Renderer", Settings::values.resolution_factor);
|
||||
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
|
||||
ReadSetting("Renderer", Settings::values.use_vsync_new);
|
||||
|
||||
// Work around to map Android setting for enabling the frame limiter to the format Citra expects
|
||||
if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) {
|
||||
Settings::values.frame_limit =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
|
||||
ReadSetting("Renderer", Settings::values.frame_limit);
|
||||
} else {
|
||||
Settings::values.frame_limit = 0;
|
||||
}
|
||||
|
||||
Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
|
||||
sdl2_config->GetInteger("Renderer", "render_3d", 0));
|
||||
Settings::values.factor_3d =
|
||||
static_cast<u8>(sdl2_config->GetInteger("Renderer", "factor_3d", 0));
|
||||
ReadSetting("Renderer", Settings::values.render_3d);
|
||||
ReadSetting("Renderer", Settings::values.factor_3d);
|
||||
std::string default_shader = "none (builtin)";
|
||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph)
|
||||
default_shader = "dubois (builtin)";
|
||||
@ -152,70 +169,49 @@ void Config::ReadValues() {
|
||||
default_shader = "horizontal (builtin)";
|
||||
Settings::values.pp_shader_name =
|
||||
sdl2_config->GetString("Renderer", "pp_shader_name", default_shader);
|
||||
Settings::values.filter_mode = sdl2_config->GetBoolean("Renderer", "filter_mode", true);
|
||||
ReadSetting("Renderer", Settings::values.filter_mode);
|
||||
|
||||
Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
|
||||
Settings::values.bg_green =
|
||||
static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0));
|
||||
Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0));
|
||||
ReadSetting("Renderer", Settings::values.bg_red);
|
||||
ReadSetting("Renderer", Settings::values.bg_green);
|
||||
ReadSetting("Renderer", Settings::values.bg_blue);
|
||||
|
||||
// Layout
|
||||
Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger(
|
||||
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::MobileLandscape)));
|
||||
Settings::values.custom_layout = sdl2_config->GetBoolean("Layout", "custom_layout", false);
|
||||
Settings::values.custom_top_left =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0));
|
||||
Settings::values.custom_top_top =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_top", 0));
|
||||
Settings::values.custom_top_right =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_right", 400));
|
||||
Settings::values.custom_top_bottom =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_bottom", 240));
|
||||
Settings::values.custom_bottom_left =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_left", 40));
|
||||
Settings::values.custom_bottom_top =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_top", 240));
|
||||
Settings::values.custom_bottom_right =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_right", 360));
|
||||
Settings::values.custom_bottom_bottom =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480));
|
||||
Settings::values.cardboard_screen_size =
|
||||
static_cast<int>(sdl2_config->GetInteger("Layout", "cardboard_screen_size", 85));
|
||||
Settings::values.cardboard_x_shift =
|
||||
static_cast<int>(sdl2_config->GetInteger("Layout", "cardboard_x_shift", 0));
|
||||
Settings::values.cardboard_y_shift =
|
||||
static_cast<int>(sdl2_config->GetInteger("Layout", "cardboard_y_shift", 0));
|
||||
ReadSetting("Layout", Settings::values.custom_layout);
|
||||
ReadSetting("Layout", Settings::values.custom_top_left);
|
||||
ReadSetting("Layout", Settings::values.custom_top_top);
|
||||
ReadSetting("Layout", Settings::values.custom_top_right);
|
||||
ReadSetting("Layout", Settings::values.custom_top_bottom);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_left);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_top);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_right);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_bottom);
|
||||
ReadSetting("Layout", Settings::values.cardboard_screen_size);
|
||||
ReadSetting("Layout", Settings::values.cardboard_x_shift);
|
||||
ReadSetting("Layout", Settings::values.cardboard_y_shift);
|
||||
|
||||
// Utility
|
||||
Settings::values.dump_textures = sdl2_config->GetBoolean("Utility", "dump_textures", false);
|
||||
Settings::values.custom_textures = sdl2_config->GetBoolean("Utility", "custom_textures", false);
|
||||
Settings::values.preload_textures =
|
||||
sdl2_config->GetBoolean("Utility", "preload_textures", false);
|
||||
ReadSetting("Utility", Settings::values.dump_textures);
|
||||
ReadSetting("Utility", Settings::values.custom_textures);
|
||||
ReadSetting("Utility", Settings::values.preload_textures);
|
||||
|
||||
// Audio
|
||||
Settings::values.audio_emulation =
|
||||
static_cast<Settings::AudioEmulation>(sdl2_config->GetInteger(
|
||||
"Audio", "audio_emulation", static_cast<int>(Settings::AudioEmulation::HLE)));
|
||||
Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
|
||||
Settings::values.enable_audio_stretching =
|
||||
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||
Settings::values.audio_device_id = sdl2_config->GetString("Audio", "output_device", "auto");
|
||||
Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1));
|
||||
Settings::values.mic_input_device =
|
||||
sdl2_config->GetString("Audio", "mic_input_device", "Default");
|
||||
Settings::values.mic_input_type =
|
||||
static_cast<Settings::MicInputType>(sdl2_config->GetInteger("Audio", "mic_input_type", 1));
|
||||
ReadSetting("Audio", Settings::values.audio_emulation);
|
||||
ReadSetting("Audio", Settings::values.sink_id);
|
||||
ReadSetting("Audio", Settings::values.enable_audio_stretching);
|
||||
ReadSetting("Audio", Settings::values.audio_device_id);
|
||||
ReadSetting("Audio", Settings::values.volume);
|
||||
ReadSetting("Audio", Settings::values.mic_input_device);
|
||||
ReadSetting("Audio", Settings::values.mic_input_type);
|
||||
|
||||
// Data Storage
|
||||
Settings::values.use_virtual_sd =
|
||||
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
||||
ReadSetting("Data Storage", Settings::values.use_virtual_sd);
|
||||
|
||||
// System
|
||||
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", true);
|
||||
Settings::values.region_value =
|
||||
sdl2_config->GetInteger("System", "region_value", Settings::REGION_VALUE_AUTO_SELECT);
|
||||
Settings::values.init_clock =
|
||||
static_cast<Settings::InitClock>(sdl2_config->GetInteger("System", "init_clock", 0));
|
||||
ReadSetting("System", Settings::values.is_new_3ds);
|
||||
ReadSetting("System", Settings::values.region_value);
|
||||
ReadSetting("System", Settings::values.init_clock);
|
||||
{
|
||||
std::tm t;
|
||||
t.tm_sec = 1;
|
||||
@ -236,10 +232,8 @@ void Config::ReadValues() {
|
||||
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
Settings::values.plugin_loader_enabled =
|
||||
sdl2_config->GetBoolean("System", "plugin_loader", false);
|
||||
Settings::values.allow_plugin_loader =
|
||||
sdl2_config->GetBoolean("System", "allow_plugin_loader", true);
|
||||
ReadSetting("System", Settings::values.plugin_loader_enabled);
|
||||
ReadSetting("System", Settings::values.allow_plugin_loader);
|
||||
|
||||
// Camera
|
||||
using namespace Service::CAM;
|
||||
@ -263,14 +257,14 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetInteger("Camera", "camera_outer_left_flip", 0);
|
||||
|
||||
// Miscellaneous
|
||||
Settings::values.log_filter = sdl2_config->GetString("Miscellaneous", "log_filter", "*:Info");
|
||||
ReadSetting("Miscellaneous", Settings::values.log_filter);
|
||||
|
||||
// Debugging
|
||||
Settings::values.record_frame_times =
|
||||
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
|
||||
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
|
||||
Settings::values.gdbstub_port =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
||||
ReadSetting("Debugging", Settings::values.renderer_debug);
|
||||
ReadSetting("Debugging", Settings::values.use_gdbstub);
|
||||
ReadSetting("Debugging", Settings::values.gdbstub_port);
|
||||
|
||||
for (const auto& service_module : Service::service_module_map) {
|
||||
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/settings.h"
|
||||
|
||||
class INIReader;
|
||||
|
||||
@ -23,4 +24,14 @@ public:
|
||||
~Config();
|
||||
|
||||
void Reload();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Applies a value read from the sdl2_config to a Setting.
|
||||
*
|
||||
* @param group The name of the INI group
|
||||
* @param setting The yuzu setting to modify
|
||||
*/
|
||||
template <typename Type, bool ranged>
|
||||
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
||||
};
|
||||
|
@ -98,13 +98,9 @@ use_cpu_jit =
|
||||
cpu_clock_percentage =
|
||||
|
||||
[Renderer]
|
||||
# Whether to render using GLES or OpenGL
|
||||
# 0: OpenGL, 1 (default): GLES
|
||||
use_gles =
|
||||
|
||||
# Whether to use software or hardware rendering.
|
||||
# 0: Software, 1 (default): Hardware
|
||||
use_hw_renderer =
|
||||
# Whether to render using OpenGL
|
||||
# 1: OpenGLES (default)
|
||||
graphics_api =
|
||||
|
||||
# Whether to use hardware shaders to emulate 3DS shaders
|
||||
# 0: Software, 1 (default): Hardware
|
||||
@ -118,10 +114,6 @@ separable_shader =
|
||||
# 0: Off (Default. Faster, but causes issues in some games) 1: On (Slower, but correct)
|
||||
shaders_accurate_mul =
|
||||
|
||||
# Enable asynchronous GPU emulation
|
||||
# 0: Off (Slower, but more accurate) 1: On (Default. Faster, but may cause issues in some games)
|
||||
use_asynchronous_gpu_emulation =
|
||||
|
||||
# Whether to use the Just-In-Time (JIT) compiler for shader emulation
|
||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||
use_shader_jit =
|
||||
@ -325,9 +317,15 @@ log_filter = *:Info
|
||||
[Debugging]
|
||||
# Record frame time data, can be found in the log directory. Boolean value
|
||||
record_frame_times =
|
||||
|
||||
# Whether to enable additional debugging information during emulation
|
||||
# 0 (default): Off, 1: On
|
||||
renderer_debug =
|
||||
|
||||
# Port for listening to GDB connections.
|
||||
use_gdbstub=false
|
||||
gdbstub_port=24689
|
||||
|
||||
# To LLE a service module add "LLE\<module name>=true"
|
||||
|
||||
[WebService]
|
||||
|
@ -25,7 +25,6 @@
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/camera/factory.h"
|
||||
#include "core/frontend/mic.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/savestate.h"
|
||||
@ -46,6 +45,7 @@
|
||||
#include "jni/ndk_motion.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@ -149,7 +149,15 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||
return Core::System::ResultStatus::ErrorLoader;
|
||||
}
|
||||
|
||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||
switch (graphics_api) {
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
window = std::make_unique<EmuWindow_Android>(s_surf);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Frontend, "Unknown graphics API {}, using OpenGL", graphics_api);
|
||||
window = std::make_unique<EmuWindow_Android>(s_surf);
|
||||
}
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">Aktiviert lineare Filterung, welche die Spieletexturen glättet.</string>
|
||||
<string name="texture_filter_name">Texturfilter</string>
|
||||
<string name="texture_filter_description">Verbessert die Grafik von Spielen durch das Anwendung eines Texturfilters. Die unterstützten Filter sind Anime4K Ultrafast, Bicubic, ScaleForce und xBRZ freescale.</string>
|
||||
<string name="hw_renderer">Aktiviere Hardware Renderer</string>
|
||||
<string name="hw_renderer_description">Benutzt Hardware, um die 3DS-Grafik zu emulieren. Wenn aktiviert, wird die Spieleleistung stark verbessert.</string>
|
||||
<string name="hw_shaders">Aktiviere Hardware Shader</string>
|
||||
<string name="hw_shaders_description">Benutzt Hardware, um die 3DS-Shader zu emulieren. Wenn aktiviert, wird die Spieleleistung stark verbessert.</string>
|
||||
<string name="shaders_accurate_mul">Aktiviere genaue Shader-Multiplikation</string>
|
||||
|
@ -62,8 +62,6 @@
|
||||
<string name="linear_filtering_description">Activa el filtro linear, que hace que los gráficos del juego se vean más suaves.</string>
|
||||
<string name="texture_filter_name">Filtro de Texturas</string>
|
||||
<string name="texture_filter_description">Mejora los gráficos visuales de los juegos al aplicar un filtro a las texturas. Los filtros soportados son Anime4K Ultrafast, Bicubic, ScaleForce, y xBRZ freescale.</string>
|
||||
<string name="hw_renderer">Activar renderizador de hardware</string>
|
||||
<string name="hw_renderer_description">Usa el hardware para emular los gráficos de 3DS. Cuando se active, el rendimiento mejorará notablemente.</string>
|
||||
<string name="hw_shaders">Activar sombreador de hardware</string>
|
||||
<string name="hw_shaders_description">Usa el hardware para emular los sombreadores de 3DS. Cuando se active, el rendimiento mejorará notablemente.</string>
|
||||
<string name="shaders_accurate_mul">Activar multiplicación precisa de sombreado</string>
|
||||
|
@ -46,8 +46,6 @@
|
||||
<!-- Graphics settings strings -->
|
||||
<string name="vsync">Aktivoi V-Sync</string>
|
||||
<string name="vsync_description">Synkronoi pelin virkistystaajuus laitteesi virkistystaajuuteen.</string>
|
||||
<string name="hw_renderer">Aktivoi Laitteistorenderöinti</string>
|
||||
<string name="hw_renderer_description">Käyttää laitteistoa emuloidakseen 3DS-grafiikoita. Kun tämä on päällä, pelien suorituskyky on huomattavasti parempi.</string>
|
||||
<string name="hw_shaders">Aktivoi Laitteistovarjostin</string>
|
||||
<string name="hw_shaders_description">Käyttää laitteistoa emuloidakseen 3DS:n varjostimia. Kun tämä on päällä, pelien suorituskyky on huomattavasti parempi.</string>
|
||||
<string name="frame_limit_enable">Aktivoi nopeuden rajoitus</string>
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">Active le filtrage linéaire, qui améliorera le lissage graphique du jeu.</string>
|
||||
<string name="texture_filter_name">Filtrage des textures</string>
|
||||
<string name="texture_filter_description">Améliore l\'aspect graphique des jeux en appliquant un filtre aux textures. Les filtres supportés sont Anime4K Ultrafast, Bicubic, ScaleForce, et xBRZ freescale.</string>
|
||||
<string name="hw_renderer">Activer le rendu matériel</string>
|
||||
<string name="hw_renderer_description">Utilise le matériel pour émuler les graphismes de la 3DS. Lorsqu\'il est activé, la performance des jeux sera améliorée de manière significative.</string>
|
||||
<string name="hw_shaders">Activer le shader (nuanceur) matériel </string>
|
||||
<string name="hw_shaders_description">Utilise le matériel pour émuler les shaders de la 3DS. Lorsqu\'il est activé, la performance des jeux sera améliorée de manière significative.</string>
|
||||
<string name="shaders_accurate_mul">Activer la multiplication précise dans les shaders</string>
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">Abilita il filtro lineare, che fa sembrare più smussata la grafica dei giochi.</string>
|
||||
<string name="texture_filter_name">Filtro Texture</string>
|
||||
<string name="texture_filter_description">Migliora la grafica dei giochi applicando un filtro alle texture. I filtri supportati sono Anime4k Ultrafast, Bicubic, ScaleForce, e xBRZ freescale.</string>
|
||||
<string name="hw_renderer">Abilita renderer hardware</string>
|
||||
<string name="hw_renderer_description">Utilizza l\'hardware per emulare la grafica del 3DS. Se abilitato, le prestazioni dei giochi miglioreranno significativamente.</string>
|
||||
<string name="hw_shaders">Abilita shader hardware</string>
|
||||
<string name="hw_shaders_description">Utilizza l\'hardware per emulare gli shader del 3DS. Se abilitato, le prestazioni dei giochi miglioreranno significativamente.</string>
|
||||
<string name="shaders_accurate_mul">Abilita moltiplicazione shader accurata</string>
|
||||
|
@ -40,8 +40,6 @@
|
||||
<string name="linear_filtering">リニアフィルタリングを有効化</string>
|
||||
<string name="linear_filtering_description">有効にすると、よりなめらかな画質が期待できます。</string>
|
||||
<string name="texture_filter_name">テクスチャフィルタ</string>
|
||||
<string name="hw_renderer">ハードウェアレンダラを有効にする</string>
|
||||
<string name="hw_renderer_description">グラフィックエミュレーションにハードウェアを使用します。有効にすると、パフォーマンスが大幅に向上します。</string>
|
||||
<string name="hw_shaders">ハードウェアシェーダを有効にする</string>
|
||||
<string name="hw_shaders_description">シェーダエミュレーションにハードウェアを使用します。有効にすると、パフォーマンスが大幅に向上します。</string>
|
||||
<string name="shaders_accurate_mul">正確なシェーダ乗算を有効にする</string>
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">게임 필터링이 매끄럽게 보이도록 선형 필터링을 활성화합니다.</string>
|
||||
<string name="texture_filter_name">텍스처 필터</string>
|
||||
<string name="texture_filter_description">텍스처에 필터를 적용하여 게임의 시각적 효과를 향상시킵니다. 지원되는 필터는 Anime4K Ultrafast, Bicubic, ScaleForce 및 xBRZ 프리스케일입니다.</string>
|
||||
<string name="hw_renderer">하드웨어 렌더러 사용</string>
|
||||
<string name="hw_renderer_description">하드웨어를 사용하여 3DS 그래픽을 에뮬레이션합니다. 활성화하면 게임 성능이 크게 향상됩니다.</string>
|
||||
<string name="hw_shaders">하드웨어 쉐이더 사용</string>
|
||||
<string name="hw_shaders_description">하드웨어를 사용하여 3DS 쉐이더를 에뮬레이션합니다. 활성화하면 게임 성능이 크게 향상됩니다.</string>
|
||||
<string name="shaders_accurate_mul">정확한 쉐이더 곱셉 사용</string>
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">Aktiverer lineær filtrering, noe som får spillvisualer til å vises jevnere.</string>
|
||||
<string name="texture_filter_name">Tekstur Filter</string>
|
||||
<string name="texture_filter_description">Forbedrer det visuelle i spill ved å bruke et filter på teksturer. De støttede filtrene er Anime4K Ultrafast, Bicubic, ScaleForce og xBRZ freescale.</string>
|
||||
<string name="hw_renderer">Aktiver maskinvaregjengivelse</string>
|
||||
<string name="hw_renderer_description">Bruker maskinvare til å emulere 3DS grafikk. Når dette er aktivert, vil spillytelsen bli betydelig forbedret.</string>
|
||||
<string name="hw_shaders">Aktiver maskinvare shader</string>
|
||||
<string name="hw_shaders_description">Bruker maskinvare for å etterligne 3DS shaders. Når dette er aktivert, vil spillytelsen bli betydelig forbedret.</string>
|
||||
<string name="shaders_accurate_mul">Aktiver nøyaktig shader-multiplikasjon</string>
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">Ativa a filtragem linear, que suaviza o visual do jogo.</string>
|
||||
<string name="texture_filter_name">Filtro de texturas</string>
|
||||
<string name="texture_filter_description">Aprimora o visual dos jogos ao aplicar filtros às texturas. Os filtros compatíveis são: Anime4K Ultrafast, Bicúbico, ScaleForce e xBRZ Freescale.</string>
|
||||
<string name="hw_renderer">Ativar renderizador por hardware</string>
|
||||
<string name="hw_renderer_description">Utiliza o hardware para emular os gráficos do 3DS. Quando ativado, o desempenho de jogo será consideravelmente melhorado.</string>
|
||||
<string name="hw_shaders">Ativar shaders via hardware</string>
|
||||
<string name="hw_shaders_description">Utiliza o hardware para emular os shaders do 3DS. Quando ativado, o desempenho do jogo será consideravelmente melhorado.</string>
|
||||
<string name="shaders_accurate_mul">Ativar multiplicação precisa de shaders</string>
|
||||
|
@ -58,8 +58,6 @@
|
||||
<string name="linear_filtering_description">开启后,游戏视觉效果会更加平滑。</string>
|
||||
<string name="texture_filter_name">纹理滤镜</string>
|
||||
<string name="texture_filter_description">通过对纹理使用滤镜来增强游戏的视觉效果。支持的滤镜有 Anime4K Ultrafast, Bicubic, ScaleForce 和 xBRZ freescale。</string>
|
||||
<string name="hw_renderer">启用硬件渲染器</string>
|
||||
<string name="hw_renderer_description">使用硬件模拟 3DS 图形。启用后,游戏性能将显著提高。</string>
|
||||
<string name="hw_shaders">启用硬件着色器</string>
|
||||
<string name="hw_shaders_description">使用硬件模拟 3DS 着色器。启用后,游戏性能将显著提高。</string>
|
||||
<string name="shaders_accurate_mul">启用精确乘法运算</string>
|
||||
|
@ -171,4 +171,12 @@
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="graphicsApiNames">
|
||||
<item>OpenGLES</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="graphicsApiValues">
|
||||
<item>1</item>
|
||||
</integer-array>
|
||||
</resources>
|
||||
|
@ -72,14 +72,15 @@
|
||||
|
||||
<!-- Graphics settings strings -->
|
||||
<string name="renderer">Renderer</string>
|
||||
<string name="graphics_api">Graphics API</string>
|
||||
<string name="renderer_debug">Enable debug renderer</string>
|
||||
<string name="renderer_debug_description">Log additional graphics related debug information. When enabled, game performance will be significantly reduced</string>
|
||||
<string name="vsync">Enable V-Sync</string>
|
||||
<string name="vsync_description">Synchronizes the game frame rate to the refresh rate of your device.</string>
|
||||
<string name="linear_filtering">Enable linear filtering</string>
|
||||
<string name="linear_filtering_description">Enables linear filtering, which causes game visuals to appear smoother.</string>
|
||||
<string name="texture_filter_name">Texture Filter</string>
|
||||
<string name="texture_filter_description">Enhances the visuals of games by applying a filter to textures. The supported filters are Anime4K Ultrafast, Bicubic, ScaleForce, and xBRZ freescale.</string>
|
||||
<string name="hw_renderer">Enable hardware renderer</string>
|
||||
<string name="hw_renderer_description">Uses hardware to emulate 3DS graphics. When enabled, game performance will be significantly improved.</string>
|
||||
<string name="hw_shaders">Enable hardware shader</string>
|
||||
<string name="hw_shaders_description">Uses hardware to emulate 3DS shaders. When enabled, game performance will be significantly improved.</string>
|
||||
<string name="shaders_accurate_mul">Enable accurate shader multiplication</string>
|
||||
|
@ -8,6 +8,10 @@ add_executable(citra
|
||||
default_ini.h
|
||||
emu_window/emu_window_sdl2.cpp
|
||||
emu_window/emu_window_sdl2.h
|
||||
emu_window/emu_window_sdl2_gl.cpp
|
||||
emu_window/emu_window_sdl2_gl.h
|
||||
emu_window/emu_window_sdl2_sw.cpp
|
||||
emu_window/emu_window_sdl2_sw.h
|
||||
lodepng_image_interface.cpp
|
||||
lodepng_image_interface.h
|
||||
precompiled_headers.h
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
#include "citra/config.h"
|
||||
#include "citra/emu_window/emu_window_sdl2.h"
|
||||
#include "citra/emu_window/emu_window_sdl2_gl.h"
|
||||
#include "citra/emu_window/emu_window_sdl2_sw.h"
|
||||
#include "citra/lodepng_image_interface.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/detached_tasks.h"
|
||||
@ -29,7 +31,6 @@
|
||||
#include "core/file_sys/cia_container.h"
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
@ -38,6 +39,7 @@
|
||||
#include "input_common/main.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#undef _UNICODE
|
||||
#include <getopt.h>
|
||||
@ -362,13 +364,23 @@ int main(int argc, char** argv) {
|
||||
|
||||
EmuWindow_SDL2::InitializeSDL2();
|
||||
|
||||
const auto emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen, false)};
|
||||
const bool use_secondary_window{Settings::values.layout_option.GetValue() ==
|
||||
Settings::LayoutOption::SeparateWindows};
|
||||
const auto secondary_window =
|
||||
use_secondary_window ? std::make_unique<EmuWindow_SDL2>(false, true) : nullptr;
|
||||
const auto CreateEmuWindow = [](bool fullscreen,
|
||||
bool is_secondary) -> std::unique_ptr<EmuWindow_SDL2> {
|
||||
switch (Settings::values.graphics_api.GetValue()) {
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
return std::make_unique<EmuWindow_SDL2_GL>(fullscreen, is_secondary);
|
||||
case Settings::GraphicsAPI::Software:
|
||||
return std::make_unique<EmuWindow_SDL2_SW>(fullscreen, is_secondary);
|
||||
}
|
||||
};
|
||||
|
||||
Frontend::ScopeAcquireContext scope(*emu_window);
|
||||
const auto emu_window{CreateEmuWindow(fullscreen, false)};
|
||||
const bool use_secondary_window{
|
||||
Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows &&
|
||||
Settings::values.graphics_api.GetValue() != Settings::GraphicsAPI::Software};
|
||||
const auto secondary_window = use_secondary_window ? CreateEmuWindow(false, true) : nullptr;
|
||||
|
||||
const auto scope = emu_window->Acquire();
|
||||
|
||||
LOG_INFO(Frontend, "Citra Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch,
|
||||
Common::g_scm_desc);
|
||||
@ -400,9 +412,6 @@ int main(int argc, char** argv) {
|
||||
case Core::System::ResultStatus::ErrorSystemMode:
|
||||
LOG_CRITICAL(Frontend, "Failed to determine system mode!");
|
||||
return -1;
|
||||
case Core::System::ResultStatus::ErrorVideoCore:
|
||||
LOG_CRITICAL(Frontend, "VideoCore not initialized");
|
||||
return -1;
|
||||
case Core::System::ResultStatus::Success:
|
||||
break; // Expected case
|
||||
default:
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <SDL.h>
|
||||
#include <inih/cpp/INIReader.h>
|
||||
@ -71,6 +72,30 @@ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs>
|
||||
},
|
||||
}};
|
||||
|
||||
template <>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<std::string>& setting) {
|
||||
std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault());
|
||||
if (setting_value.empty()) {
|
||||
setting_value = setting.GetDefault();
|
||||
}
|
||||
setting = std::move(setting_value);
|
||||
}
|
||||
|
||||
template <>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<bool>& setting) {
|
||||
setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault());
|
||||
}
|
||||
|
||||
template <typename Type, bool ranged>
|
||||
void Config::ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting) {
|
||||
if constexpr (std::is_floating_point_v<Type>) {
|
||||
setting = sdl2_config->GetReal(group, setting.GetLabel(), setting.GetDefault());
|
||||
} else {
|
||||
setting = static_cast<Type>(sdl2_config->GetInteger(
|
||||
group, setting.GetLabel(), static_cast<long>(setting.GetDefault())));
|
||||
}
|
||||
}
|
||||
|
||||
void Config::ReadValues() {
|
||||
// Controls
|
||||
// TODO: add multiple input profile support
|
||||
@ -104,104 +129,71 @@ void Config::ReadValues() {
|
||||
InputCommon::CemuhookUDP::DEFAULT_PORT));
|
||||
|
||||
// Core
|
||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||
Settings::values.cpu_clock_percentage =
|
||||
sdl2_config->GetInteger("Core", "cpu_clock_percentage", 100);
|
||||
ReadSetting("Core", Settings::values.use_cpu_jit);
|
||||
ReadSetting("Core", Settings::values.cpu_clock_percentage);
|
||||
|
||||
// Renderer
|
||||
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", false);
|
||||
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
|
||||
Settings::values.use_hw_shader = sdl2_config->GetBoolean("Renderer", "use_hw_shader", true);
|
||||
ReadSetting("Renderer", Settings::values.graphics_api);
|
||||
ReadSetting("Renderer", Settings::values.use_gles);
|
||||
ReadSetting("Renderer", Settings::values.use_hw_shader);
|
||||
#ifdef __APPLE__
|
||||
// Separable shader is broken on macos with Intel GPU thanks to poor drivers.
|
||||
// We still want to provide this option for test/development purposes, but disable it by
|
||||
// default.
|
||||
Settings::values.separable_shader =
|
||||
sdl2_config->GetBoolean("Renderer", "separable_shader", false);
|
||||
ReadSetting("Renderer", Settings::values.separable_shader);
|
||||
#endif
|
||||
Settings::values.shaders_accurate_mul =
|
||||
sdl2_config->GetBoolean("Renderer", "shaders_accurate_mul", true);
|
||||
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
|
||||
Settings::values.resolution_factor =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "resolution_factor", 1));
|
||||
Settings::values.use_disk_shader_cache =
|
||||
sdl2_config->GetBoolean("Renderer", "use_disk_shader_cache", true);
|
||||
Settings::values.frame_limit =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
|
||||
Settings::values.use_vsync_new =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync_new", 1));
|
||||
Settings::values.texture_filter_name =
|
||||
sdl2_config->GetString("Renderer", "texture_filter_name", "none");
|
||||
ReadSetting("Renderer", Settings::values.shaders_accurate_mul);
|
||||
ReadSetting("Renderer", Settings::values.use_shader_jit);
|
||||
ReadSetting("Renderer", Settings::values.resolution_factor);
|
||||
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
|
||||
ReadSetting("Renderer", Settings::values.frame_limit);
|
||||
ReadSetting("Renderer", Settings::values.use_vsync_new);
|
||||
ReadSetting("Renderer", Settings::values.texture_filter_name);
|
||||
|
||||
Settings::values.mono_render_option = static_cast<Settings::MonoRenderOption>(
|
||||
sdl2_config->GetInteger("Renderer", "mono_render_option", 0));
|
||||
Settings::values.render_3d = static_cast<Settings::StereoRenderOption>(
|
||||
sdl2_config->GetInteger("Renderer", "render_3d", 0));
|
||||
Settings::values.factor_3d =
|
||||
static_cast<u8>(sdl2_config->GetInteger("Renderer", "factor_3d", 0));
|
||||
Settings::values.pp_shader_name =
|
||||
sdl2_config->GetString("Renderer", "pp_shader_name", "none (builtin)");
|
||||
Settings::values.anaglyph_shader_name =
|
||||
sdl2_config->GetString("Renderer", "anaglyph_shader_name", "dubois (builtin)");
|
||||
Settings::values.filter_mode = sdl2_config->GetBoolean("Renderer", "filter_mode", true);
|
||||
ReadSetting("Renderer", Settings::values.mono_render_option);
|
||||
ReadSetting("Renderer", Settings::values.render_3d);
|
||||
ReadSetting("Renderer", Settings::values.factor_3d);
|
||||
ReadSetting("Renderer", Settings::values.pp_shader_name);
|
||||
ReadSetting("Renderer", Settings::values.anaglyph_shader_name);
|
||||
ReadSetting("Renderer", Settings::values.filter_mode);
|
||||
|
||||
Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0));
|
||||
Settings::values.bg_green =
|
||||
static_cast<float>(sdl2_config->GetReal("Renderer", "bg_green", 0.0));
|
||||
Settings::values.bg_blue = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_blue", 0.0));
|
||||
ReadSetting("Renderer", Settings::values.bg_red);
|
||||
ReadSetting("Renderer", Settings::values.bg_green);
|
||||
ReadSetting("Renderer", Settings::values.bg_blue);
|
||||
|
||||
// Layout
|
||||
Settings::values.layout_option =
|
||||
static_cast<Settings::LayoutOption>(sdl2_config->GetInteger("Layout", "layout_option", 0));
|
||||
Settings::values.swap_screen = sdl2_config->GetBoolean("Layout", "swap_screen", false);
|
||||
Settings::values.upright_screen = sdl2_config->GetBoolean("Layout", "upright_screen", false);
|
||||
Settings::values.large_screen_proportion =
|
||||
sdl2_config->GetReal("Layout", "large_screen_proportion", 4.0);
|
||||
Settings::values.custom_layout = sdl2_config->GetBoolean("Layout", "custom_layout", false);
|
||||
Settings::values.custom_top_left =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0));
|
||||
Settings::values.custom_top_top =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_top", 0));
|
||||
Settings::values.custom_top_right =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_right", 400));
|
||||
Settings::values.custom_top_bottom =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_bottom", 240));
|
||||
Settings::values.custom_bottom_left =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_left", 40));
|
||||
Settings::values.custom_bottom_top =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_top", 240));
|
||||
Settings::values.custom_bottom_right =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_right", 360));
|
||||
Settings::values.custom_bottom_bottom =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_bottom_bottom", 480));
|
||||
Settings::values.custom_second_layer_opacity =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_second_layer_opacity", 100));
|
||||
ReadSetting("Layout", Settings::values.layout_option);
|
||||
ReadSetting("Layout", Settings::values.swap_screen);
|
||||
ReadSetting("Layout", Settings::values.upright_screen);
|
||||
ReadSetting("Layout", Settings::values.large_screen_proportion);
|
||||
ReadSetting("Layout", Settings::values.custom_layout);
|
||||
ReadSetting("Layout", Settings::values.custom_top_left);
|
||||
ReadSetting("Layout", Settings::values.custom_top_top);
|
||||
ReadSetting("Layout", Settings::values.custom_top_right);
|
||||
ReadSetting("Layout", Settings::values.custom_top_bottom);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_left);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_top);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_right);
|
||||
ReadSetting("Layout", Settings::values.custom_bottom_bottom);
|
||||
ReadSetting("Layout", Settings::values.custom_second_layer_opacity);
|
||||
|
||||
// Utility
|
||||
Settings::values.dump_textures = sdl2_config->GetBoolean("Utility", "dump_textures", false);
|
||||
Settings::values.custom_textures = sdl2_config->GetBoolean("Utility", "custom_textures", false);
|
||||
Settings::values.preload_textures =
|
||||
sdl2_config->GetBoolean("Utility", "preload_textures", false);
|
||||
ReadSetting("Utility", Settings::values.dump_textures);
|
||||
ReadSetting("Utility", Settings::values.custom_textures);
|
||||
ReadSetting("Utility", Settings::values.preload_textures);
|
||||
|
||||
// Audio
|
||||
Settings::values.audio_emulation = static_cast<Settings::AudioEmulation>(
|
||||
sdl2_config->GetInteger("Audio", "audio_emulation", 0));
|
||||
Settings::values.sink_id = sdl2_config->GetString("Audio", "output_engine", "auto");
|
||||
Settings::values.enable_audio_stretching =
|
||||
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
|
||||
Settings::values.audio_device_id = sdl2_config->GetString("Audio", "output_device", "auto");
|
||||
Settings::values.volume = static_cast<float>(sdl2_config->GetReal("Audio", "volume", 1));
|
||||
Settings::values.mic_input_device =
|
||||
sdl2_config->GetString("Audio", "mic_input_device", Frontend::Mic::default_device_name);
|
||||
Settings::values.mic_input_type =
|
||||
static_cast<Settings::MicInputType>(sdl2_config->GetInteger("Audio", "mic_input_type", 0));
|
||||
ReadSetting("Audio", Settings::values.audio_emulation);
|
||||
ReadSetting("Audio", Settings::values.sink_id);
|
||||
ReadSetting("Audio", Settings::values.enable_audio_stretching);
|
||||
ReadSetting("Audio", Settings::values.audio_device_id);
|
||||
ReadSetting("Audio", Settings::values.volume);
|
||||
ReadSetting("Audio", Settings::values.mic_input_device);
|
||||
ReadSetting("Audio", Settings::values.mic_input_type);
|
||||
|
||||
// Data Storage
|
||||
Settings::values.use_virtual_sd =
|
||||
sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
|
||||
|
||||
Settings::values.use_custom_storage =
|
||||
sdl2_config->GetBoolean("Data Storage", "use_custom_storage", false);
|
||||
ReadSetting("Data Storage", Settings::values.use_virtual_sd);
|
||||
ReadSetting("Data Storage", Settings::values.use_custom_storage);
|
||||
|
||||
if (Settings::values.use_custom_storage) {
|
||||
FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir,
|
||||
@ -211,11 +203,9 @@ void Config::ReadValues() {
|
||||
}
|
||||
|
||||
// System
|
||||
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", true);
|
||||
Settings::values.region_value =
|
||||
sdl2_config->GetInteger("System", "region_value", Settings::REGION_VALUE_AUTO_SELECT);
|
||||
Settings::values.init_clock =
|
||||
static_cast<Settings::InitClock>(sdl2_config->GetInteger("System", "init_clock", 1));
|
||||
ReadSetting("System", Settings::values.is_new_3ds);
|
||||
ReadSetting("System", Settings::values.region_value);
|
||||
ReadSetting("System", Settings::values.init_clock);
|
||||
{
|
||||
std::tm t;
|
||||
t.tm_sec = 1;
|
||||
@ -236,6 +226,8 @@ void Config::ReadValues() {
|
||||
std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch())
|
||||
.count();
|
||||
}
|
||||
ReadSetting("System", Settings::values.plugin_loader_enabled);
|
||||
ReadSetting("System", Settings::values.allow_plugin_loader);
|
||||
|
||||
{
|
||||
constexpr const char* default_init_time_offset = "0 00:00:00";
|
||||
@ -311,14 +303,14 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetInteger("Camera", "camera_outer_left_flip", 0);
|
||||
|
||||
// Miscellaneous
|
||||
Settings::values.log_filter = sdl2_config->GetString("Miscellaneous", "log_filter", "*:Info");
|
||||
ReadSetting("Miscellaneous", Settings::values.log_filter);
|
||||
|
||||
// Debugging
|
||||
Settings::values.record_frame_times =
|
||||
sdl2_config->GetBoolean("Debugging", "record_frame_times", false);
|
||||
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
|
||||
Settings::values.gdbstub_port =
|
||||
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
||||
ReadSetting("Debugging", Settings::values.renderer_debug);
|
||||
ReadSetting("Debugging", Settings::values.use_gdbstub);
|
||||
ReadSetting("Debugging", Settings::values.gdbstub_port);
|
||||
|
||||
for (const auto& service_module : Service::service_module_map) {
|
||||
bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/settings.h"
|
||||
|
||||
class INIReader;
|
||||
|
||||
@ -21,4 +22,14 @@ public:
|
||||
~Config();
|
||||
|
||||
void Reload();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Applies a value read from the sdl2_config to a Setting.
|
||||
*
|
||||
* @param group The name of the INI group
|
||||
* @param setting The yuzu setting to modify
|
||||
*/
|
||||
template <typename Type, bool ranged>
|
||||
void ReadSetting(const std::string& group, Settings::Setting<Type, ranged>& setting);
|
||||
};
|
||||
|
@ -98,14 +98,14 @@ use_cpu_jit =
|
||||
cpu_clock_percentage =
|
||||
|
||||
[Renderer]
|
||||
# Whether to render using OpenGL or Software
|
||||
# 0: Software, 1: OpenGL (default)
|
||||
graphics_api =
|
||||
|
||||
# Whether to render using GLES or OpenGL
|
||||
# 0 (default): OpenGL, 1: GLES
|
||||
use_gles =
|
||||
|
||||
# Whether to use software or hardware rendering.
|
||||
# 0: Software, 1 (default): Hardware
|
||||
use_hw_renderer =
|
||||
|
||||
# Whether to use hardware shaders to emulate 3DS shaders
|
||||
# 0: Software, 1 (default): Hardware
|
||||
use_hw_shader =
|
||||
@ -328,9 +328,15 @@ log_filter = *:Info
|
||||
[Debugging]
|
||||
# Record frame time data, can be found in the log directory. Boolean value
|
||||
record_frame_times =
|
||||
|
||||
# Port for listening to GDB connections.
|
||||
use_gdbstub=false
|
||||
gdbstub_port=24689
|
||||
|
||||
# Whether to enable additional debugging information during emulation
|
||||
# 0 (default): Off, 1: On
|
||||
renderer_debug =
|
||||
|
||||
# To LLE a service module add "LLE\<module name>=true"
|
||||
|
||||
[WebService]
|
||||
|
@ -7,40 +7,14 @@
|
||||
#include <string>
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <fmt/format.h>
|
||||
#include <glad/glad.h>
|
||||
#include "citra/emu_window/emu_window_sdl2.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/3ds.h"
|
||||
#include "core/core.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
#include "input_common/sdl/sdl.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
SharedContext_SDL2::SharedContext_SDL2() {
|
||||
window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
|
||||
context = SDL_GL_CreateContext(window);
|
||||
}
|
||||
|
||||
SharedContext_SDL2::~SharedContext_SDL2() {
|
||||
SDL_GL_DeleteContext(context);
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
void SharedContext_SDL2::MakeCurrent() {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
}
|
||||
|
||||
void SharedContext_SDL2::DoneCurrent() {
|
||||
SDL_GL_MakeCurrent(window, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
|
||||
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
|
||||
@ -135,78 +109,9 @@ void EmuWindow_SDL2::Fullscreen() {
|
||||
SDL_MaximizeWindow(render_window);
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen, bool is_secondary) : EmuWindow(is_secondary) {
|
||||
// Initialize the window
|
||||
if (Settings::values.use_gles) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
} else {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
// Enable context sharing for the shared context
|
||||
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
|
||||
// Enable vsync
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
|
||||
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
}
|
||||
|
||||
window_context = SDL_GL_CreateContext(render_window);
|
||||
core_context = CreateSharedContext();
|
||||
last_saved_context = nullptr;
|
||||
|
||||
if (window_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
if (core_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
render_window_id = SDL_GetWindowID(render_window);
|
||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
|
||||
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool is_secondary) : EmuWindow(is_secondary) {}
|
||||
|
||||
EmuWindow_SDL2::~EmuWindow_SDL2() {
|
||||
core_context.reset();
|
||||
SDL_GL_DeleteContext(window_context);
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
@ -222,28 +127,6 @@ void EmuWindow_SDL2::InitializeSDL2() {
|
||||
SDL_SetMainReady();
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
|
||||
return std::make_unique<SharedContext_SDL2>();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::SaveContext() {
|
||||
last_saved_context = SDL_GL_GetCurrentContext();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::RestoreContext() {
|
||||
SDL_GL_MakeCurrent(render_window, last_saved_context);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::Present() {
|
||||
SDL_GL_MakeCurrent(render_window, window_context);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
while (IsOpen()) {
|
||||
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||
SDL_GL_SwapWindow(render_window);
|
||||
}
|
||||
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::PollEvents() {
|
||||
SDL_Event event;
|
||||
std::vector<SDL_Event> other_window_events;
|
||||
@ -312,14 +195,6 @@ void EmuWindow_SDL2::PollEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::MakeCurrent() {
|
||||
core_context->MakeCurrent();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::DoneCurrent() {
|
||||
core_context->DoneCurrent();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
|
||||
SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
|
||||
}
|
||||
|
@ -10,56 +10,27 @@
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
class SharedContext_SDL2 : public Frontend::GraphicsContext {
|
||||
public:
|
||||
using SDL_GLContext = void*;
|
||||
|
||||
SharedContext_SDL2();
|
||||
|
||||
~SharedContext_SDL2() override;
|
||||
|
||||
void MakeCurrent() override;
|
||||
|
||||
void DoneCurrent() override;
|
||||
|
||||
private:
|
||||
SDL_GLContext context;
|
||||
SDL_Window* window;
|
||||
};
|
||||
|
||||
class EmuWindow_SDL2 : public Frontend::EmuWindow {
|
||||
public:
|
||||
explicit EmuWindow_SDL2(bool fullscreen, bool is_secondary);
|
||||
explicit EmuWindow_SDL2(bool is_secondary);
|
||||
~EmuWindow_SDL2();
|
||||
|
||||
/// Initializes SDL2
|
||||
static void InitializeSDL2();
|
||||
|
||||
void Present();
|
||||
/// Presents the most recent frame from the video backend
|
||||
virtual void Present() {}
|
||||
|
||||
/// Polls window events
|
||||
void PollEvents() override;
|
||||
|
||||
/// Makes the graphics context current for the caller thread
|
||||
void MakeCurrent() override;
|
||||
|
||||
/// Releases the GL context from the caller thread
|
||||
void DoneCurrent() override;
|
||||
|
||||
/// Whether the window is still open, and a close request hasn't yet been sent
|
||||
bool IsOpen() const;
|
||||
|
||||
/// Close the window.
|
||||
void RequestClose();
|
||||
|
||||
/// Creates a new context that is shared with the current context
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
/// Saves the current context, for the purpose of e.g. creating new shared contexts
|
||||
void SaveContext() override;
|
||||
/// Restores the context previously saved
|
||||
void RestoreContext() override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
/// Called by PollEvents when a key is pressed or released.
|
||||
void OnKeyEvent(int key, u8 state);
|
||||
|
||||
@ -105,17 +76,6 @@ private:
|
||||
/// Fake hidden window for the core context
|
||||
SDL_Window* dummy_window;
|
||||
|
||||
using SDL_GLContext = void*;
|
||||
|
||||
/// The OpenGL context associated with the window
|
||||
SDL_GLContext window_context;
|
||||
|
||||
/// Used by SaveContext and RestoreContext
|
||||
SDL_GLContext last_saved_context;
|
||||
|
||||
/// The OpenGL context associated with the core
|
||||
std::unique_ptr<Frontend::GraphicsContext> core_context;
|
||||
|
||||
/// Keeps track of how often to update the title bar during gameplay
|
||||
u32 last_time = 0;
|
||||
};
|
||||
|
152
src/citra/emu_window/emu_window_sdl2_gl.cpp
Normal file
152
src/citra/emu_window/emu_window_sdl2_gl.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <glad/glad.h>
|
||||
#include "citra/emu_window/emu_window_sdl2_gl.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
class SDLGLContext : public Frontend::GraphicsContext {
|
||||
public:
|
||||
using SDL_GLContext = void*;
|
||||
|
||||
SDLGLContext() {
|
||||
window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
|
||||
context = SDL_GL_CreateContext(window);
|
||||
}
|
||||
|
||||
~SDLGLContext() override {
|
||||
SDL_GL_DeleteContext(context);
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
void MakeCurrent() override {
|
||||
SDL_GL_MakeCurrent(window, context);
|
||||
}
|
||||
|
||||
void DoneCurrent() override {
|
||||
SDL_GL_MakeCurrent(window, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
SDL_Window* window;
|
||||
SDL_GLContext context;
|
||||
};
|
||||
|
||||
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen, bool is_secondary)
|
||||
: EmuWindow_SDL2{is_secondary} {
|
||||
// Initialize the window
|
||||
if (Settings::values.use_gles) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
} else {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
|
||||
// Enable context sharing for the shared context
|
||||
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
|
||||
// Enable vsync
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
// Enable debug context
|
||||
if (Settings::values.renderer_debug) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
|
||||
}
|
||||
|
||||
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
}
|
||||
|
||||
window_context = SDL_GL_CreateContext(render_window);
|
||||
core_context = CreateSharedContext();
|
||||
last_saved_context = nullptr;
|
||||
|
||||
if (window_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
if (core_context == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
render_window_id = SDL_GetWindowID(render_window);
|
||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
|
||||
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
|
||||
core_context.reset();
|
||||
SDL_DestroyWindow(render_window);
|
||||
SDL_GL_DeleteContext(window_context);
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
|
||||
return std::make_unique<SDLGLContext>();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::MakeCurrent() {
|
||||
core_context->MakeCurrent();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::DoneCurrent() {
|
||||
core_context->DoneCurrent();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::SaveContext() {
|
||||
last_saved_context = SDL_GL_GetCurrentContext();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::RestoreContext() {
|
||||
SDL_GL_MakeCurrent(render_window, last_saved_context);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_GL::Present() {
|
||||
SDL_GL_MakeCurrent(render_window, window_context);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
while (IsOpen()) {
|
||||
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||
SDL_GL_SwapWindow(render_window);
|
||||
}
|
||||
SDL_GL_MakeCurrent(render_window, nullptr);
|
||||
}
|
35
src/citra/emu_window/emu_window_sdl2_gl.h
Normal file
35
src/citra/emu_window/emu_window_sdl2_gl.h
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "citra/emu_window/emu_window_sdl2.h"
|
||||
|
||||
struct SDL_Window;
|
||||
|
||||
class EmuWindow_SDL2_GL : public EmuWindow_SDL2 {
|
||||
public:
|
||||
explicit EmuWindow_SDL2_GL(bool fullscreen, bool is_secondary);
|
||||
~EmuWindow_SDL2_GL();
|
||||
|
||||
void Present() override;
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
void MakeCurrent() override;
|
||||
void DoneCurrent() override;
|
||||
void SaveContext() override;
|
||||
void RestoreContext() override;
|
||||
|
||||
private:
|
||||
using SDL_GLContext = void*;
|
||||
|
||||
/// The OpenGL context associated with the window
|
||||
SDL_GLContext window_context;
|
||||
|
||||
/// Used by SaveContext and RestoreContext
|
||||
SDL_GLContext last_saved_context;
|
||||
|
||||
/// The OpenGL context associated with the core
|
||||
std::unique_ptr<Frontend::GraphicsContext> core_context;
|
||||
};
|
127
src/citra/emu_window/emu_window_sdl2_sw.cpp
Normal file
127
src/citra/emu_window/emu_window_sdl2_sw.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
#include <SDL_rect.h>
|
||||
#include "citra/emu_window/emu_window_sdl2_sw.h"
|
||||
#include "common/color.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
class DummyContext : public Frontend::GraphicsContext {};
|
||||
|
||||
EmuWindow_SDL2_SW::EmuWindow_SDL2_SW(bool fullscreen, bool is_secondary)
|
||||
: EmuWindow_SDL2{is_secondary} {
|
||||
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
|
||||
SDL_WINDOW_SHOWN);
|
||||
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
window_surface = SDL_GetWindowSurface(render_window);
|
||||
renderer = SDL_CreateSoftwareRenderer(window_surface);
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
}
|
||||
|
||||
render_window_id = SDL_GetWindowID(render_window);
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_SW::~EmuWindow_SDL2_SW() {
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(render_window);
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> EmuWindow_SDL2_SW::CreateSharedContext() const {
|
||||
return std::make_unique<DummyContext>();
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2_SW::Present() {
|
||||
const auto layout{Layout::DefaultFrameLayout(
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, false, false)};
|
||||
|
||||
while (IsOpen()) {
|
||||
SDL_SetRenderDrawColor(renderer, Settings::values.bg_red.GetValue() * 255,
|
||||
Settings::values.bg_green.GetValue() * 255,
|
||||
Settings::values.bg_blue.GetValue() * 255, 0xFF);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
const auto draw_screen = [&](int fb_id) {
|
||||
const auto dst_rect = fb_id == 0 ? layout.top_screen : layout.bottom_screen;
|
||||
SDL_Rect sdl_rect{static_cast<int>(dst_rect.left), static_cast<int>(dst_rect.top),
|
||||
static_cast<int>(dst_rect.GetWidth()),
|
||||
static_cast<int>(dst_rect.GetHeight())};
|
||||
SDL_Surface* screen = LoadFramebuffer(fb_id);
|
||||
SDL_BlitSurface(screen, nullptr, window_surface, &sdl_rect);
|
||||
SDL_FreeSurface(screen);
|
||||
};
|
||||
|
||||
draw_screen(0);
|
||||
draw_screen(1);
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_UpdateWindowSurface(render_window);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Surface* EmuWindow_SDL2_SW::LoadFramebuffer(int fb_id) {
|
||||
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
|
||||
const PAddr framebuffer_addr =
|
||||
framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2;
|
||||
|
||||
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
||||
const u8* framebuffer_data = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr);
|
||||
|
||||
const int width = framebuffer.height;
|
||||
const int height = framebuffer.width;
|
||||
const int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);
|
||||
|
||||
SDL_Surface* surface =
|
||||
SDL_CreateRGBSurfaceWithFormat(0, width, height, 0, SDL_PIXELFORMAT_ABGR8888);
|
||||
SDL_LockSurface(surface);
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
const u8* pixel = framebuffer_data + (x * height + height - y) * bpp;
|
||||
const Common::Vec4 color = [&] {
|
||||
switch (framebuffer.color_format) {
|
||||
case GPU::Regs::PixelFormat::RGBA8:
|
||||
return Common::Color::DecodeRGBA8(pixel);
|
||||
case GPU::Regs::PixelFormat::RGB8:
|
||||
return Common::Color::DecodeRGB8(pixel);
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return Common::Color::DecodeRGB565(pixel);
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return Common::Color::DecodeRGB5A1(pixel);
|
||||
case GPU::Regs::PixelFormat::RGBA4:
|
||||
return Common::Color::DecodeRGBA4(pixel);
|
||||
}
|
||||
}();
|
||||
|
||||
u8* dst_pixel = reinterpret_cast<u8*>(surface->pixels) + (y * width + x) * 4;
|
||||
std::memcpy(dst_pixel, color.AsArray(), sizeof(color));
|
||||
}
|
||||
}
|
||||
SDL_UnlockSurface(surface);
|
||||
return surface;
|
||||
}
|
32
src/citra/emu_window/emu_window_sdl2_sw.h
Normal file
32
src/citra/emu_window/emu_window_sdl2_sw.h
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "citra/emu_window/emu_window_sdl2.h"
|
||||
|
||||
struct SDL_Renderer;
|
||||
struct SDL_Surface;
|
||||
|
||||
class EmuWindow_SDL2_SW : public EmuWindow_SDL2 {
|
||||
public:
|
||||
explicit EmuWindow_SDL2_SW(bool fullscreen, bool is_secondary);
|
||||
~EmuWindow_SDL2_SW();
|
||||
|
||||
void Present() override;
|
||||
std::unique_ptr<GraphicsContext> CreateSharedContext() const override;
|
||||
void MakeCurrent() override {}
|
||||
void DoneCurrent() override {}
|
||||
|
||||
private:
|
||||
/// Loads a framebuffer to an SDL surface
|
||||
SDL_Surface* LoadFramebuffer(int fb_id);
|
||||
|
||||
/// The SDL software renderer
|
||||
SDL_Renderer* renderer;
|
||||
|
||||
/// The window surface
|
||||
SDL_Surface* window_surface;
|
||||
};
|
@ -269,6 +269,10 @@ target_link_libraries(citra-qt PRIVATE audio_core common core input_common netwo
|
||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia Qt5::Concurrent)
|
||||
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
|
||||
if (NOT WIN32)
|
||||
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(citra-qt PRIVATE Qt5::DBus)
|
||||
endif()
|
||||
@ -325,6 +329,10 @@ if (MSVC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT APPLE)
|
||||
target_compile_definitions(citra-qt PRIVATE HAS_OPENGL)
|
||||
endif()
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(citra-qt PRIVATE precompiled_headers.h)
|
||||
endif()
|
||||
|
@ -2,31 +2,45 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLFunctions_4_3_Core>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <fmt/format.h>
|
||||
#include "citra_qt/bootmanager.h"
|
||||
#include "citra_qt/main.h"
|
||||
#include "common/color.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/3ds.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <objc/message.h>
|
||||
#include <objc/objc.h>
|
||||
#endif
|
||||
|
||||
#if !defined(WIN32)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
EmuThread::EmuThread(Frontend::GraphicsContext& core_context) : core_context(core_context) {}
|
||||
|
||||
EmuThread::~EmuThread() = default;
|
||||
@ -44,7 +58,7 @@ static GMainWindow* GetMainWindow() {
|
||||
|
||||
void EmuThread::run() {
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
Frontend::ScopeAcquireContext scope(core_context);
|
||||
const auto scope = core_context.Acquire();
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
@ -55,6 +69,7 @@ void EmuThread::run() {
|
||||
});
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
emit HideLoadingScreen();
|
||||
|
||||
core_context.MakeCurrent();
|
||||
|
||||
@ -113,89 +128,262 @@ void EmuThread::run() {
|
||||
#endif
|
||||
}
|
||||
|
||||
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
|
||||
bool is_secondary)
|
||||
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
||||
event_handler(event_handler), is_secondary{is_secondary} {
|
||||
#ifdef HAS_OPENGL
|
||||
class OpenGLSharedContext : public Frontend::GraphicsContext {
|
||||
public:
|
||||
/// Create the original context that should be shared from
|
||||
explicit OpenGLSharedContext() {
|
||||
QSurfaceFormat format;
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = shared_context->format();
|
||||
format.setSwapInterval(Settings::values.use_vsync_new ? 1 : 0);
|
||||
this->setFormat(format);
|
||||
format.setVersion(4, 3);
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
|
||||
context->setShareContext(shared_context);
|
||||
context->setScreen(this->screen());
|
||||
context->setFormat(format);
|
||||
context->create();
|
||||
|
||||
setSurfaceType(QWindow::OpenGLSurface);
|
||||
|
||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||
if (Settings::values.renderer_debug) {
|
||||
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
|
||||
}
|
||||
|
||||
OpenGLWindow::~OpenGLWindow() {
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
format.setSwapInterval(0);
|
||||
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create main openGL context");
|
||||
}
|
||||
|
||||
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
|
||||
offscreen_surface->setFormat(format);
|
||||
offscreen_surface->create();
|
||||
surface = offscreen_surface.get();
|
||||
}
|
||||
|
||||
/// Create the shared contexts for rendering and presentation
|
||||
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface) {
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = share_context->format();
|
||||
format.setSwapInterval(main_surface ? Settings::values.use_vsync_new.GetValue() : 0);
|
||||
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setShareContext(share_context);
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create shared openGL context");
|
||||
}
|
||||
|
||||
surface = main_surface;
|
||||
}
|
||||
|
||||
~OpenGLSharedContext() {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
void OpenGLWindow::Present() {
|
||||
if (!isExposed())
|
||||
return;
|
||||
|
||||
context->makeCurrent(this);
|
||||
if (VideoCore::g_renderer) {
|
||||
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||
}
|
||||
context->swapBuffers(this);
|
||||
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
|
||||
f->glFinish();
|
||||
QWindow::requestUpdate();
|
||||
void SwapBuffers() override {
|
||||
context->swapBuffers(surface);
|
||||
}
|
||||
|
||||
bool OpenGLWindow::event(QEvent* event) {
|
||||
switch (event->type()) {
|
||||
case QEvent::UpdateRequest:
|
||||
void MakeCurrent() override {
|
||||
// We can't track the current state of the underlying context in this wrapper class because
|
||||
// Qt may make the underlying context not current for one reason or another. In particular,
|
||||
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
|
||||
// Instead of always just making the context current (which does not have any caching to
|
||||
// check if the underlying context is already current) we can check for the current context
|
||||
// in the thread local data by calling `currentContext()` and checking if its ours.
|
||||
if (QOpenGLContext::currentContext() != context.get()) {
|
||||
context->makeCurrent(surface);
|
||||
}
|
||||
}
|
||||
|
||||
void DoneCurrent() override {
|
||||
context->doneCurrent();
|
||||
}
|
||||
|
||||
QOpenGLContext* GetShareContext() const {
|
||||
return context.get();
|
||||
}
|
||||
|
||||
private:
|
||||
// Avoid using Qt parent system here since we might move the QObjects to new threads
|
||||
// As a note, this means we should avoid using slots/signals with the objects too
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
|
||||
QSurface* surface;
|
||||
};
|
||||
#endif
|
||||
|
||||
class DummyContext : public Frontend::GraphicsContext {};
|
||||
|
||||
class RenderWidget : public QWidget {
|
||||
public:
|
||||
RenderWidget(GRenderWindow* parent) : QWidget(parent) {
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
virtual ~RenderWidget() = default;
|
||||
|
||||
virtual void Present() {}
|
||||
|
||||
void paintEvent(QPaintEvent* event) override {
|
||||
Present();
|
||||
return true;
|
||||
case QEvent::MouseButtonPress:
|
||||
case QEvent::MouseButtonRelease:
|
||||
case QEvent::MouseButtonDblClick:
|
||||
case QEvent::MouseMove:
|
||||
case QEvent::KeyPress:
|
||||
case QEvent::KeyRelease:
|
||||
case QEvent::FocusIn:
|
||||
case QEvent::FocusOut:
|
||||
case QEvent::FocusAboutToChange:
|
||||
case QEvent::Enter:
|
||||
case QEvent::Leave:
|
||||
case QEvent::Wheel:
|
||||
case QEvent::TabletMove:
|
||||
case QEvent::TabletPress:
|
||||
case QEvent::TabletRelease:
|
||||
case QEvent::TabletEnterProximity:
|
||||
case QEvent::TabletLeaveProximity:
|
||||
case QEvent::TouchBegin:
|
||||
case QEvent::TouchUpdate:
|
||||
case QEvent::TouchEnd:
|
||||
case QEvent::InputMethodQuery:
|
||||
case QEvent::TouchCancel:
|
||||
return QCoreApplication::sendEvent(event_handler, event);
|
||||
case QEvent::Drop:
|
||||
GetMainWindow()->DropAction(static_cast<QDropEvent*>(event));
|
||||
return true;
|
||||
case QEvent::DragEnter:
|
||||
case QEvent::DragMove:
|
||||
GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event));
|
||||
return true;
|
||||
default:
|
||||
return QWindow::event(event);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void OpenGLWindow::exposeEvent(QExposeEvent* event) {
|
||||
QWindow::requestUpdate();
|
||||
QWindow::exposeEvent(event);
|
||||
std::pair<unsigned, unsigned> GetSize() const {
|
||||
return std::make_pair(width(), height());
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
class OpenGLRenderWidget : public RenderWidget {
|
||||
public:
|
||||
explicit OpenGLRenderWidget(GRenderWindow* parent, bool is_secondary)
|
||||
: RenderWidget(parent), is_secondary(is_secondary) {
|
||||
setAttribute(Qt::WA_NativeWindow);
|
||||
setAttribute(Qt::WA_PaintOnScreen);
|
||||
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
|
||||
}
|
||||
|
||||
void SetContext(std::unique_ptr<Frontend::GraphicsContext>&& context_) {
|
||||
context = std::move(context_);
|
||||
}
|
||||
|
||||
void Present() override {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (!Core::System::GetInstance().IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
context->MakeCurrent();
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
VideoCore::g_renderer->TryPresent(100, is_secondary);
|
||||
context->SwapBuffers();
|
||||
glFinish();
|
||||
}
|
||||
|
||||
QPaintEngine* paintEngine() const override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Frontend::GraphicsContext> context{};
|
||||
bool is_secondary;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct SoftwareRenderWidget : public RenderWidget {
|
||||
explicit SoftwareRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {}
|
||||
|
||||
void Present() override {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (!Core::System::GetInstance().IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto layout{Layout::DefaultFrameLayout(width(), height(), false, false)};
|
||||
QPainter painter(this);
|
||||
|
||||
const auto draw_screen = [&](int fb_id) {
|
||||
const auto rect = fb_id == 0 ? layout.top_screen : layout.bottom_screen;
|
||||
const QImage screen = LoadFramebuffer(fb_id);
|
||||
painter.drawImage(rect.left, rect.top, screen);
|
||||
};
|
||||
|
||||
painter.fillRect(rect(), qRgb(Settings::values.bg_red.GetValue() * 255,
|
||||
Settings::values.bg_green.GetValue() * 255,
|
||||
Settings::values.bg_blue.GetValue() * 255));
|
||||
draw_screen(0);
|
||||
draw_screen(1);
|
||||
|
||||
painter.end();
|
||||
}
|
||||
|
||||
QImage LoadFramebuffer(int fb_id) {
|
||||
const auto& framebuffer = GPU::g_regs.framebuffer_config[fb_id];
|
||||
const PAddr framebuffer_addr =
|
||||
framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2;
|
||||
|
||||
Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height);
|
||||
const u8* framebuffer_data = VideoCore::g_memory->GetPhysicalPointer(framebuffer_addr);
|
||||
|
||||
const int width = framebuffer.height;
|
||||
const int height = framebuffer.width;
|
||||
const int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);
|
||||
|
||||
QImage image{width, height, QImage::Format_RGBA8888};
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
const u8* pixel = framebuffer_data + (x * height + height - y) * bpp;
|
||||
const Common::Vec4 color = [&] {
|
||||
switch (framebuffer.color_format) {
|
||||
case GPU::Regs::PixelFormat::RGBA8:
|
||||
return Common::Color::DecodeRGBA8(pixel);
|
||||
case GPU::Regs::PixelFormat::RGB8:
|
||||
return Common::Color::DecodeRGB8(pixel);
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return Common::Color::DecodeRGB565(pixel);
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return Common::Color::DecodeRGB5A1(pixel);
|
||||
case GPU::Regs::PixelFormat::RGBA4:
|
||||
return Common::Color::DecodeRGBA4(pixel);
|
||||
}
|
||||
}();
|
||||
|
||||
image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
};
|
||||
|
||||
static Frontend::WindowSystemType GetWindowSystemType() {
|
||||
// Determine WSI type based on Qt platform.
|
||||
const QString platform_name = QGuiApplication::platformName();
|
||||
if (platform_name == QStringLiteral("windows"))
|
||||
return Frontend::WindowSystemType::Windows;
|
||||
else if (platform_name == QStringLiteral("xcb"))
|
||||
return Frontend::WindowSystemType::X11;
|
||||
else if (platform_name == QStringLiteral("wayland"))
|
||||
return Frontend::WindowSystemType::Wayland;
|
||||
else if (platform_name == QStringLiteral("cocoa"))
|
||||
return Frontend::WindowSystemType::MacOS;
|
||||
|
||||
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
|
||||
return Frontend::WindowSystemType::Windows;
|
||||
}
|
||||
|
||||
static Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
|
||||
Frontend::EmuWindow::WindowSystemInfo wsi;
|
||||
wsi.type = GetWindowSystemType();
|
||||
|
||||
if (window) {
|
||||
#if defined(WIN32)
|
||||
// Our Win32 Qt external doesn't have the private API.
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#elif defined(__APPLE__)
|
||||
wsi.render_surface = reinterpret_cast<void* (*)(id, SEL)>(objc_msgSend)(
|
||||
reinterpret_cast<id>(window->winId()), sel_registerName("layer"));
|
||||
#else
|
||||
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
|
||||
wsi.display_connection = pni->nativeResourceForWindow("display", window);
|
||||
if (wsi.type == Frontend::WindowSystemType::Wayland)
|
||||
wsi.render_surface = pni->nativeResourceForWindow("surface", window);
|
||||
else
|
||||
wsi.render_surface = reinterpret_cast<void*>(window->winId());
|
||||
#endif
|
||||
wsi.render_surface_scale = static_cast<float>(window->devicePixelRatio());
|
||||
} else {
|
||||
wsi.render_surface = nullptr;
|
||||
wsi.render_surface_scale = 1.0f;
|
||||
}
|
||||
|
||||
return wsi;
|
||||
}
|
||||
|
||||
std::shared_ptr<Frontend::GraphicsContext> GRenderWindow::main_context;
|
||||
|
||||
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
|
||||
: QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread) {
|
||||
@ -218,11 +406,11 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_se
|
||||
GRenderWindow::~GRenderWindow() = default;
|
||||
|
||||
void GRenderWindow::MakeCurrent() {
|
||||
core_context->MakeCurrent();
|
||||
main_context->MakeCurrent();
|
||||
}
|
||||
|
||||
void GRenderWindow::DoneCurrent() {
|
||||
core_context->DoneCurrent();
|
||||
main_context->DoneCurrent();
|
||||
}
|
||||
|
||||
void GRenderWindow::PollEvents() {
|
||||
@ -295,8 +483,9 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
|
||||
}
|
||||
|
||||
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
|
||||
return; // touch input is handled in TouchBeginEvent
|
||||
}
|
||||
|
||||
auto pos = event->pos();
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
@ -309,8 +498,9 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
||||
}
|
||||
|
||||
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
|
||||
return; // touch input is handled in TouchUpdateEvent
|
||||
}
|
||||
|
||||
auto pos = event->pos();
|
||||
const auto [x, y] = ScaleTouch(pos);
|
||||
@ -320,8 +510,9 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||
}
|
||||
|
||||
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem)
|
||||
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
|
||||
return; // touch input is handled in TouchEndEvent
|
||||
}
|
||||
|
||||
if (event->button() == Qt::LeftButton)
|
||||
this->TouchReleased();
|
||||
@ -393,42 +584,61 @@ void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
||||
OnFramebufferSizeChanged();
|
||||
}
|
||||
|
||||
void GRenderWindow::InitRenderTarget() {
|
||||
ReleaseRenderTarget();
|
||||
bool GRenderWindow::InitRenderTarget() {
|
||||
{
|
||||
// Create a dummy render widget so that Qt
|
||||
// places the render window at the correct position.
|
||||
const RenderWidget dummy_widget{this};
|
||||
}
|
||||
|
||||
first_frame = false;
|
||||
|
||||
GMainWindow* parent = GetMainWindow();
|
||||
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
|
||||
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext(),
|
||||
is_secondary);
|
||||
child_window->create();
|
||||
child_widget = createWindowContainer(child_window, this);
|
||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||
switch (graphics_api) {
|
||||
case Settings::GraphicsAPI::Software:
|
||||
InitializeSoftware();
|
||||
break;
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
if (!InitializeOpenGL() || !LoadOpenGL()) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the Window System information with the new render target
|
||||
window_info = GetWindowSystemInfo(child_widget->windowHandle());
|
||||
|
||||
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||
|
||||
layout()->addWidget(child_widget);
|
||||
// Reset minimum required size to avoid resizing issues on the main window after restarting.
|
||||
setMinimumSize(1, 1);
|
||||
|
||||
core_context = CreateSharedContext();
|
||||
resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
OnFramebufferSizeChanged();
|
||||
BackupGeometry();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GRenderWindow::ReleaseRenderTarget() {
|
||||
if (child_widget) {
|
||||
layout()->removeWidget(child_widget);
|
||||
delete child_widget;
|
||||
child_widget->deleteLater();
|
||||
child_widget = nullptr;
|
||||
}
|
||||
main_context.reset();
|
||||
}
|
||||
|
||||
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
|
||||
if (res_scale == 0)
|
||||
if (res_scale == 0) {
|
||||
res_scale = VideoCore::GetResolutionScaleFactor();
|
||||
}
|
||||
|
||||
const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)};
|
||||
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
|
||||
VideoCore::RequestScreenshot(
|
||||
VideoCore::g_renderer->RequestScreenshot(
|
||||
screenshot_image.bits(),
|
||||
[this, screenshot_path] {
|
||||
const std::string std_screenshot_path = screenshot_path.toStdString();
|
||||
@ -445,6 +655,59 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
|
||||
setMinimumSize(minimal_size.first, minimal_size.second);
|
||||
}
|
||||
|
||||
bool GRenderWindow::InitializeOpenGL() {
|
||||
#ifdef HAS_OPENGL
|
||||
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
|
||||
// WA_DontShowOnScreen, WA_DeleteOnClose
|
||||
auto child = new OpenGLRenderWidget(this, is_secondary);
|
||||
child_widget = child;
|
||||
child_widget->windowHandle()->create();
|
||||
|
||||
if (!main_context) {
|
||||
main_context = std::make_shared<OpenGLSharedContext>();
|
||||
}
|
||||
|
||||
auto child_context = CreateSharedContext();
|
||||
child->SetContext(std::move(child_context));
|
||||
|
||||
return true;
|
||||
#else
|
||||
QMessageBox::warning(this, tr("OpenGL not available!"),
|
||||
tr("Citra has not been compiled with OpenGL support."));
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GRenderWindow::InitializeSoftware() {
|
||||
child_widget = new SoftwareRenderWidget(this);
|
||||
main_context = std::make_unique<DummyContext>();
|
||||
}
|
||||
|
||||
bool GRenderWindow::LoadOpenGL() {
|
||||
auto context = CreateSharedContext();
|
||||
auto scope = context->Acquire();
|
||||
if (!gladLoadGL()) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error while initializing OpenGL!"),
|
||||
tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString renderer =
|
||||
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||
|
||||
if (!GLAD_GL_VERSION_4_3) {
|
||||
LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString());
|
||||
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"),
|
||||
tr("Your GPU may not support OpenGL 4.3, or you do not have the "
|
||||
"latest graphics driver.<br><br>GL Renderer:<br>%1")
|
||||
.arg(renderer));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
|
||||
this->emu_thread = emu_thread;
|
||||
}
|
||||
@ -458,29 +721,15 @@ void GRenderWindow::showEvent(QShowEvent* event) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
|
||||
return std::make_unique<GLContext>(QOpenGLContext::globalShareContext());
|
||||
#ifdef HAS_OPENGL
|
||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||
if (graphics_api == Settings::GraphicsAPI::OpenGL) {
|
||||
auto gl_context = static_cast<OpenGLSharedContext*>(main_context.get());
|
||||
// Bind the shared contexts to the main surface in case the backend wants to take over
|
||||
// presentation
|
||||
return std::make_unique<OpenGLSharedContext>(gl_context->GetShareContext(),
|
||||
child_widget->windowHandle());
|
||||
}
|
||||
|
||||
GLContext::GLContext(QOpenGLContext* shared_context)
|
||||
: context(std::make_unique<QOpenGLContext>(shared_context->parent())),
|
||||
surface(std::make_unique<QOffscreenSurface>(nullptr)) {
|
||||
|
||||
// disable vsync for any shared contexts
|
||||
auto format = shared_context->format();
|
||||
format.setSwapInterval(0);
|
||||
|
||||
context->setShareContext(shared_context);
|
||||
context->setFormat(format);
|
||||
context->create();
|
||||
surface->setParent(shared_context->parent());
|
||||
surface->setFormat(format);
|
||||
surface->create();
|
||||
}
|
||||
|
||||
void GLContext::MakeCurrent() {
|
||||
context->makeCurrent(surface.get());
|
||||
}
|
||||
|
||||
void GLContext::DoneCurrent() {
|
||||
context->doneCurrent();
|
||||
#endif
|
||||
return std::make_unique<DummyContext>();
|
||||
}
|
||||
|
@ -27,19 +27,6 @@ namespace VideoCore {
|
||||
enum class LoadCallbackStage;
|
||||
}
|
||||
|
||||
class GLContext : public Frontend::GraphicsContext {
|
||||
public:
|
||||
explicit GLContext(QOpenGLContext* shared_context);
|
||||
|
||||
void MakeCurrent() override;
|
||||
|
||||
void DoneCurrent() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
std::unique_ptr<QOffscreenSurface> surface;
|
||||
};
|
||||
|
||||
class EmuThread final : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
@ -126,26 +113,6 @@ signals:
|
||||
void HideLoadingScreen();
|
||||
};
|
||||
|
||||
class OpenGLWindow : public QWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
|
||||
bool is_secondary = false);
|
||||
|
||||
~OpenGLWindow();
|
||||
|
||||
void Present();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
void exposeEvent(QExposeEvent* event) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<QOpenGLContext> context;
|
||||
QWidget* event_handler;
|
||||
bool is_secondary;
|
||||
};
|
||||
|
||||
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
|
||||
Q_OBJECT
|
||||
|
||||
@ -185,13 +152,15 @@ public:
|
||||
return has_focus;
|
||||
}
|
||||
|
||||
void InitRenderTarget();
|
||||
bool InitRenderTarget();
|
||||
|
||||
/// Destroy the previous run's child_widget which should also destroy the child_window
|
||||
void ReleaseRenderTarget();
|
||||
|
||||
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
|
||||
|
||||
std::pair<u32, u32> ScaleTouch(const QPointF pos) const;
|
||||
|
||||
public slots:
|
||||
|
||||
void OnEmulationStarting(EmuThread* emu_thread);
|
||||
@ -211,29 +180,28 @@ signals:
|
||||
void MouseActivity();
|
||||
|
||||
private:
|
||||
std::pair<u32, u32> ScaleTouch(QPointF pos) const;
|
||||
void TouchBeginEvent(const QTouchEvent* event);
|
||||
void TouchUpdateEvent(const QTouchEvent* event);
|
||||
void TouchEndEvent();
|
||||
|
||||
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
|
||||
|
||||
std::unique_ptr<GraphicsContext> core_context;
|
||||
bool InitializeOpenGL();
|
||||
void InitializeSoftware();
|
||||
bool LoadOpenGL();
|
||||
|
||||
QByteArray geometry;
|
||||
|
||||
/// Native window handle that backs this presentation widget
|
||||
QWindow* child_window = nullptr;
|
||||
|
||||
/// In order to embed the window into GRenderWindow, you need to use createWindowContainer to
|
||||
/// put the child_window into a widget then add it to the layout. This child_widget can be
|
||||
/// parented to GRenderWindow and use Qt's lifetime system
|
||||
QWidget* child_widget = nullptr;
|
||||
|
||||
EmuThread* emu_thread;
|
||||
|
||||
/// Main context that will be shared with all other contexts that are requested.
|
||||
/// If this is used in a shared context setting, then this should not be used directly, but
|
||||
/// should instead be shared from
|
||||
static std::shared_ptr<Frontend::GraphicsContext> main_context;
|
||||
|
||||
/// Temporary storage of the screenshot taken
|
||||
QImage screenshot_image;
|
||||
QByteArray geometry;
|
||||
bool first_frame = false;
|
||||
bool has_focus = false;
|
||||
|
||||
|
@ -482,6 +482,7 @@ void Config::ReadDebuggingValues() {
|
||||
qt_config->value(QStringLiteral("record_frame_times"), false).toBool();
|
||||
ReadBasicSetting(Settings::values.use_gdbstub);
|
||||
ReadBasicSetting(Settings::values.gdbstub_port);
|
||||
ReadBasicSetting(Settings::values.renderer_debug);
|
||||
|
||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||
for (const auto& service_module : Service::service_module_map) {
|
||||
@ -625,7 +626,7 @@ void Config::ReadPathValues() {
|
||||
void Config::ReadRendererValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||
|
||||
ReadGlobalSetting(Settings::values.use_hw_renderer);
|
||||
ReadGlobalSetting(Settings::values.graphics_api);
|
||||
ReadGlobalSetting(Settings::values.use_hw_shader);
|
||||
#ifdef __APPLE__
|
||||
// Hardware shader is broken on macos with Intel GPUs thanks to poor drivers.
|
||||
@ -992,6 +993,7 @@ void Config::SaveDebuggingValues() {
|
||||
qt_config->setValue(QStringLiteral("record_frame_times"), Settings::values.record_frame_times);
|
||||
WriteBasicSetting(Settings::values.use_gdbstub);
|
||||
WriteBasicSetting(Settings::values.gdbstub_port);
|
||||
WriteBasicSetting(Settings::values.renderer_debug);
|
||||
|
||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||
for (const auto& service_module : Settings::values.lle_modules) {
|
||||
@ -1103,7 +1105,7 @@ void Config::SavePathValues() {
|
||||
void Config::SaveRendererValues() {
|
||||
qt_config->beginGroup(QStringLiteral("Renderer"));
|
||||
|
||||
WriteGlobalSetting(Settings::values.use_hw_renderer);
|
||||
WriteGlobalSetting(Settings::values.graphics_api);
|
||||
WriteGlobalSetting(Settings::values.use_hw_shader);
|
||||
#ifdef __APPLE__
|
||||
// Hardware shader is broken on macos thanks to poor drivers.
|
||||
|
@ -83,6 +83,21 @@ template <>
|
||||
void SetPerGameSetting(QComboBox* combobox,
|
||||
const Settings::SwitchableSetting<std::string>* setting);
|
||||
|
||||
/// Given an index of a combobox setting extracts the setting taking into
|
||||
/// account per-game status
|
||||
template <typename Type, bool ranged>
|
||||
Type GetComboboxSetting(int index, const Settings::SwitchableSetting<Type, ranged>* setting) {
|
||||
if (Settings::IsConfiguringGlobal() && setting->UsingGlobal()) {
|
||||
return static_cast<Type>(index);
|
||||
} else if (!Settings::IsConfiguringGlobal()) {
|
||||
if (index == 0) {
|
||||
return setting->GetValue();
|
||||
} else {
|
||||
return static_cast<Type>(index - ConfigurationShared::USE_GLOBAL_OFFSET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a Qt widget sets the background color to indicate whether the setting
|
||||
/// is per-game overriden (highlighted) or global (non-highlighted)
|
||||
void SetHighlight(QWidget* widget, bool highlighted);
|
||||
|
@ -37,6 +37,7 @@ ConfigureDebug::ConfigureDebug(QWidget* parent)
|
||||
|
||||
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
|
||||
ui->toggle_cpu_jit->setEnabled(!is_powered_on);
|
||||
ui->toggle_renderer_debug->setEnabled(!is_powered_on);
|
||||
|
||||
// Set a minimum width for the label to prevent the slider from changing size.
|
||||
// This scales across DPIs. (This value should be enough for "xxx%")
|
||||
@ -62,6 +63,7 @@ void ConfigureDebug::SetConfiguration() {
|
||||
ui->toggle_console->setChecked(UISettings::values.show_console.GetValue());
|
||||
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter.GetValue()));
|
||||
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit.GetValue());
|
||||
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
|
||||
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
if (Settings::values.cpu_clock_percentage.UsingGlobal()) {
|
||||
@ -91,6 +93,7 @@ void ConfigureDebug::ApplyConfiguration() {
|
||||
filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
Log::SetGlobalFilter(filter);
|
||||
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
||||
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
|
||||
|
||||
ConfigurationShared::ApplyPerGameSetting(
|
||||
&Settings::values.cpu_clock_percentage, ui->clock_speed_combo,
|
||||
|
@ -23,5 +23,6 @@ public:
|
||||
void SetConfiguration();
|
||||
void SetupPerGameUI();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::ConfigureDebug> ui;
|
||||
};
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>443</width>
|
||||
<height>358</height>
|
||||
<width>523</width>
|
||||
<height>447</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -112,12 +112,34 @@
|
||||
<string>CPU</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="toggle_cpu_jit">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable CPU JIT</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QWidget" name="clock_speed_widget" native="true">
|
||||
<layout class="QHBoxLayout" name="clock_speed_layout">
|
||||
<property name="spacing">
|
||||
<number>7</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="clock_speed_combo">
|
||||
<item>
|
||||
@ -180,13 +202,10 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="toggle_cpu_jit">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enables the use of the ARM JIT compiler for emulating the 3DS CPUs. Don't disable unless for debugging purposes</p></body></html></string>
|
||||
</property>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="toggle_renderer_debug">
|
||||
<property name="text">
|
||||
<string>Enable CPU JIT</string>
|
||||
<string>Enable debug renderer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -22,7 +22,9 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
|
||||
|
||||
ui->layout_group->setEnabled(!Settings::values.custom_layout);
|
||||
|
||||
ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer.GetValue());
|
||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||
const bool res_scale_enabled = graphics_api != Settings::GraphicsAPI::Software;
|
||||
ui->resolution_factor_combobox->setEnabled(res_scale_enabled);
|
||||
|
||||
connect(ui->render_3d_combobox,
|
||||
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
|
@ -16,22 +16,20 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGraphics>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
SetupPerGameUI();
|
||||
SetConfiguration();
|
||||
|
||||
ui->hw_renderer_group->setEnabled(ui->hw_renderer_group->isEnabled() &&
|
||||
ui->toggle_hw_renderer->isChecked());
|
||||
ui->toggle_vsync_new->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||
// Set the index to -1 to ensure the below lambda is called with setCurrentIndex
|
||||
ui->graphics_api_combo->setCurrentIndex(-1);
|
||||
|
||||
connect(ui->toggle_hw_renderer, &QCheckBox::toggled, this, [this] {
|
||||
const bool checked = ui->toggle_hw_renderer->isChecked();
|
||||
ui->hw_renderer_group->setEnabled(checked);
|
||||
ui->toggle_disk_shader_cache->setEnabled(checked && ui->toggle_hw_shader->isChecked());
|
||||
});
|
||||
connect(ui->graphics_api_combo, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
[this](int index) {
|
||||
const auto graphics_api =
|
||||
ConfigurationShared::GetComboboxSetting(index, &Settings::values.graphics_api);
|
||||
const bool is_software = graphics_api == Settings::GraphicsAPI::Software;
|
||||
|
||||
ui->hw_shader_group->setEnabled(ui->toggle_hw_shader->isChecked());
|
||||
ui->toggle_disk_shader_cache->setEnabled(ui->toggle_hw_renderer->isChecked() &&
|
||||
ui->hw_renderer_group->setEnabled(!is_software);
|
||||
ui->toggle_disk_shader_cache->setEnabled(!is_software &&
|
||||
ui->toggle_hw_shader->isChecked());
|
||||
});
|
||||
|
||||
connect(ui->toggle_hw_shader, &QCheckBox::toggled, this, [this] {
|
||||
const bool checked = ui->toggle_hw_shader->isChecked();
|
||||
@ -60,12 +58,24 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||
// TODO(B3N30): Hide this for macs with none Intel GPUs, too.
|
||||
ui->toggle_separable_shader->setVisible(false);
|
||||
#endif
|
||||
|
||||
SetupPerGameUI();
|
||||
SetConfiguration();
|
||||
}
|
||||
|
||||
ConfigureGraphics::~ConfigureGraphics() = default;
|
||||
|
||||
void ConfigureGraphics::SetConfiguration() {
|
||||
ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer.GetValue());
|
||||
if (!Settings::IsConfiguringGlobal()) {
|
||||
ConfigurationShared::SetHighlight(ui->graphics_api_group,
|
||||
!Settings::values.graphics_api.UsingGlobal());
|
||||
ConfigurationShared::SetPerGameSetting(ui->graphics_api_combo,
|
||||
&Settings::values.graphics_api);
|
||||
} else {
|
||||
ui->graphics_api_combo->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.graphics_api.GetValue()));
|
||||
}
|
||||
|
||||
ui->toggle_hw_shader->setChecked(Settings::values.use_hw_shader.GetValue());
|
||||
ui->toggle_separable_shader->setChecked(Settings::values.separable_shader.GetValue());
|
||||
ui->toggle_accurate_mul->setChecked(Settings::values.shaders_accurate_mul.GetValue());
|
||||
@ -78,8 +88,8 @@ void ConfigureGraphics::SetConfiguration() {
|
||||
}
|
||||
|
||||
void ConfigureGraphics::ApplyConfiguration() {
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_renderer,
|
||||
ui->toggle_hw_renderer, use_hw_renderer);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.graphics_api,
|
||||
ui->graphics_api_combo);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_hw_shader, ui->toggle_hw_shader,
|
||||
use_hw_shader);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.separable_shader,
|
||||
@ -103,7 +113,7 @@ void ConfigureGraphics::RetranslateUI() {
|
||||
void ConfigureGraphics::SetupPerGameUI() {
|
||||
// Block the global settings if a game is currently running that overrides them
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
ui->toggle_hw_renderer->setEnabled(Settings::values.use_hw_renderer.UsingGlobal());
|
||||
ui->graphics_api_group->setEnabled(Settings::values.graphics_api.UsingGlobal());
|
||||
ui->toggle_hw_shader->setEnabled(Settings::values.use_hw_shader.UsingGlobal());
|
||||
ui->toggle_separable_shader->setEnabled(Settings::values.separable_shader.UsingGlobal());
|
||||
ui->toggle_accurate_mul->setEnabled(Settings::values.shaders_accurate_mul.UsingGlobal());
|
||||
@ -115,8 +125,10 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
|
||||
ui->toggle_shader_jit->setVisible(false);
|
||||
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_hw_renderer,
|
||||
Settings::values.use_hw_renderer, use_hw_renderer);
|
||||
ConfigurationShared::SetColoredComboBox(
|
||||
ui->graphics_api_combo, ui->graphics_api_group,
|
||||
static_cast<u32>(Settings::values.graphics_api.GetValue(true)));
|
||||
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_hw_shader, Settings::values.use_hw_shader,
|
||||
use_hw_shader);
|
||||
ConfigurationShared::SetColoredTristate(ui->toggle_separable_shader,
|
||||
|
@ -28,9 +28,9 @@ public:
|
||||
|
||||
void UpdateBackgroundColorButton(const QColor& color);
|
||||
|
||||
private:
|
||||
void SetupPerGameUI();
|
||||
|
||||
ConfigurationShared::CheckState use_hw_renderer;
|
||||
ConfigurationShared::CheckState use_hw_shader;
|
||||
ConfigurationShared::CheckState separable_shader;
|
||||
ConfigurationShared::CheckState shaders_accurate_mul;
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>430</height>
|
||||
<height>443</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
@ -20,27 +20,65 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="apiBox">
|
||||
<property name="title">
|
||||
<string>API Settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QWidget" name="graphics_api_group" native="true">
|
||||
<layout class="QHBoxLayout" name="graphics_api_group_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="graphics_api_label">
|
||||
<property name="text">
|
||||
<string>Graphics API</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="graphics_api_combo">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Software</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>OpenGL</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="rendererBox">
|
||||
<property name="title">
|
||||
<string>Renderer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_hw_renderer">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Use OpenGL to accelerate rendering.</p><p>Disable to debug graphics-related problem.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable Hardware Renderer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="hw_renderer_group" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>16</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
@ -157,8 +195,6 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>toggle_hw_renderer</tabstop>
|
||||
<tabstop>toggle_hw_shader</tabstop>
|
||||
<tabstop>toggle_separable_shader</tabstop>
|
||||
<tabstop>toggle_accurate_mul</tabstop>
|
||||
<tabstop>toggle_shader_jit</tabstop>
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QOpenGLFunctions_4_3_Core>
|
||||
#include <QSysInfo>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtGui>
|
||||
@ -88,7 +87,6 @@
|
||||
#include "core/file_sys/archive_extsavedata.h"
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
@ -1026,16 +1024,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
render_window->InitRenderTarget();
|
||||
secondary_window->InitRenderTarget();
|
||||
|
||||
Frontend::ScopeAcquireContext scope(*render_window);
|
||||
|
||||
const QString below_gl43_title = tr("OpenGL 4.3 Unsupported");
|
||||
const QString below_gl43_message = tr("Your GPU may not support OpenGL 4.3, or you do not "
|
||||
"have the latest graphics driver.");
|
||||
|
||||
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_4_3_Core>()) {
|
||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
||||
return false;
|
||||
}
|
||||
const auto scope = render_window->Acquire();
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
@ -1091,28 +1080,6 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
tr("GBA Virtual Console ROMs are not supported by Citra."));
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorVideoCore:
|
||||
QMessageBox::critical(
|
||||
this, tr("Video Core Error"),
|
||||
tr("An error has occurred. Please <a "
|
||||
"href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>see "
|
||||
"the "
|
||||
"log</a> for more details. "
|
||||
"Ensure that you have the latest graphics drivers for your GPU."));
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorVideoCore_ErrorGenericDrivers:
|
||||
QMessageBox::critical(
|
||||
this, tr("Video Core Error"),
|
||||
tr("You are running default Windows drivers "
|
||||
"for your GPU. You need to install the "
|
||||
"proper drivers for your graphics card from the manufacturer's website."));
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL43:
|
||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
||||
break;
|
||||
|
||||
default:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error while loading ROM!"),
|
||||
@ -2786,14 +2753,6 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
||||
|
||||
QSurfaceFormat format;
|
||||
format.setVersion(4, 3);
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
format.setSwapInterval(0);
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
|
||||
SetHighDPIAttributes();
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
@ -55,6 +55,60 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
#define DECLARE_ENUM_FLAG_OPERATORS(type) \
|
||||
[[nodiscard]] constexpr type operator|(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator&(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator<<(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) << static_cast<T>(b)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator>>(type a, type b) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(static_cast<T>(a) >> static_cast<T>(b)); \
|
||||
} \
|
||||
constexpr type& operator|=(type& a, type b) noexcept { \
|
||||
a = a | b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator&=(type& a, type b) noexcept { \
|
||||
a = a & b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator^=(type& a, type b) noexcept { \
|
||||
a = a ^ b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator<<=(type& a, type b) noexcept { \
|
||||
a = a << b; \
|
||||
return a; \
|
||||
} \
|
||||
constexpr type& operator>>=(type& a, type b) noexcept { \
|
||||
a = a >> b; \
|
||||
return a; \
|
||||
} \
|
||||
[[nodiscard]] constexpr type operator~(type key) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<type>(~static_cast<T>(key)); \
|
||||
} \
|
||||
[[nodiscard]] constexpr bool True(type key) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<T>(key) != 0; \
|
||||
} \
|
||||
[[nodiscard]] constexpr bool False(type key) noexcept { \
|
||||
using T = std::underlying_type_t<type>; \
|
||||
return static_cast<T>(key) == 0; \
|
||||
}
|
||||
|
||||
// Generic function to get last error message.
|
||||
// Call directly after the command or use the error num.
|
||||
// This function might change the error code.
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
namespace Settings {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string_view GetAudioEmulationName(AudioEmulation emulation) {
|
||||
switch (emulation) {
|
||||
case AudioEmulation::HLE:
|
||||
@ -31,6 +33,17 @@ std::string_view GetAudioEmulationName(AudioEmulation emulation) {
|
||||
}
|
||||
};
|
||||
|
||||
std::string_view GetGraphicsAPIName(GraphicsAPI api) {
|
||||
switch (api) {
|
||||
case GraphicsAPI::Software:
|
||||
return "Software";
|
||||
case GraphicsAPI::OpenGL:
|
||||
return "OpenGL";
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
Values values = {};
|
||||
static bool configuring_global = true;
|
||||
|
||||
@ -38,7 +51,6 @@ void Apply() {
|
||||
GDBStub::SetServerPort(values.gdbstub_port.GetValue());
|
||||
GDBStub::ToggleServer(values.use_gdbstub.GetValue());
|
||||
|
||||
VideoCore::g_hw_renderer_enabled = values.use_hw_renderer.GetValue();
|
||||
VideoCore::g_shader_jit_enabled = values.use_shader_jit.GetValue();
|
||||
VideoCore::g_hw_shader_enabled = values.use_hw_shader.GetValue();
|
||||
VideoCore::g_separable_shader_enabled = values.separable_shader.GetValue();
|
||||
@ -101,7 +113,7 @@ void LogSettings() {
|
||||
log_setting("Core_UseCpuJit", values.use_cpu_jit.GetValue());
|
||||
log_setting("Core_CPUClockPercentage", values.cpu_clock_percentage.GetValue());
|
||||
log_setting("Renderer_UseGLES", values.use_gles.GetValue());
|
||||
log_setting("Renderer_UseHwRenderer", values.use_hw_renderer.GetValue());
|
||||
log_setting("Renderer_GraphicsAPI", GetGraphicsAPIName(values.graphics_api.GetValue()));
|
||||
log_setting("Renderer_UseHwShader", values.use_hw_shader.GetValue());
|
||||
log_setting("Renderer_SeparableShader", values.separable_shader.GetValue());
|
||||
log_setting("Renderer_ShadersAccurateMul", values.shaders_accurate_mul.GetValue());
|
||||
@ -186,7 +198,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
values.is_new_3ds.SetGlobal(true);
|
||||
|
||||
// Renderer
|
||||
values.use_hw_renderer.SetGlobal(true);
|
||||
values.graphics_api.SetGlobal(true);
|
||||
values.use_hw_shader.SetGlobal(true);
|
||||
values.separable_shader.SetGlobal(true);
|
||||
values.use_disk_shader_cache.SetGlobal(true);
|
||||
|
@ -15,6 +15,11 @@
|
||||
|
||||
namespace Settings {
|
||||
|
||||
enum class GraphicsAPI {
|
||||
Software = 0,
|
||||
OpenGL = 1,
|
||||
};
|
||||
|
||||
enum class InitClock : u32 {
|
||||
SystemTime = 0,
|
||||
FixedTime = 1,
|
||||
@ -415,8 +420,9 @@ struct Values {
|
||||
Setting<bool> allow_plugin_loader{true, "allow_plugin_loader"};
|
||||
|
||||
// Renderer
|
||||
SwitchableSetting<GraphicsAPI> graphics_api{GraphicsAPI::OpenGL, "graphics_api"};
|
||||
Setting<bool> use_gles{false, "use_gles"};
|
||||
SwitchableSetting<bool> use_hw_renderer{true, "use_hw_renderer"};
|
||||
Setting<bool> renderer_debug{false, "renderer_debug"};
|
||||
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
|
||||
SwitchableSetting<bool> separable_shader{false, "use_separable_shader"};
|
||||
SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};
|
||||
|
@ -113,8 +113,6 @@ add_library(core STATIC
|
||||
frontend/input.h
|
||||
frontend/mic.cpp
|
||||
frontend/mic.h
|
||||
frontend/scope_acquire_context.cpp
|
||||
frontend/scope_acquire_context.h
|
||||
gdbstub/gdbstub.cpp
|
||||
gdbstub/gdbstub.h
|
||||
hle/applets/applet.cpp
|
||||
|
@ -431,17 +431,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
video_dumper = std::make_unique<VideoDumper::NullBackend>();
|
||||
#endif
|
||||
|
||||
VideoCore::ResultStatus result = VideoCore::Init(emu_window, secondary_window, *memory);
|
||||
if (result != VideoCore::ResultStatus::Success) {
|
||||
switch (result) {
|
||||
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
||||
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
|
||||
case VideoCore::ResultStatus::ErrorBelowGL43:
|
||||
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
|
||||
default:
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
}
|
||||
VideoCore::Init(emu_window, secondary_window, *this);
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
@ -450,7 +440,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
RendererBase& System::Renderer() {
|
||||
VideoCore::RendererBase& System::Renderer() {
|
||||
return *VideoCore::g_renderer;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,9 @@ namespace VideoDumper {
|
||||
class Backend;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RendererBase;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
@ -87,11 +89,6 @@ public:
|
||||
ErrorLoader_ErrorGbaTitle, ///< Error loading the specified application as it is GBA Virtual
|
||||
///< Console
|
||||
ErrorSystemFiles, ///< Error in finding system files
|
||||
ErrorVideoCore, ///< Error in the video core
|
||||
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
|
||||
/// generic drivers installed
|
||||
ErrorVideoCore_ErrorBelowGL43, ///< Error in the video core due to the user not having
|
||||
/// OpenGL 4.3 or higher
|
||||
ErrorSavestate, ///< Error saving or loading
|
||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||
ErrorUnknown ///< Any other error
|
||||
@ -210,7 +207,7 @@ public:
|
||||
return *dsp_core;
|
||||
}
|
||||
|
||||
[[nodiscard]] RendererBase& Renderer();
|
||||
[[nodiscard]] VideoCore::RendererBase& Renderer();
|
||||
|
||||
/**
|
||||
* Gets a reference to the service manager.
|
||||
|
@ -14,6 +14,17 @@
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
/// Information for the Graphics Backends signifying what type of screen pointer is in
|
||||
/// WindowInformation
|
||||
enum class WindowSystemType : u8 {
|
||||
Headless,
|
||||
Android,
|
||||
Windows,
|
||||
MacOS,
|
||||
X11,
|
||||
Wayland,
|
||||
};
|
||||
|
||||
struct Frame;
|
||||
/**
|
||||
* For smooth Vsync rendering, we want to always present the latest frame that the core generates,
|
||||
@ -62,11 +73,33 @@ class GraphicsContext {
|
||||
public:
|
||||
virtual ~GraphicsContext();
|
||||
|
||||
/// Inform the driver to swap the front/back buffers and present the current image
|
||||
virtual void SwapBuffers(){};
|
||||
|
||||
/// Makes the graphics context current for the caller thread
|
||||
virtual void MakeCurrent() = 0;
|
||||
virtual void MakeCurrent(){};
|
||||
|
||||
/// Releases (dunno if this is the "right" word) the context from the caller thread
|
||||
virtual void DoneCurrent() = 0;
|
||||
virtual void DoneCurrent(){};
|
||||
|
||||
class Scoped {
|
||||
public:
|
||||
explicit Scoped(GraphicsContext& context_) : context(context_) {
|
||||
context.MakeCurrent();
|
||||
}
|
||||
~Scoped() {
|
||||
context.DoneCurrent();
|
||||
}
|
||||
|
||||
private:
|
||||
GraphicsContext& context;
|
||||
};
|
||||
|
||||
/// Calls MakeCurrent on the context and calls DoneCurrent when the scope for the returned value
|
||||
/// ends
|
||||
[[nodiscard]] Scoped Acquire() {
|
||||
return Scoped{*this};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -100,6 +133,23 @@ public:
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight};
|
||||
};
|
||||
|
||||
/// Data describing host window system information
|
||||
struct WindowSystemInfo {
|
||||
// Window system type. Determines which GL context or Vulkan WSI is used.
|
||||
WindowSystemType type = WindowSystemType::Headless;
|
||||
|
||||
// Connection to a display server. This is used on X11 and Wayland platforms.
|
||||
void* display_connection = nullptr;
|
||||
|
||||
// Render surface. This is a pointer to the native window handle, which depends
|
||||
// on the platform. e.g. HWND for Windows, Window for X11. If the surface is
|
||||
// set to nullptr, the video backend will run in headless mode.
|
||||
void* render_surface = nullptr;
|
||||
|
||||
// Scale of the render surface. For hidpi systems, this will be >1.
|
||||
float render_surface_scale = 1.0f;
|
||||
};
|
||||
|
||||
/// Polls window events
|
||||
virtual void PollEvents() = 0;
|
||||
|
||||
@ -163,6 +213,13 @@ public:
|
||||
config = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns system information about the drawing area.
|
||||
*/
|
||||
const WindowSystemInfo& GetWindowInfo() const {
|
||||
return window_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the framebuffer layout (width, height, and screen regions)
|
||||
* @note This method is thread-safe
|
||||
@ -211,6 +268,7 @@ protected:
|
||||
}
|
||||
|
||||
bool is_secondary{};
|
||||
WindowSystemInfo window_info;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -1,17 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
ScopeAcquireContext::ScopeAcquireContext(Frontend::GraphicsContext& context) : context{context} {
|
||||
context.MakeCurrent();
|
||||
}
|
||||
ScopeAcquireContext::~ScopeAcquireContext() {
|
||||
context.DoneCurrent();
|
||||
}
|
||||
|
||||
} // namespace Frontend
|
@ -1,23 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
class GraphicsContext;
|
||||
|
||||
/// Helper class to acquire/release window context within a given scope
|
||||
class ScopeAcquireContext : NonCopyable {
|
||||
public:
|
||||
explicit ScopeAcquireContext(Frontend::GraphicsContext& context);
|
||||
~ScopeAcquireContext();
|
||||
|
||||
private:
|
||||
Frontend::GraphicsContext& context;
|
||||
};
|
||||
|
||||
} // namespace Frontend
|
@ -133,8 +133,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
|
||||
Settings::values.resolution_factor.GetValue());
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit",
|
||||
Settings::values.frame_limit.GetValue());
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwRenderer",
|
||||
Settings::values.use_hw_renderer.GetValue());
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_Backend",
|
||||
static_cast<int>(Settings::values.graphics_api.GetValue()));
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwShader",
|
||||
Settings::values.use_hw_shader.GetValue());
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_ShadersAccurateMul",
|
||||
|
@ -13,6 +13,8 @@ add_library(video_core STATIC
|
||||
precompiled_headers.h
|
||||
primitive_assembly.cpp
|
||||
primitive_assembly.h
|
||||
rasterizer_accelerated.cpp
|
||||
rasterizer_accelerated.h
|
||||
rasterizer_interface.h
|
||||
regs.cpp
|
||||
regs.h
|
||||
@ -39,6 +41,8 @@ add_library(video_core STATIC
|
||||
rasterizer_cache/texture_runtime.h
|
||||
renderer_opengl/frame_dumper_opengl.cpp
|
||||
renderer_opengl/frame_dumper_opengl.h
|
||||
renderer_opengl/gl_driver.cpp
|
||||
renderer_opengl/gl_driver.h
|
||||
renderer_opengl/gl_rasterizer.cpp
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_resource_manager.cpp
|
||||
@ -82,6 +86,22 @@ add_library(video_core STATIC
|
||||
#temporary, move these back in alphabetical order before merging
|
||||
renderer_opengl/gl_format_reinterpreter.cpp
|
||||
renderer_opengl/gl_format_reinterpreter.h
|
||||
renderer_software/rasterizer.cpp
|
||||
renderer_software/rasterizer.h
|
||||
renderer_software/renderer_software.cpp
|
||||
renderer_software/renderer_software.h
|
||||
renderer_software/sw_clipper.cpp
|
||||
renderer_software/sw_clipper.h
|
||||
renderer_software/sw_framebuffer.cpp
|
||||
renderer_software/sw_framebuffer.h
|
||||
renderer_software/sw_lighting.cpp
|
||||
renderer_software/sw_lighting.h
|
||||
renderer_software/sw_proctex.cpp
|
||||
renderer_software/sw_proctex.h
|
||||
renderer_software/sw_rasterizer.cpp
|
||||
renderer_software/sw_rasterizer.h
|
||||
renderer_software/sw_texturing.cpp
|
||||
renderer_software/sw_texturing.h
|
||||
shader/debug_data.h
|
||||
shader/shader.cpp
|
||||
shader/shader.h
|
||||
@ -91,20 +111,8 @@ add_library(video_core STATIC
|
||||
shader/shader_jit_x64_compiler.cpp
|
||||
shader/shader_jit_x64.h
|
||||
shader/shader_jit_x64_compiler.h
|
||||
swrasterizer/clipper.cpp
|
||||
swrasterizer/clipper.h
|
||||
swrasterizer/framebuffer.cpp
|
||||
swrasterizer/framebuffer.h
|
||||
swrasterizer/lighting.cpp
|
||||
swrasterizer/lighting.h
|
||||
swrasterizer/proctex.cpp
|
||||
swrasterizer/proctex.h
|
||||
swrasterizer/rasterizer.cpp
|
||||
swrasterizer/rasterizer.h
|
||||
swrasterizer/swrasterizer.cpp
|
||||
swrasterizer/swrasterizer.h
|
||||
swrasterizer/texturing.cpp
|
||||
swrasterizer/texturing.h
|
||||
shader/shader_uniforms.cpp
|
||||
shader/shader_uniforms.h
|
||||
texture/etc1.cpp
|
||||
texture/etc1.h
|
||||
texture/texture_decode.cpp
|
||||
|
832
src/video_core/rasterizer_accelerated.cpp
Normal file
832
src/video_core/rasterizer_accelerated.cpp
Normal file
@ -0,0 +1,832 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <limits>
|
||||
#include "common/alignment.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/rasterizer_accelerated.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
static Common::Vec4f ColorRGBA8(const u32 color) {
|
||||
const auto rgba =
|
||||
Common::Vec4u{color >> 0 & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF, color >> 24 & 0xFF};
|
||||
return rgba / 255.0f;
|
||||
}
|
||||
|
||||
static Common::Vec3f LightColor(const Pica::LightingRegs::LightColor& color) {
|
||||
return Common::Vec3u{color.r, color.g, color.b} / 255.0f;
|
||||
}
|
||||
|
||||
RasterizerAccelerated::HardwareVertex::HardwareVertex(const Pica::Shader::OutputVertex& v,
|
||||
bool flip_quaternion) {
|
||||
position[0] = v.pos.x.ToFloat32();
|
||||
position[1] = v.pos.y.ToFloat32();
|
||||
position[2] = v.pos.z.ToFloat32();
|
||||
position[3] = v.pos.w.ToFloat32();
|
||||
color[0] = v.color.x.ToFloat32();
|
||||
color[1] = v.color.y.ToFloat32();
|
||||
color[2] = v.color.z.ToFloat32();
|
||||
color[3] = v.color.w.ToFloat32();
|
||||
tex_coord0[0] = v.tc0.x.ToFloat32();
|
||||
tex_coord0[1] = v.tc0.y.ToFloat32();
|
||||
tex_coord1[0] = v.tc1.x.ToFloat32();
|
||||
tex_coord1[1] = v.tc1.y.ToFloat32();
|
||||
tex_coord2[0] = v.tc2.x.ToFloat32();
|
||||
tex_coord2[1] = v.tc2.y.ToFloat32();
|
||||
tex_coord0_w = v.tc0_w.ToFloat32();
|
||||
normquat[0] = v.quat.x.ToFloat32();
|
||||
normquat[1] = v.quat.y.ToFloat32();
|
||||
normquat[2] = v.quat.z.ToFloat32();
|
||||
normquat[3] = v.quat.w.ToFloat32();
|
||||
view[0] = v.view.x.ToFloat32();
|
||||
view[1] = v.view.y.ToFloat32();
|
||||
view[2] = v.view.z.ToFloat32();
|
||||
|
||||
if (flip_quaternion) {
|
||||
normquat = -normquat;
|
||||
}
|
||||
}
|
||||
|
||||
RasterizerAccelerated::RasterizerAccelerated(Memory::MemorySystem& memory_)
|
||||
: memory{memory_}, regs{Pica::g_state.regs} {
|
||||
uniform_block_data.lighting_lut_dirty.fill(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a helper function to resolve an issue when interpolating opposite quaternions. See below
|
||||
* for a detailed description of this issue (yuriks):
|
||||
*
|
||||
* For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you
|
||||
* interpolate two quaternions that are opposite, instead of going from one rotation to another
|
||||
* using the shortest path, you'll go around the longest path. You can test if two quaternions are
|
||||
* opposite by checking if Dot(Q1, Q2) < 0. In that case, you can flip either of them, therefore
|
||||
* making Dot(Q1, -Q2) positive.
|
||||
*
|
||||
* This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This is
|
||||
* correct for most cases but can still rotate around the long way sometimes. An implementation
|
||||
* which did `lerp(lerp(Q1, Q2), Q3)` (with proper weighting), applying the dot product check
|
||||
* between each step would work for those cases at the cost of being more complex to implement.
|
||||
*
|
||||
* Fortunately however, the 3DS hardware happens to also use this exact same logic to work around
|
||||
* these issues, making this basic implementation actually more accurate to the hardware.
|
||||
*/
|
||||
static bool AreQuaternionsOpposite(Common::Vec4<Pica::float24> qa, Common::Vec4<Pica::float24> qb) {
|
||||
Common::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()};
|
||||
Common::Vec4f b{qb.x.ToFloat32(), qb.y.ToFloat32(), qb.z.ToFloat32(), qb.w.ToFloat32()};
|
||||
|
||||
return (Common::Dot(a, b) < 0.f);
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::AddTriangle(const Pica::Shader::OutputVertex& v0,
|
||||
const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) {
|
||||
vertex_batch.emplace_back(v0, false);
|
||||
vertex_batch.emplace_back(v1, AreQuaternionsOpposite(v0.quat, v1.quat));
|
||||
vertex_batch.emplace_back(v2, AreQuaternionsOpposite(v0.quat, v2.quat));
|
||||
}
|
||||
|
||||
RasterizerAccelerated::VertexArrayInfo RasterizerAccelerated::AnalyzeVertexArray(
|
||||
bool is_indexed, u32 stride_alignment) {
|
||||
const auto& vertex_attributes = regs.pipeline.vertex_attributes;
|
||||
|
||||
u32 vertex_min;
|
||||
u32 vertex_max;
|
||||
if (is_indexed) {
|
||||
const auto& index_info = regs.pipeline.index_array;
|
||||
const PAddr address = vertex_attributes.GetPhysicalBaseAddress() + index_info.offset;
|
||||
const u8* index_address_8 = memory.GetPhysicalPointer(address);
|
||||
const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8);
|
||||
const bool index_u16 = index_info.format != 0;
|
||||
|
||||
vertex_min = 0xFFFF;
|
||||
vertex_max = 0;
|
||||
const u32 size = regs.pipeline.num_vertices * (index_u16 ? 2 : 1);
|
||||
FlushRegion(address, size);
|
||||
for (u32 index = 0; index < regs.pipeline.num_vertices; ++index) {
|
||||
const u32 vertex = index_u16 ? index_address_16[index] : index_address_8[index];
|
||||
vertex_min = std::min(vertex_min, vertex);
|
||||
vertex_max = std::max(vertex_max, vertex);
|
||||
}
|
||||
} else {
|
||||
vertex_min = regs.pipeline.vertex_offset;
|
||||
vertex_max = regs.pipeline.vertex_offset + regs.pipeline.num_vertices - 1;
|
||||
}
|
||||
|
||||
const u32 vertex_num = vertex_max - vertex_min + 1;
|
||||
u32 vs_input_size = 0;
|
||||
for (const auto& loader : vertex_attributes.attribute_loaders) {
|
||||
if (loader.component_count != 0) {
|
||||
const u32 aligned_stride =
|
||||
Common::AlignUp(static_cast<u32>(loader.byte_count), stride_alignment);
|
||||
vs_input_size += Common::AlignUp(aligned_stride * vertex_num, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return {vertex_min, vertex_max, vs_input_size};
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncEntireState() {
|
||||
// Sync renderer-specific fixed-function state
|
||||
SyncFixedState();
|
||||
|
||||
// Sync uniforms
|
||||
SyncClipCoef();
|
||||
SyncDepthScale();
|
||||
SyncDepthOffset();
|
||||
SyncAlphaTest();
|
||||
SyncCombinerColor();
|
||||
auto& tev_stages = regs.texturing.GetTevStages();
|
||||
for (std::size_t index = 0; index < tev_stages.size(); ++index) {
|
||||
SyncTevConstColor(index, tev_stages[index]);
|
||||
}
|
||||
|
||||
SyncGlobalAmbient();
|
||||
for (unsigned light_index = 0; light_index < 8; light_index++) {
|
||||
SyncLightSpecular0(light_index);
|
||||
SyncLightSpecular1(light_index);
|
||||
SyncLightDiffuse(light_index);
|
||||
SyncLightAmbient(light_index);
|
||||
SyncLightPosition(light_index);
|
||||
SyncLightDistanceAttenuationBias(light_index);
|
||||
SyncLightDistanceAttenuationScale(light_index);
|
||||
}
|
||||
|
||||
SyncFogColor();
|
||||
SyncProcTexNoise();
|
||||
SyncProcTexBias();
|
||||
SyncShadowBias();
|
||||
SyncShadowTextureBias();
|
||||
|
||||
for (unsigned tex_index = 0; tex_index < 3; tex_index++) {
|
||||
SyncTextureLodBias(tex_index);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::NotifyPicaRegisterChanged(u32 id) {
|
||||
switch (id) {
|
||||
// Depth modifiers
|
||||
case PICA_REG_INDEX(rasterizer.viewport_depth_range):
|
||||
SyncDepthScale();
|
||||
break;
|
||||
case PICA_REG_INDEX(rasterizer.viewport_depth_near_plane):
|
||||
SyncDepthOffset();
|
||||
break;
|
||||
|
||||
// Depth buffering
|
||||
case PICA_REG_INDEX(rasterizer.depthmap_enable):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Shadow texture
|
||||
case PICA_REG_INDEX(texturing.shadow):
|
||||
SyncShadowTextureBias();
|
||||
break;
|
||||
|
||||
// Fog state
|
||||
case PICA_REG_INDEX(texturing.fog_color):
|
||||
SyncFogColor();
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[0]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[1]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[2]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[3]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[4]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[5]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[6]):
|
||||
case PICA_REG_INDEX(texturing.fog_lut_data[7]):
|
||||
uniform_block_data.fog_lut_dirty = true;
|
||||
break;
|
||||
|
||||
// ProcTex state
|
||||
case PICA_REG_INDEX(texturing.proctex):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_offset):
|
||||
SyncProcTexBias();
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(texturing.proctex_noise_u):
|
||||
case PICA_REG_INDEX(texturing.proctex_noise_v):
|
||||
case PICA_REG_INDEX(texturing.proctex_noise_frequency):
|
||||
SyncProcTexNoise();
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[0]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[1]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[2]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[3]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[4]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[5]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[6]):
|
||||
case PICA_REG_INDEX(texturing.proctex_lut_data[7]):
|
||||
using Pica::TexturingRegs;
|
||||
switch (regs.texturing.proctex_lut_config.ref_table.Value()) {
|
||||
case TexturingRegs::ProcTexLutTable::Noise:
|
||||
uniform_block_data.proctex_noise_lut_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::ColorMap:
|
||||
uniform_block_data.proctex_color_map_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::AlphaMap:
|
||||
uniform_block_data.proctex_alpha_map_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::Color:
|
||||
uniform_block_data.proctex_lut_dirty = true;
|
||||
break;
|
||||
case TexturingRegs::ProcTexLutTable::ColorDiff:
|
||||
uniform_block_data.proctex_diff_lut_dirty = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// Alpha test
|
||||
case PICA_REG_INDEX(framebuffer.output_merger.alpha_test):
|
||||
SyncAlphaTest();
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(framebuffer.shadow):
|
||||
SyncShadowBias();
|
||||
break;
|
||||
|
||||
// Scissor test
|
||||
case PICA_REG_INDEX(rasterizer.scissor_test.mode):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
case PICA_REG_INDEX(texturing.main_config):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Texture 0 type
|
||||
case PICA_REG_INDEX(texturing.texture0.type):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// TEV stages
|
||||
// (This also syncs fog_mode and fog_flip which are part of tev_combiner_buffer_input)
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_source1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_modifier1):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_op):
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.color_scale):
|
||||
case PICA_REG_INDEX(texturing.tev_combiner_buffer_input):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage0.const_r):
|
||||
SyncTevConstColor(0, regs.texturing.tev_stage0);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage1.const_r):
|
||||
SyncTevConstColor(1, regs.texturing.tev_stage1);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage2.const_r):
|
||||
SyncTevConstColor(2, regs.texturing.tev_stage2);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage3.const_r):
|
||||
SyncTevConstColor(3, regs.texturing.tev_stage3);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage4.const_r):
|
||||
SyncTevConstColor(4, regs.texturing.tev_stage4);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.tev_stage5.const_r):
|
||||
SyncTevConstColor(5, regs.texturing.tev_stage5);
|
||||
break;
|
||||
|
||||
// TEV combiner buffer color
|
||||
case PICA_REG_INDEX(texturing.tev_combiner_buffer_color):
|
||||
SyncCombinerColor();
|
||||
break;
|
||||
|
||||
// Fragment lighting switches
|
||||
case PICA_REG_INDEX(lighting.disable):
|
||||
case PICA_REG_INDEX(lighting.max_light_index):
|
||||
case PICA_REG_INDEX(lighting.config0):
|
||||
case PICA_REG_INDEX(lighting.config1):
|
||||
case PICA_REG_INDEX(lighting.abs_lut_input):
|
||||
case PICA_REG_INDEX(lighting.lut_input):
|
||||
case PICA_REG_INDEX(lighting.lut_scale):
|
||||
case PICA_REG_INDEX(lighting.light_enable):
|
||||
break;
|
||||
|
||||
// Fragment lighting specular 0 color
|
||||
case PICA_REG_INDEX(lighting.light[0].specular_0):
|
||||
SyncLightSpecular0(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].specular_0):
|
||||
SyncLightSpecular0(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].specular_0):
|
||||
SyncLightSpecular0(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].specular_0):
|
||||
SyncLightSpecular0(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].specular_0):
|
||||
SyncLightSpecular0(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].specular_0):
|
||||
SyncLightSpecular0(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].specular_0):
|
||||
SyncLightSpecular0(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].specular_0):
|
||||
SyncLightSpecular0(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting specular 1 color
|
||||
case PICA_REG_INDEX(lighting.light[0].specular_1):
|
||||
SyncLightSpecular1(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].specular_1):
|
||||
SyncLightSpecular1(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].specular_1):
|
||||
SyncLightSpecular1(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].specular_1):
|
||||
SyncLightSpecular1(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].specular_1):
|
||||
SyncLightSpecular1(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].specular_1):
|
||||
SyncLightSpecular1(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].specular_1):
|
||||
SyncLightSpecular1(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].specular_1):
|
||||
SyncLightSpecular1(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting diffuse color
|
||||
case PICA_REG_INDEX(lighting.light[0].diffuse):
|
||||
SyncLightDiffuse(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].diffuse):
|
||||
SyncLightDiffuse(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].diffuse):
|
||||
SyncLightDiffuse(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].diffuse):
|
||||
SyncLightDiffuse(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].diffuse):
|
||||
SyncLightDiffuse(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].diffuse):
|
||||
SyncLightDiffuse(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].diffuse):
|
||||
SyncLightDiffuse(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].diffuse):
|
||||
SyncLightDiffuse(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting ambient color
|
||||
case PICA_REG_INDEX(lighting.light[0].ambient):
|
||||
SyncLightAmbient(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].ambient):
|
||||
SyncLightAmbient(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].ambient):
|
||||
SyncLightAmbient(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].ambient):
|
||||
SyncLightAmbient(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].ambient):
|
||||
SyncLightAmbient(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].ambient):
|
||||
SyncLightAmbient(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].ambient):
|
||||
SyncLightAmbient(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].ambient):
|
||||
SyncLightAmbient(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting position
|
||||
case PICA_REG_INDEX(lighting.light[0].x):
|
||||
case PICA_REG_INDEX(lighting.light[0].z):
|
||||
SyncLightPosition(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].x):
|
||||
case PICA_REG_INDEX(lighting.light[1].z):
|
||||
SyncLightPosition(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].x):
|
||||
case PICA_REG_INDEX(lighting.light[2].z):
|
||||
SyncLightPosition(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].x):
|
||||
case PICA_REG_INDEX(lighting.light[3].z):
|
||||
SyncLightPosition(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].x):
|
||||
case PICA_REG_INDEX(lighting.light[4].z):
|
||||
SyncLightPosition(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].x):
|
||||
case PICA_REG_INDEX(lighting.light[5].z):
|
||||
SyncLightPosition(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].x):
|
||||
case PICA_REG_INDEX(lighting.light[6].z):
|
||||
SyncLightPosition(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].x):
|
||||
case PICA_REG_INDEX(lighting.light[7].z):
|
||||
SyncLightPosition(7);
|
||||
break;
|
||||
|
||||
// Fragment spot lighting direction
|
||||
case PICA_REG_INDEX(lighting.light[0].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[0].spot_z):
|
||||
SyncLightSpotDirection(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[1].spot_z):
|
||||
SyncLightSpotDirection(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[2].spot_z):
|
||||
SyncLightSpotDirection(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[3].spot_z):
|
||||
SyncLightSpotDirection(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[4].spot_z):
|
||||
SyncLightSpotDirection(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[5].spot_z):
|
||||
SyncLightSpotDirection(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[6].spot_z):
|
||||
SyncLightSpotDirection(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].spot_x):
|
||||
case PICA_REG_INDEX(lighting.light[7].spot_z):
|
||||
SyncLightSpotDirection(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting light source config
|
||||
case PICA_REG_INDEX(lighting.light[0].config):
|
||||
case PICA_REG_INDEX(lighting.light[1].config):
|
||||
case PICA_REG_INDEX(lighting.light[2].config):
|
||||
case PICA_REG_INDEX(lighting.light[3].config):
|
||||
case PICA_REG_INDEX(lighting.light[4].config):
|
||||
case PICA_REG_INDEX(lighting.light[5].config):
|
||||
case PICA_REG_INDEX(lighting.light[6].config):
|
||||
case PICA_REG_INDEX(lighting.light[7].config):
|
||||
shader_dirty = true;
|
||||
break;
|
||||
|
||||
// Fragment lighting distance attenuation bias
|
||||
case PICA_REG_INDEX(lighting.light[0].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].dist_atten_bias):
|
||||
SyncLightDistanceAttenuationBias(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting distance attenuation scale
|
||||
case PICA_REG_INDEX(lighting.light[0].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[1].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[2].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(2);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[3].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(3);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[4].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(4);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[5].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(5);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[6].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(6);
|
||||
break;
|
||||
case PICA_REG_INDEX(lighting.light[7].dist_atten_scale):
|
||||
SyncLightDistanceAttenuationScale(7);
|
||||
break;
|
||||
|
||||
// Fragment lighting global ambient color (emission + ambient * ambient)
|
||||
case PICA_REG_INDEX(lighting.global_ambient):
|
||||
SyncGlobalAmbient();
|
||||
break;
|
||||
|
||||
// Fragment lighting lookup tables
|
||||
case PICA_REG_INDEX(lighting.lut_data[0]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[1]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[2]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[3]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[4]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[5]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[6]):
|
||||
case PICA_REG_INDEX(lighting.lut_data[7]): {
|
||||
const auto& lut_config = regs.lighting.lut_config;
|
||||
uniform_block_data.lighting_lut_dirty[lut_config.type] = true;
|
||||
uniform_block_data.lighting_lut_dirty_any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Texture LOD biases
|
||||
case PICA_REG_INDEX(texturing.texture0.lod.bias):
|
||||
SyncTextureLodBias(0);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.texture1.lod.bias):
|
||||
SyncTextureLodBias(1);
|
||||
break;
|
||||
case PICA_REG_INDEX(texturing.texture2.lod.bias):
|
||||
SyncTextureLodBias(2);
|
||||
break;
|
||||
|
||||
// Clipping plane
|
||||
case PICA_REG_INDEX(rasterizer.clip_coef[0]):
|
||||
case PICA_REG_INDEX(rasterizer.clip_coef[1]):
|
||||
case PICA_REG_INDEX(rasterizer.clip_coef[2]):
|
||||
case PICA_REG_INDEX(rasterizer.clip_coef[3]):
|
||||
SyncClipCoef();
|
||||
break;
|
||||
|
||||
default:
|
||||
// Forward registers that map to fixed function API features to the video backend
|
||||
NotifyFixedFunctionPicaRegisterChanged(id);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncDepthScale() {
|
||||
float depth_scale = Pica::float24::FromRaw(regs.rasterizer.viewport_depth_range).ToFloat32();
|
||||
|
||||
if (depth_scale != uniform_block_data.data.depth_scale) {
|
||||
uniform_block_data.data.depth_scale = depth_scale;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncDepthOffset() {
|
||||
float depth_offset =
|
||||
Pica::float24::FromRaw(regs.rasterizer.viewport_depth_near_plane).ToFloat32();
|
||||
|
||||
if (depth_offset != uniform_block_data.data.depth_offset) {
|
||||
uniform_block_data.data.depth_offset = depth_offset;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncFogColor() {
|
||||
const auto& fog_color_regs = regs.texturing.fog_color;
|
||||
const Common::Vec3f fog_color = {
|
||||
fog_color_regs.r.Value() / 255.0f,
|
||||
fog_color_regs.g.Value() / 255.0f,
|
||||
fog_color_regs.b.Value() / 255.0f,
|
||||
};
|
||||
|
||||
if (fog_color != uniform_block_data.data.fog_color) {
|
||||
uniform_block_data.data.fog_color = fog_color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncProcTexNoise() {
|
||||
const Common::Vec2f proctex_noise_f = {
|
||||
Pica::float16::FromRaw(regs.texturing.proctex_noise_frequency.u).ToFloat32(),
|
||||
Pica::float16::FromRaw(regs.texturing.proctex_noise_frequency.v).ToFloat32(),
|
||||
};
|
||||
const Common::Vec2f proctex_noise_a = {
|
||||
regs.texturing.proctex_noise_u.amplitude / 4095.0f,
|
||||
regs.texturing.proctex_noise_v.amplitude / 4095.0f,
|
||||
};
|
||||
const Common::Vec2f proctex_noise_p = {
|
||||
Pica::float16::FromRaw(regs.texturing.proctex_noise_u.phase).ToFloat32(),
|
||||
Pica::float16::FromRaw(regs.texturing.proctex_noise_v.phase).ToFloat32(),
|
||||
};
|
||||
|
||||
if (proctex_noise_f != uniform_block_data.data.proctex_noise_f ||
|
||||
proctex_noise_a != uniform_block_data.data.proctex_noise_a ||
|
||||
proctex_noise_p != uniform_block_data.data.proctex_noise_p) {
|
||||
uniform_block_data.data.proctex_noise_f = proctex_noise_f;
|
||||
uniform_block_data.data.proctex_noise_a = proctex_noise_a;
|
||||
uniform_block_data.data.proctex_noise_p = proctex_noise_p;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncProcTexBias() {
|
||||
const auto proctex_bias = Pica::float16::FromRaw(regs.texturing.proctex.bias_low |
|
||||
(regs.texturing.proctex_lut.bias_high << 8))
|
||||
.ToFloat32();
|
||||
if (proctex_bias != uniform_block_data.data.proctex_bias) {
|
||||
uniform_block_data.data.proctex_bias = proctex_bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncAlphaTest() {
|
||||
if (regs.framebuffer.output_merger.alpha_test.ref != uniform_block_data.data.alphatest_ref) {
|
||||
uniform_block_data.data.alphatest_ref = regs.framebuffer.output_merger.alpha_test.ref;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncCombinerColor() {
|
||||
auto combiner_color = ColorRGBA8(regs.texturing.tev_combiner_buffer_color.raw);
|
||||
if (combiner_color != uniform_block_data.data.tev_combiner_buffer_color) {
|
||||
uniform_block_data.data.tev_combiner_buffer_color = combiner_color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncTevConstColor(
|
||||
std::size_t stage_index, const Pica::TexturingRegs::TevStageConfig& tev_stage) {
|
||||
const auto const_color = ColorRGBA8(tev_stage.const_color);
|
||||
|
||||
if (const_color == uniform_block_data.data.const_color[stage_index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
uniform_block_data.data.const_color[stage_index] = const_color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncGlobalAmbient() {
|
||||
auto color = LightColor(regs.lighting.global_ambient);
|
||||
if (color != uniform_block_data.data.lighting_global_ambient) {
|
||||
uniform_block_data.data.lighting_global_ambient = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightSpecular0(int light_index) {
|
||||
auto color = LightColor(regs.lighting.light[light_index].specular_0);
|
||||
if (color != uniform_block_data.data.light_src[light_index].specular_0) {
|
||||
uniform_block_data.data.light_src[light_index].specular_0 = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightSpecular1(int light_index) {
|
||||
auto color = LightColor(regs.lighting.light[light_index].specular_1);
|
||||
if (color != uniform_block_data.data.light_src[light_index].specular_1) {
|
||||
uniform_block_data.data.light_src[light_index].specular_1 = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightDiffuse(int light_index) {
|
||||
auto color = LightColor(regs.lighting.light[light_index].diffuse);
|
||||
if (color != uniform_block_data.data.light_src[light_index].diffuse) {
|
||||
uniform_block_data.data.light_src[light_index].diffuse = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightAmbient(int light_index) {
|
||||
auto color = LightColor(regs.lighting.light[light_index].ambient);
|
||||
if (color != uniform_block_data.data.light_src[light_index].ambient) {
|
||||
uniform_block_data.data.light_src[light_index].ambient = color;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightPosition(int light_index) {
|
||||
const Common::Vec3f position = {
|
||||
Pica::float16::FromRaw(regs.lighting.light[light_index].x).ToFloat32(),
|
||||
Pica::float16::FromRaw(regs.lighting.light[light_index].y).ToFloat32(),
|
||||
Pica::float16::FromRaw(regs.lighting.light[light_index].z).ToFloat32(),
|
||||
};
|
||||
|
||||
if (position != uniform_block_data.data.light_src[light_index].position) {
|
||||
uniform_block_data.data.light_src[light_index].position = position;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightSpotDirection(int light_index) {
|
||||
const auto& light = regs.lighting.light[light_index];
|
||||
const auto spot_direction =
|
||||
Common::Vec3f{light.spot_x / 2047.0f, light.spot_y / 2047.0f, light.spot_z / 2047.0f};
|
||||
|
||||
if (spot_direction != uniform_block_data.data.light_src[light_index].spot_direction) {
|
||||
uniform_block_data.data.light_src[light_index].spot_direction = spot_direction;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightDistanceAttenuationBias(int light_index) {
|
||||
float dist_atten_bias =
|
||||
Pica::float20::FromRaw(regs.lighting.light[light_index].dist_atten_bias).ToFloat32();
|
||||
|
||||
if (dist_atten_bias != uniform_block_data.data.light_src[light_index].dist_atten_bias) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_bias = dist_atten_bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncLightDistanceAttenuationScale(int light_index) {
|
||||
float dist_atten_scale =
|
||||
Pica::float20::FromRaw(regs.lighting.light[light_index].dist_atten_scale).ToFloat32();
|
||||
|
||||
if (dist_atten_scale != uniform_block_data.data.light_src[light_index].dist_atten_scale) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_scale = dist_atten_scale;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncShadowBias() {
|
||||
const auto& shadow = regs.framebuffer.shadow;
|
||||
float constant = Pica::float16::FromRaw(shadow.constant).ToFloat32();
|
||||
float linear = Pica::float16::FromRaw(shadow.linear).ToFloat32();
|
||||
|
||||
if (constant != uniform_block_data.data.shadow_bias_constant ||
|
||||
linear != uniform_block_data.data.shadow_bias_linear) {
|
||||
uniform_block_data.data.shadow_bias_constant = constant;
|
||||
uniform_block_data.data.shadow_bias_linear = linear;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncShadowTextureBias() {
|
||||
int bias = regs.texturing.shadow.bias << 1;
|
||||
if (bias != uniform_block_data.data.shadow_texture_bias) {
|
||||
uniform_block_data.data.shadow_texture_bias = bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncTextureLodBias(int tex_index) {
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
const float bias = pica_textures[tex_index].config.lod.bias / 256.0f;
|
||||
if (bias != uniform_block_data.data.tex_lod_bias[tex_index]) {
|
||||
uniform_block_data.data.tex_lod_bias[tex_index] = bias;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncClipCoef() {
|
||||
const auto raw_clip_coef = regs.rasterizer.GetClipCoef();
|
||||
const Common::Vec4f new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(),
|
||||
raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()};
|
||||
if (new_clip_coef != uniform_block_data.data.clip_coef) {
|
||||
uniform_block_data.data.clip_coef = new_clip_coef;
|
||||
uniform_block_data.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
159
src/video_core/rasterizer_accelerated.h
Normal file
159
src/video_core/rasterizer_accelerated.h
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/shader/shader_uniforms.h"
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
}
|
||||
|
||||
namespace Pica {
|
||||
struct Regs;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class RasterizerAccelerated : public RasterizerInterface {
|
||||
public:
|
||||
RasterizerAccelerated(Memory::MemorySystem& memory);
|
||||
virtual ~RasterizerAccelerated() = default;
|
||||
|
||||
void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) override;
|
||||
void NotifyPicaRegisterChanged(u32 id) override;
|
||||
void SyncEntireState() override;
|
||||
|
||||
protected:
|
||||
/// Sync fixed-function pipeline state
|
||||
virtual void SyncFixedState() = 0;
|
||||
|
||||
/// Notifies that a fixed function PICA register changed to the video backend
|
||||
virtual void NotifyFixedFunctionPicaRegisterChanged(u32 id) = 0;
|
||||
|
||||
/// Syncs the depth scale to match the PICA register
|
||||
void SyncDepthScale();
|
||||
|
||||
/// Syncs the depth offset to match the PICA register
|
||||
void SyncDepthOffset();
|
||||
|
||||
/// Syncs the fog states to match the PICA register
|
||||
void SyncFogColor();
|
||||
|
||||
/// Sync the procedural texture noise configuration to match the PICA register
|
||||
void SyncProcTexNoise();
|
||||
|
||||
/// Sync the procedural texture bias configuration to match the PICA register
|
||||
void SyncProcTexBias();
|
||||
|
||||
/// Syncs the alpha test states to match the PICA register
|
||||
void SyncAlphaTest();
|
||||
|
||||
/// Syncs the TEV combiner color buffer to match the PICA register
|
||||
void SyncCombinerColor();
|
||||
|
||||
/// Syncs the TEV constant color to match the PICA register
|
||||
void SyncTevConstColor(std::size_t tev_index,
|
||||
const Pica::TexturingRegs::TevStageConfig& tev_stage);
|
||||
|
||||
/// Syncs the lighting global ambient color to match the PICA register
|
||||
void SyncGlobalAmbient();
|
||||
|
||||
/// Syncs the specified light's specular 0 color to match the PICA register
|
||||
void SyncLightSpecular0(int light_index);
|
||||
|
||||
/// Syncs the specified light's specular 1 color to match the PICA register
|
||||
void SyncLightSpecular1(int light_index);
|
||||
|
||||
/// Syncs the specified light's diffuse color to match the PICA register
|
||||
void SyncLightDiffuse(int light_index);
|
||||
|
||||
/// Syncs the specified light's ambient color to match the PICA register
|
||||
void SyncLightAmbient(int light_index);
|
||||
|
||||
/// Syncs the specified light's position to match the PICA register
|
||||
void SyncLightPosition(int light_index);
|
||||
|
||||
/// Syncs the specified spot light direcition to match the PICA register
|
||||
void SyncLightSpotDirection(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation bias to match the PICA register
|
||||
void SyncLightDistanceAttenuationBias(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation scale to match the PICA register
|
||||
void SyncLightDistanceAttenuationScale(int light_index);
|
||||
|
||||
/// Syncs the shadow rendering bias to match the PICA register
|
||||
void SyncShadowBias();
|
||||
|
||||
/// Syncs the shadow texture bias to match the PICA register
|
||||
void SyncShadowTextureBias();
|
||||
|
||||
/// Syncs the texture LOD bias to match the PICA register
|
||||
void SyncTextureLodBias(int tex_index);
|
||||
|
||||
/// Syncs the clip coefficients to match the PICA register
|
||||
void SyncClipCoef();
|
||||
|
||||
protected:
|
||||
/// Structure that keeps tracks of the uniform state
|
||||
struct UniformBlockData {
|
||||
Pica::Shader::UniformData data{};
|
||||
std::array<bool, Pica::LightingRegs::NumLightingSampler> lighting_lut_dirty{};
|
||||
bool lighting_lut_dirty_any = true;
|
||||
bool fog_lut_dirty = true;
|
||||
bool proctex_noise_lut_dirty = true;
|
||||
bool proctex_color_map_dirty = true;
|
||||
bool proctex_alpha_map_dirty = true;
|
||||
bool proctex_lut_dirty = true;
|
||||
bool proctex_diff_lut_dirty = true;
|
||||
bool dirty = true;
|
||||
};
|
||||
|
||||
/// Structure that the hardware rendered vertices are composed of
|
||||
struct HardwareVertex {
|
||||
HardwareVertex() = default;
|
||||
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion);
|
||||
|
||||
Common::Vec4f position;
|
||||
Common::Vec4f color;
|
||||
Common::Vec2f tex_coord0;
|
||||
Common::Vec2f tex_coord1;
|
||||
Common::Vec2f tex_coord2;
|
||||
float tex_coord0_w;
|
||||
Common::Vec4f normquat;
|
||||
Common::Vec3f view;
|
||||
};
|
||||
|
||||
struct VertexArrayInfo {
|
||||
u32 vs_input_index_min;
|
||||
u32 vs_input_index_max;
|
||||
u32 vs_input_size;
|
||||
};
|
||||
|
||||
/// Retrieve the range and the size of the input vertex
|
||||
VertexArrayInfo AnalyzeVertexArray(bool is_indexed, u32 stride_alignment = 1);
|
||||
|
||||
protected:
|
||||
Memory::MemorySystem& memory;
|
||||
Pica::Regs& regs;
|
||||
|
||||
std::vector<HardwareVertex> vertex_batch;
|
||||
bool shader_dirty = true;
|
||||
|
||||
UniformBlockData uniform_block_data{};
|
||||
std::array<std::array<Common::Vec2f, 256>, Pica::LightingRegs::NumLightingSampler>
|
||||
lighting_lut_data{};
|
||||
std::array<Common::Vec2f, 128> fog_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_noise_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_color_map_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_alpha_map_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_lut_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_diff_lut_data{};
|
||||
};
|
||||
} // namespace VideoCore
|
@ -2,15 +2,17 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/tracer/recorder.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/swrasterizer/swrasterizer.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
RendererBase::RendererBase(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window_)
|
||||
: render_window{window}, secondary_window{secondary_window_} {}
|
||||
namespace VideoCore {
|
||||
|
||||
RendererBase::RendererBase(Core::System& system_, Frontend::EmuWindow& window,
|
||||
Frontend::EmuWindow* secondary_window_)
|
||||
: system{system_}, render_window{window}, secondary_window{secondary_window_} {}
|
||||
|
||||
RendererBase::~RendererBase() = default;
|
||||
|
||||
@ -25,19 +27,35 @@ void RendererBase::UpdateCurrentFramebufferLayout(bool is_portrait_mode) {
|
||||
}
|
||||
}
|
||||
|
||||
void RendererBase::RefreshRasterizerSetting() {
|
||||
bool hw_renderer_enabled = VideoCore::g_hw_renderer_enabled;
|
||||
if (rasterizer == nullptr || opengl_rasterizer_active != hw_renderer_enabled) {
|
||||
opengl_rasterizer_active = hw_renderer_enabled;
|
||||
void RendererBase::EndFrame() {
|
||||
current_frame++;
|
||||
|
||||
if (hw_renderer_enabled) {
|
||||
rasterizer = std::make_unique<OpenGL::RasterizerOpenGL>(render_window);
|
||||
} else {
|
||||
rasterizer = std::make_unique<VideoCore::SWRasterizer>();
|
||||
}
|
||||
system.perf_stats->EndSystemFrame();
|
||||
|
||||
render_window.PollEvents();
|
||||
|
||||
system.frame_limiter.DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||
system.perf_stats->BeginSystemFrame();
|
||||
|
||||
if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
|
||||
Pica::g_debug_context->recorder->FrameFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void RendererBase::Sync() {
|
||||
rasterizer->SyncEntireState();
|
||||
bool RendererBase::IsScreenshotPending() const {
|
||||
return renderer_settings.screenshot_requested;
|
||||
}
|
||||
|
||||
void RendererBase::RequestScreenshot(void* data, std::function<void()> callback,
|
||||
const Layout::FramebufferLayout& layout) {
|
||||
if (renderer_settings.screenshot_requested) {
|
||||
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
|
||||
return;
|
||||
}
|
||||
renderer_settings.screenshot_bits = data;
|
||||
renderer_settings.screenshot_complete_callback = callback;
|
||||
renderer_settings.screenshot_framebuffer_layout = layout;
|
||||
renderer_settings.screenshot_requested = true;
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@ -4,25 +4,36 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
struct RendererSettings {
|
||||
// Screenshot
|
||||
std::atomic_bool screenshot_requested{false};
|
||||
void* screenshot_bits{};
|
||||
std::function<void()> screenshot_complete_callback;
|
||||
Layout::FramebufferLayout screenshot_framebuffer_layout;
|
||||
};
|
||||
|
||||
class RendererBase : NonCopyable {
|
||||
public:
|
||||
explicit RendererBase(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window);
|
||||
explicit RendererBase(Core::System& system, Frontend::EmuWindow& window,
|
||||
Frontend::EmuWindow* secondary_window);
|
||||
virtual ~RendererBase();
|
||||
|
||||
/// Initialize the renderer
|
||||
virtual VideoCore::ResultStatus Init() = 0;
|
||||
|
||||
/// Shutdown the renderer
|
||||
virtual void ShutDown() = 0;
|
||||
/// Returns the rasterizer owned by the renderer
|
||||
virtual VideoCore::RasterizerInterface* Rasterizer() const = 0;
|
||||
|
||||
/// Finalize rendering the guest frame and draw into the presentation texture
|
||||
virtual void SwapBuffers() = 0;
|
||||
@ -35,27 +46,29 @@ public:
|
||||
}
|
||||
|
||||
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
||||
virtual void PrepareVideoDumping() = 0;
|
||||
virtual void PrepareVideoDumping() {}
|
||||
|
||||
/// Cleans up after video dumping is ended
|
||||
virtual void CleanupVideoDumping() = 0;
|
||||
virtual void CleanupVideoDumping() {}
|
||||
|
||||
/// Synchronizes fixed function renderer state
|
||||
virtual void Sync() {}
|
||||
|
||||
/// Updates the framebuffer layout of the contained render window handle.
|
||||
void UpdateCurrentFramebufferLayout(bool is_portrait_mode = {});
|
||||
|
||||
/// Ends the current frame
|
||||
void EndFrame();
|
||||
|
||||
// Getter/setter functions:
|
||||
// ------------------------
|
||||
|
||||
f32 GetCurrentFPS() const {
|
||||
return m_current_fps;
|
||||
return current_fps;
|
||||
}
|
||||
|
||||
int GetCurrentFrame() const {
|
||||
return m_current_frame;
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface* Rasterizer() const {
|
||||
return rasterizer.get();
|
||||
return current_frame;
|
||||
}
|
||||
|
||||
Frontend::EmuWindow& GetRenderWindow() {
|
||||
@ -66,16 +79,28 @@ public:
|
||||
return render_window;
|
||||
}
|
||||
|
||||
void RefreshRasterizerSetting();
|
||||
void Sync();
|
||||
[[nodiscard]] RendererSettings& Settings() {
|
||||
return renderer_settings;
|
||||
}
|
||||
|
||||
[[nodiscard]] const RendererSettings& Settings() const {
|
||||
return renderer_settings;
|
||||
}
|
||||
|
||||
/// Returns true if a screenshot is being processed
|
||||
[[nodiscard]] bool IsScreenshotPending() const;
|
||||
|
||||
/// Request a screenshot of the next frame
|
||||
void RequestScreenshot(void* data, std::function<void()> callback,
|
||||
const Layout::FramebufferLayout& layout);
|
||||
|
||||
protected:
|
||||
Core::System& system;
|
||||
RendererSettings renderer_settings;
|
||||
Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
|
||||
Frontend::EmuWindow* secondary_window; ///< Reference to the secondary render window handle.
|
||||
std::unique_ptr<VideoCore::RasterizerInterface> rasterizer;
|
||||
f32 m_current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
||||
int m_current_frame = 0; ///< Current frame, should be set by the renderer
|
||||
|
||||
private:
|
||||
bool opengl_rasterizer_active = false;
|
||||
f32 current_fps = 0.0f; ///< Current framerate, should be set by the renderer
|
||||
int current_frame = 0; ///< Current frame, should be set by the renderer
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
|
||||
@ -39,7 +38,7 @@ void FrameDumperOpenGL::StopDumping() {
|
||||
}
|
||||
|
||||
void FrameDumperOpenGL::PresentLoop() {
|
||||
Frontend::ScopeAcquireContext scope{*context};
|
||||
const auto scope = context->Acquire();
|
||||
InitializeOpenGLObjects();
|
||||
|
||||
const auto& layout = GetLayout();
|
||||
|
156
src/video_core/renderer_opengl/gl_driver.cpp
Normal file
156
src/video_core/renderer_opengl/gl_driver.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "video_core/renderer_opengl/gl_driver.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
DECLARE_ENUM_FLAG_OPERATORS(DriverBug);
|
||||
|
||||
inline std::string_view GetSource(GLenum source) {
|
||||
#define RET(s) \
|
||||
case GL_DEBUG_SOURCE_##s: \
|
||||
return #s
|
||||
switch (source) {
|
||||
RET(API);
|
||||
RET(WINDOW_SYSTEM);
|
||||
RET(SHADER_COMPILER);
|
||||
RET(THIRD_PARTY);
|
||||
RET(APPLICATION);
|
||||
RET(OTHER);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
#undef RET
|
||||
|
||||
return std::string_view{};
|
||||
}
|
||||
|
||||
inline std::string_view GetType(GLenum type) {
|
||||
#define RET(t) \
|
||||
case GL_DEBUG_TYPE_##t: \
|
||||
return #t
|
||||
switch (type) {
|
||||
RET(ERROR);
|
||||
RET(DEPRECATED_BEHAVIOR);
|
||||
RET(UNDEFINED_BEHAVIOR);
|
||||
RET(PORTABILITY);
|
||||
RET(PERFORMANCE);
|
||||
RET(OTHER);
|
||||
RET(MARKER);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
#undef RET
|
||||
|
||||
return std::string_view{};
|
||||
}
|
||||
|
||||
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
|
||||
GLsizei length, const GLchar* message, const void* user_param) {
|
||||
Log::Level level = Log::Level::Info;
|
||||
switch (severity) {
|
||||
case GL_DEBUG_SEVERITY_HIGH:
|
||||
level = Log::Level::Critical;
|
||||
break;
|
||||
case GL_DEBUG_SEVERITY_MEDIUM:
|
||||
level = Log::Level::Warning;
|
||||
break;
|
||||
case GL_DEBUG_SEVERITY_NOTIFICATION:
|
||||
case GL_DEBUG_SEVERITY_LOW:
|
||||
level = Log::Level::Debug;
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type),
|
||||
id, message);
|
||||
}
|
||||
|
||||
Driver::Driver(Core::TelemetrySession& telemetry_session_) : telemetry_session{telemetry_session_} {
|
||||
const bool enable_debug = Settings::values.renderer_debug.GetValue();
|
||||
if (enable_debug) {
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glDebugMessageCallback(DebugHandler, nullptr);
|
||||
}
|
||||
|
||||
ReportDriverInfo();
|
||||
DeduceVendor();
|
||||
CheckExtensionSupport();
|
||||
FindBugs();
|
||||
}
|
||||
|
||||
Driver::~Driver() = default;
|
||||
|
||||
bool Driver::HasBug(DriverBug bug) const {
|
||||
return True(bugs & bug);
|
||||
}
|
||||
|
||||
void Driver::ReportDriverInfo() {
|
||||
// Report the context version and the vendor string
|
||||
gl_version = std::string_view{reinterpret_cast<const char*>(glGetString(GL_VERSION))};
|
||||
gpu_vendor = std::string_view{reinterpret_cast<const char*>(glGetString(GL_VENDOR))};
|
||||
gpu_model = std::string_view{reinterpret_cast<const char*>(glGetString(GL_RENDERER))};
|
||||
|
||||
LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
|
||||
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
|
||||
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
|
||||
|
||||
// Add the information to the telemetry system
|
||||
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
|
||||
telemetry_session.AddField(user_system, "GPU_Vendor", std::string{gpu_vendor});
|
||||
telemetry_session.AddField(user_system, "GPU_Model", std::string{gpu_model});
|
||||
telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string{gl_version});
|
||||
}
|
||||
|
||||
void Driver::DeduceVendor() {
|
||||
if (gpu_vendor.find("NVIDIA") != gpu_vendor.npos) {
|
||||
vendor = Vendor::Nvidia;
|
||||
} else if ((gpu_vendor.find("ATI") != gpu_vendor.npos) ||
|
||||
(gpu_vendor.find("AMD") != gpu_vendor.npos) ||
|
||||
(gpu_vendor.find("Advanced Micro Devices") != gpu_vendor.npos)) {
|
||||
vendor = Vendor::AMD;
|
||||
} else if (gpu_vendor.find("Intel") != gpu_vendor.npos) {
|
||||
vendor = Vendor::Intel;
|
||||
} else if (gpu_vendor.find("ARM") != gpu_vendor.npos) {
|
||||
vendor = Vendor::ARM;
|
||||
} else if (gpu_vendor.find("Qualcomm") != gpu_vendor.npos) {
|
||||
vendor = Vendor::Qualcomm;
|
||||
} else if (gpu_vendor.find("Samsung") != gpu_vendor.npos) {
|
||||
vendor = Vendor::Samsung;
|
||||
} else if (gpu_vendor.find("GDI Generic") != gpu_vendor.npos) {
|
||||
vendor = Vendor::Generic;
|
||||
}
|
||||
}
|
||||
|
||||
void Driver::CheckExtensionSupport() {
|
||||
ext_buffer_storage = GLAD_GL_EXT_buffer_storage;
|
||||
arb_buffer_storage = GLAD_GL_ARB_buffer_storage;
|
||||
arb_clear_texture = GLAD_GL_ARB_clear_texture;
|
||||
arb_get_texture_sub_image = GLAD_GL_ARB_get_texture_sub_image;
|
||||
ext_clip_cull_distance = GLAD_GL_EXT_clip_cull_distance;
|
||||
is_suitable = GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1;
|
||||
}
|
||||
|
||||
void Driver::FindBugs() {
|
||||
#ifdef __unix__
|
||||
const bool is_linux = true;
|
||||
#else
|
||||
const bool is_linux = false;
|
||||
#endif
|
||||
|
||||
// TODO: Check if these have been fixed in the newer driver
|
||||
if (vendor == Vendor::AMD) {
|
||||
bugs |= DriverBug::ShaderStageChangeFreeze | DriverBug::VertexArrayOutOfBound;
|
||||
}
|
||||
|
||||
if (vendor == Vendor::AMD || (vendor == Vendor::Intel && !is_linux)) {
|
||||
bugs |= DriverBug::BrokenTextureView;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
114
src/video_core/renderer_opengl/gl_driver.h
Normal file
114
src/video_core/renderer_opengl/gl_driver.h
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
class TelemetrySession;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
enum class Vendor {
|
||||
Unknown = 0,
|
||||
AMD = 1,
|
||||
Nvidia = 2,
|
||||
Intel = 3,
|
||||
ARM = 4,
|
||||
Qualcomm = 5,
|
||||
Samsung = 6,
|
||||
Generic = 7,
|
||||
};
|
||||
|
||||
enum class DriverBug {
|
||||
// AMD drivers sometimes freezes when one shader stage is changed but not the others.
|
||||
ShaderStageChangeFreeze = 1 << 0,
|
||||
// On AMD drivers there is a strange crash in indexed drawing. The crash happens when the buffer
|
||||
// read position is near the end and is an out-of-bound access to the vertex buffer. This is
|
||||
// probably a bug in the driver and is related to the usage of vec3<byte> attributes in the
|
||||
// vertex array. Doubling the allocation size for the vertex buffer seems to avoid the crash.
|
||||
VertexArrayOutOfBound = 1 << 1,
|
||||
// On AMD and Intel drivers on Windows glTextureView produces incorrect results
|
||||
BrokenTextureView = 1 << 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility class that loads the OpenGL function pointers and reports
|
||||
* information about the graphics device and driver used
|
||||
*/
|
||||
class Driver {
|
||||
public:
|
||||
Driver(Core::TelemetrySession& telemetry_session);
|
||||
~Driver();
|
||||
|
||||
/// Returns true of the driver has a particular bug stated in the DriverBug enum
|
||||
bool HasBug(DriverBug bug) const;
|
||||
|
||||
/// Returns the vendor of the currently selected physical device
|
||||
Vendor GetVendor() const {
|
||||
return vendor;
|
||||
}
|
||||
|
||||
/// Returns the gpu vendor string returned by the driver
|
||||
std::string_view GetVendorString() const {
|
||||
return gpu_vendor;
|
||||
}
|
||||
|
||||
/// Returns true if the implementation is suitable for emulation
|
||||
bool IsSuitable() const {
|
||||
return is_suitable;
|
||||
}
|
||||
|
||||
/// Returns true if the implementation supports ARB_buffer_storage
|
||||
bool HasArbBufferStorage() const {
|
||||
return arb_buffer_storage;
|
||||
}
|
||||
|
||||
/// Returns true if the implementation supports EXT_buffer_storage
|
||||
bool HasExtBufferStorage() const {
|
||||
return ext_buffer_storage;
|
||||
}
|
||||
|
||||
/// Returns true if the implementation supports ARB_clear_texture
|
||||
bool HasArbClearTexture() const {
|
||||
return arb_clear_texture;
|
||||
}
|
||||
|
||||
/// Returns true if the implementation supports ARB_get_texture_sub_image
|
||||
bool HasArbGetTextureSubImage() const {
|
||||
return arb_get_texture_sub_image;
|
||||
}
|
||||
|
||||
/// Returns true if the implementation supports EXT_clip_cull_distance
|
||||
bool HasExtClipCullDistance() const {
|
||||
return ext_clip_cull_distance;
|
||||
}
|
||||
|
||||
private:
|
||||
void ReportDriverInfo();
|
||||
void DeduceVendor();
|
||||
void CheckExtensionSupport();
|
||||
void FindBugs();
|
||||
|
||||
private:
|
||||
Core::TelemetrySession& telemetry_session;
|
||||
Vendor vendor = Vendor::Unknown;
|
||||
DriverBug bugs{};
|
||||
bool is_suitable{};
|
||||
|
||||
bool ext_buffer_storage{};
|
||||
bool arb_buffer_storage{};
|
||||
bool arb_clear_texture{};
|
||||
bool arb_get_texture_sub_image{};
|
||||
bool ext_clip_cull_distance{};
|
||||
|
||||
std::string_view gl_version{};
|
||||
std::string_view gpu_vendor{};
|
||||
std::string_view gpu_model{};
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
File diff suppressed because it is too large
Load Diff
@ -3,12 +3,11 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include "common/vector_math.h"
|
||||
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/pica_types.h"
|
||||
#include "video_core/rasterizer_accelerated.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
@ -20,20 +19,20 @@ class EmuWindow;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class Driver;
|
||||
class ShaderProgramManager;
|
||||
|
||||
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
|
||||
class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
|
||||
public:
|
||||
explicit RasterizerOpenGL(Frontend::EmuWindow& emu_window);
|
||||
explicit RasterizerOpenGL(Memory::MemorySystem& memory, Frontend::EmuWindow& emu_window,
|
||||
Driver& driver);
|
||||
~RasterizerOpenGL() override;
|
||||
|
||||
void LoadDiskResources(const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) override;
|
||||
|
||||
void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) override;
|
||||
void DrawTriangles() override;
|
||||
void NotifyPicaRegisterChanged(u32 id) override;
|
||||
void FlushAll() override;
|
||||
void FlushRegion(PAddr addr, u32 size) override;
|
||||
void InvalidateRegion(PAddr addr, u32 size) override;
|
||||
@ -46,10 +45,10 @@ public:
|
||||
u32 pixel_stride, ScreenInfo& screen_info) override;
|
||||
bool AccelerateDrawBatch(bool is_indexed) override;
|
||||
|
||||
/// Syncs entire status to match PICA registers
|
||||
void SyncEntireState() override;
|
||||
|
||||
private:
|
||||
void SyncFixedState() override;
|
||||
void NotifyFixedFunctionPicaRegisterChanged(u32 id) override;
|
||||
|
||||
struct SamplerInfo {
|
||||
using TextureConfig = Pica::TexturingRegs::TextureConfig;
|
||||
|
||||
@ -76,66 +75,15 @@ private:
|
||||
bool supress_mipmap_for_cube = false;
|
||||
};
|
||||
|
||||
/// Structure that the hardware rendered vertices are composed of
|
||||
struct HardwareVertex {
|
||||
HardwareVertex() = default;
|
||||
HardwareVertex(const Pica::Shader::OutputVertex& v, bool flip_quaternion) {
|
||||
position[0] = v.pos.x.ToFloat32();
|
||||
position[1] = v.pos.y.ToFloat32();
|
||||
position[2] = v.pos.z.ToFloat32();
|
||||
position[3] = v.pos.w.ToFloat32();
|
||||
color[0] = v.color.x.ToFloat32();
|
||||
color[1] = v.color.y.ToFloat32();
|
||||
color[2] = v.color.z.ToFloat32();
|
||||
color[3] = v.color.w.ToFloat32();
|
||||
tex_coord0[0] = v.tc0.x.ToFloat32();
|
||||
tex_coord0[1] = v.tc0.y.ToFloat32();
|
||||
tex_coord1[0] = v.tc1.x.ToFloat32();
|
||||
tex_coord1[1] = v.tc1.y.ToFloat32();
|
||||
tex_coord2[0] = v.tc2.x.ToFloat32();
|
||||
tex_coord2[1] = v.tc2.y.ToFloat32();
|
||||
tex_coord0_w = v.tc0_w.ToFloat32();
|
||||
normquat[0] = v.quat.x.ToFloat32();
|
||||
normquat[1] = v.quat.y.ToFloat32();
|
||||
normquat[2] = v.quat.z.ToFloat32();
|
||||
normquat[3] = v.quat.w.ToFloat32();
|
||||
view[0] = v.view.x.ToFloat32();
|
||||
view[1] = v.view.y.ToFloat32();
|
||||
view[2] = v.view.z.ToFloat32();
|
||||
|
||||
if (flip_quaternion) {
|
||||
normquat = -normquat;
|
||||
}
|
||||
}
|
||||
|
||||
Common::Vec4f position;
|
||||
Common::Vec4f color;
|
||||
Common::Vec2f tex_coord0;
|
||||
Common::Vec2f tex_coord1;
|
||||
Common::Vec2f tex_coord2;
|
||||
float tex_coord0_w;
|
||||
Common::Vec4f normquat;
|
||||
Common::Vec3f view;
|
||||
};
|
||||
|
||||
/// Syncs the clip enabled status to match the PICA register
|
||||
void SyncClipEnabled();
|
||||
|
||||
/// Syncs the clip coefficients to match the PICA register
|
||||
void SyncClipCoef();
|
||||
|
||||
/// Sets the OpenGL shader in accordance with the current PICA register state
|
||||
void SetShader();
|
||||
|
||||
/// Syncs the cull mode to match the PICA register
|
||||
void SyncCullMode();
|
||||
|
||||
/// Syncs the depth scale to match the PICA register
|
||||
void SyncDepthScale();
|
||||
|
||||
/// Syncs the depth offset to match the PICA register
|
||||
void SyncDepthOffset();
|
||||
|
||||
/// Syncs the blend enabled status to match the PICA register
|
||||
void SyncBlendEnabled();
|
||||
|
||||
@ -145,18 +93,6 @@ private:
|
||||
/// Syncs the blend color to match the PICA register
|
||||
void SyncBlendColor();
|
||||
|
||||
/// Syncs the fog states to match the PICA register
|
||||
void SyncFogColor();
|
||||
|
||||
/// Sync the procedural texture noise configuration to match the PICA register
|
||||
void SyncProcTexNoise();
|
||||
|
||||
/// Sync the procedural texture bias configuration to match the PICA register
|
||||
void SyncProcTexBias();
|
||||
|
||||
/// Syncs the alpha test states to match the PICA register
|
||||
void SyncAlphaTest();
|
||||
|
||||
/// Syncs the logic op states to match the PICA register
|
||||
void SyncLogicOp();
|
||||
|
||||
@ -175,46 +111,6 @@ private:
|
||||
/// Syncs the depth test states to match the PICA register
|
||||
void SyncDepthTest();
|
||||
|
||||
/// Syncs the TEV combiner color buffer to match the PICA register
|
||||
void SyncCombinerColor();
|
||||
|
||||
/// Syncs the TEV constant color to match the PICA register
|
||||
void SyncTevConstColor(std::size_t tev_index,
|
||||
const Pica::TexturingRegs::TevStageConfig& tev_stage);
|
||||
|
||||
/// Syncs the lighting global ambient color to match the PICA register
|
||||
void SyncGlobalAmbient();
|
||||
|
||||
/// Syncs the specified light's specular 0 color to match the PICA register
|
||||
void SyncLightSpecular0(int light_index);
|
||||
|
||||
/// Syncs the specified light's specular 1 color to match the PICA register
|
||||
void SyncLightSpecular1(int light_index);
|
||||
|
||||
/// Syncs the specified light's diffuse color to match the PICA register
|
||||
void SyncLightDiffuse(int light_index);
|
||||
|
||||
/// Syncs the specified light's ambient color to match the PICA register
|
||||
void SyncLightAmbient(int light_index);
|
||||
|
||||
/// Syncs the specified light's position to match the PICA register
|
||||
void SyncLightPosition(int light_index);
|
||||
|
||||
/// Syncs the specified spot light direcition to match the PICA register
|
||||
void SyncLightSpotDirection(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation bias to match the PICA register
|
||||
void SyncLightDistanceAttenuationBias(int light_index);
|
||||
|
||||
/// Syncs the specified light's distance attenuation scale to match the PICA register
|
||||
void SyncLightDistanceAttenuationScale(int light_index);
|
||||
|
||||
/// Syncs the shadow rendering bias to match the PICA register
|
||||
void SyncShadowBias();
|
||||
|
||||
/// Syncs the shadow texture bias to match the PICA register
|
||||
void SyncShadowTextureBias();
|
||||
|
||||
/// Syncs and uploads the lighting, fog and proctex LUTs
|
||||
void SyncAndUploadLUTs();
|
||||
void SyncAndUploadLUTsLF();
|
||||
@ -228,15 +124,6 @@ private:
|
||||
/// Internal implementation for AccelerateDrawBatch
|
||||
bool AccelerateDrawBatchInternal(bool is_indexed);
|
||||
|
||||
struct VertexArrayInfo {
|
||||
u32 vs_input_index_min;
|
||||
u32 vs_input_index_max;
|
||||
u32 vs_input_size;
|
||||
};
|
||||
|
||||
/// Retrieve the range and the size of the input vertex
|
||||
VertexArrayInfo AnalyzeVertexArray(bool is_indexed);
|
||||
|
||||
/// Setup vertex array for AccelerateDrawBatch
|
||||
void SetupVertexArray(u8* array_ptr, GLintptr buffer_offset, GLuint vs_input_index_min,
|
||||
GLuint vs_input_index_max);
|
||||
@ -247,38 +134,13 @@ private:
|
||||
/// Setup geometry shader for AccelerateDrawBatch
|
||||
bool SetupGeometryShader();
|
||||
|
||||
bool is_amd;
|
||||
|
||||
private:
|
||||
Driver& driver;
|
||||
OpenGLState state;
|
||||
GLuint default_texture;
|
||||
|
||||
RasterizerCacheOpenGL res_cache;
|
||||
|
||||
std::vector<HardwareVertex> vertex_batch;
|
||||
|
||||
bool shader_dirty = true;
|
||||
|
||||
struct {
|
||||
UniformData data;
|
||||
std::array<bool, Pica::LightingRegs::NumLightingSampler> lighting_lut_dirty;
|
||||
bool lighting_lut_dirty_any;
|
||||
bool fog_lut_dirty;
|
||||
bool proctex_noise_lut_dirty;
|
||||
bool proctex_color_map_dirty;
|
||||
bool proctex_alpha_map_dirty;
|
||||
bool proctex_lut_dirty;
|
||||
bool proctex_diff_lut_dirty;
|
||||
bool dirty;
|
||||
} uniform_block_data = {};
|
||||
|
||||
std::unique_ptr<ShaderProgramManager> shader_program_manager;
|
||||
|
||||
// They shall be big enough for about one frame.
|
||||
static constexpr std::size_t VERTEX_BUFFER_SIZE = 16 * 1024 * 1024;
|
||||
static constexpr std::size_t INDEX_BUFFER_SIZE = 1 * 1024 * 1024;
|
||||
static constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
|
||||
static constexpr std::size_t TEXTURE_BUFFER_SIZE = 1 * 1024 * 1024;
|
||||
|
||||
OGLVertexArray sw_vao; // VAO for software shader draw
|
||||
OGLVertexArray hw_vao; // VAO for hardware shader / accelerate draw
|
||||
std::array<bool, 16> hw_vao_enabled_attributes{};
|
||||
@ -299,15 +161,6 @@ private:
|
||||
OGLTexture texture_buffer_lut_lf;
|
||||
OGLTexture texture_buffer_lut_rg;
|
||||
OGLTexture texture_buffer_lut_rgba;
|
||||
|
||||
std::array<std::array<Common::Vec2f, 256>, Pica::LightingRegs::NumLightingSampler>
|
||||
lighting_lut_data{};
|
||||
std::array<Common::Vec2f, 128> fog_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_noise_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_color_map_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_alpha_map_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_lut_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_diff_lut_data{};
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/shader/shader_uniforms.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
using Pica::FramebufferRegs;
|
||||
@ -23,53 +24,7 @@ using VSOutputAttributes = RasterizerRegs::VSOutputAttributes;
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
constexpr std::string_view UniformBlockDef = R"(
|
||||
#define NUM_TEV_STAGES 6
|
||||
#define NUM_LIGHTS 8
|
||||
#define NUM_LIGHTING_SAMPLERS 24
|
||||
|
||||
struct LightSrc {
|
||||
vec3 specular_0;
|
||||
vec3 specular_1;
|
||||
vec3 diffuse;
|
||||
vec3 ambient;
|
||||
vec3 position;
|
||||
vec3 spot_direction;
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
};
|
||||
|
||||
layout (std140) uniform shader_data {
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
float depth_offset;
|
||||
float shadow_bias_constant;
|
||||
float shadow_bias_linear;
|
||||
int scissor_x1;
|
||||
int scissor_y1;
|
||||
int scissor_x2;
|
||||
int scissor_y2;
|
||||
int fog_lut_offset;
|
||||
int proctex_noise_lut_offset;
|
||||
int proctex_color_map_offset;
|
||||
int proctex_alpha_map_offset;
|
||||
int proctex_lut_offset;
|
||||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
ivec4 lighting_lut_offset[NUM_LIGHTING_SAMPLERS / 4];
|
||||
vec3 fog_color;
|
||||
vec2 proctex_noise_f;
|
||||
vec2 proctex_noise_a;
|
||||
vec2 proctex_noise_p;
|
||||
vec3 lighting_global_ambient;
|
||||
LightSrc light_src[NUM_LIGHTS];
|
||||
vec4 const_color[NUM_TEV_STAGES];
|
||||
vec4 tev_combiner_buffer_color;
|
||||
vec4 clip_coef;
|
||||
};
|
||||
)";
|
||||
const std::string UniformBlockDef = Pica::Shader::BuildShaderUniformDefinitions();
|
||||
|
||||
static std::string GetVertexInterfaceDeclaration(bool is_output, bool separable_shader) {
|
||||
std::string out;
|
||||
|
@ -6,13 +6,13 @@
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <boost/variant.hpp>
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include <variant>
|
||||
#include "video_core/renderer_opengl/gl_driver.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/shader/shader_uniforms.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace OpenGL {
|
||||
@ -85,7 +85,8 @@ static std::tuple<PicaVSConfig, Pica::Shader::ShaderSetup> BuildVSConfigFromRaw(
|
||||
return {PicaVSConfig{raw.GetRawShaderConfig().vs, setup}, setup};
|
||||
}
|
||||
|
||||
static void SetShaderUniformBlockBinding(GLuint shader, const char* name, UniformBindings binding,
|
||||
static void SetShaderUniformBlockBinding(GLuint shader, const char* name,
|
||||
Pica::Shader::UniformBindings binding,
|
||||
std::size_t expected_size) {
|
||||
const GLuint ub_index = glGetUniformBlockIndex(shader, name);
|
||||
if (ub_index == GL_INVALID_INDEX) {
|
||||
@ -100,9 +101,10 @@ static void SetShaderUniformBlockBinding(GLuint shader, const char* name, Unifor
|
||||
}
|
||||
|
||||
static void SetShaderUniformBlockBindings(GLuint shader) {
|
||||
SetShaderUniformBlockBinding(shader, "shader_data", UniformBindings::Common,
|
||||
sizeof(UniformData));
|
||||
SetShaderUniformBlockBinding(shader, "vs_config", UniformBindings::VS, sizeof(VSUniformData));
|
||||
SetShaderUniformBlockBinding(shader, "shader_data", Pica::Shader::UniformBindings::Common,
|
||||
sizeof(Pica::Shader::UniformData));
|
||||
SetShaderUniformBlockBinding(shader, "vs_config", Pica::Shader::UniformBindings::VS,
|
||||
sizeof(Pica::Shader::VSUniformData));
|
||||
}
|
||||
|
||||
static void SetShaderSamplerBinding(GLuint shader, const char* name,
|
||||
@ -148,21 +150,6 @@ static void SetShaderSamplerBindings(GLuint shader) {
|
||||
cur_state.Apply();
|
||||
}
|
||||
|
||||
void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs,
|
||||
const Pica::Shader::ShaderSetup& setup) {
|
||||
std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools),
|
||||
[](bool value) -> BoolAligned { return {value ? GL_TRUE : GL_FALSE}; });
|
||||
std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i),
|
||||
[](const auto& value) -> Common::Vec4u {
|
||||
return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()};
|
||||
});
|
||||
std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f),
|
||||
[](const auto& value) -> Common::Vec4f {
|
||||
return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(),
|
||||
value.w.ToFloat32()};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An object representing a shader program staging. It can be either a shader object or a program
|
||||
* object, depending on whether separable program is used.
|
||||
@ -178,12 +165,12 @@ public:
|
||||
}
|
||||
|
||||
void Create(const char* source, GLenum type) {
|
||||
if (shader_or_program.which() == 0) {
|
||||
boost::get<OGLShader>(shader_or_program).Create(source, type);
|
||||
if (shader_or_program.index() == 0) {
|
||||
std::get<OGLShader>(shader_or_program).Create(source, type);
|
||||
} else {
|
||||
OGLShader shader;
|
||||
shader.Create(source, type);
|
||||
OGLProgram& program = boost::get<OGLProgram>(shader_or_program);
|
||||
OGLProgram& program = std::get<OGLProgram>(shader_or_program);
|
||||
program.Create(true, {shader.handle});
|
||||
SetShaderUniformBlockBindings(program.handle);
|
||||
|
||||
@ -194,10 +181,10 @@ public:
|
||||
}
|
||||
|
||||
GLuint GetHandle() const {
|
||||
if (shader_or_program.which() == 0) {
|
||||
return boost::get<OGLShader>(shader_or_program).handle;
|
||||
if (shader_or_program.index() == 0) {
|
||||
return std::get<OGLShader>(shader_or_program).handle;
|
||||
} else {
|
||||
return boost::get<OGLProgram>(shader_or_program).handle;
|
||||
return std::get<OGLProgram>(shader_or_program).handle;
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,7 +195,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
boost::variant<OGLShader, OGLProgram> shader_or_program;
|
||||
std::variant<OGLShader, OGLProgram> shader_or_program;
|
||||
};
|
||||
|
||||
class TrivialVertexShader {
|
||||
@ -329,8 +316,8 @@ using FragmentShaders = ShaderCache<PicaFSConfig, &GenerateFragmentShader, GL_FR
|
||||
|
||||
class ShaderProgramManager::Impl {
|
||||
public:
|
||||
explicit Impl(bool separable, bool is_amd)
|
||||
: is_amd(is_amd), separable(separable), programmable_vertex_shaders(separable),
|
||||
explicit Impl(bool separable)
|
||||
: separable(separable), programmable_vertex_shaders(separable),
|
||||
trivial_vertex_shader(separable), fixed_geometry_shaders(separable),
|
||||
fragment_shaders(separable), disk_cache(separable) {
|
||||
if (separable)
|
||||
@ -363,7 +350,6 @@ public:
|
||||
static_assert(offsetof(ShaderTuple, fs_hash) == sizeof(std::size_t) * 2,
|
||||
"ShaderTuple layout changed!");
|
||||
|
||||
bool is_amd;
|
||||
bool separable;
|
||||
|
||||
ShaderTuple current;
|
||||
@ -379,9 +365,9 @@ public:
|
||||
ShaderDiskCache disk_cache;
|
||||
};
|
||||
|
||||
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable,
|
||||
bool is_amd)
|
||||
: impl(std::make_unique<Impl>(separable, is_amd)), emu_window{emu_window_} {}
|
||||
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, const Driver& driver_,
|
||||
bool separable)
|
||||
: impl(std::make_unique<Impl>(separable)), emu_window{emu_window_}, driver{driver_} {}
|
||||
|
||||
ShaderProgramManager::~ShaderProgramManager() = default;
|
||||
|
||||
@ -443,10 +429,7 @@ void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) {
|
||||
|
||||
void ShaderProgramManager::ApplyTo(OpenGLState& state) {
|
||||
if (impl->separable) {
|
||||
if (impl->is_amd) {
|
||||
// Without this reseting, AMD sometimes freezes when one stage is changed but not
|
||||
// for the others. On the other hand, including this reset seems to introduce memory
|
||||
// leak in Intel Graphics.
|
||||
if (driver.HasBug(DriverBug::ShaderStageChangeFreeze)) {
|
||||
glUseProgramStages(
|
||||
impl->pipeline.handle,
|
||||
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0);
|
||||
@ -641,7 +624,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
std::size_t built_shaders = 0; // It doesn't have be atomic since it's used behind a mutex
|
||||
const auto LoadRawSepareble = [&](Frontend::GraphicsContext* context, std::size_t begin,
|
||||
std::size_t end) {
|
||||
Frontend::ScopeAcquireContext scope(*context);
|
||||
const auto scope = context->Acquire();
|
||||
for (std::size_t i = begin; i < end; ++i) {
|
||||
if (stop_loading || compilation_failed) {
|
||||
return;
|
||||
|
@ -5,13 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
@ -19,8 +13,7 @@ class EmuWindow;
|
||||
|
||||
namespace Pica {
|
||||
struct Regs;
|
||||
struct ShaderRegs;
|
||||
} // namespace Pica
|
||||
}
|
||||
|
||||
namespace Pica::Shader {
|
||||
struct ShaderSetup;
|
||||
@ -28,87 +21,13 @@ struct ShaderSetup;
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
enum class UniformBindings : u32 { Common, VS, GS };
|
||||
|
||||
struct LightSrc {
|
||||
alignas(16) Common::Vec3f specular_0;
|
||||
alignas(16) Common::Vec3f specular_1;
|
||||
alignas(16) Common::Vec3f diffuse;
|
||||
alignas(16) Common::Vec3f ambient;
|
||||
alignas(16) Common::Vec3f position;
|
||||
alignas(16) Common::Vec3f spot_direction; // negated
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
};
|
||||
|
||||
/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
|
||||
// NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at
|
||||
// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
||||
// Not following that rule will cause problems on some AMD drivers.
|
||||
struct UniformData {
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
float depth_offset;
|
||||
float shadow_bias_constant;
|
||||
float shadow_bias_linear;
|
||||
int scissor_x1;
|
||||
int scissor_y1;
|
||||
int scissor_x2;
|
||||
int scissor_y2;
|
||||
int fog_lut_offset;
|
||||
int proctex_noise_lut_offset;
|
||||
int proctex_color_map_offset;
|
||||
int proctex_alpha_map_offset;
|
||||
int proctex_lut_offset;
|
||||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
alignas(16) Common::Vec4i lighting_lut_offset[Pica::LightingRegs::NumLightingSampler / 4];
|
||||
alignas(16) Common::Vec3f fog_color;
|
||||
alignas(8) Common::Vec2f proctex_noise_f;
|
||||
alignas(8) Common::Vec2f proctex_noise_a;
|
||||
alignas(8) Common::Vec2f proctex_noise_p;
|
||||
alignas(16) Common::Vec3f lighting_global_ambient;
|
||||
LightSrc light_src[8];
|
||||
alignas(16) Common::Vec4f const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) Common::Vec4f tev_combiner_buffer_color;
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
};
|
||||
|
||||
static_assert(sizeof(UniformData) == 0x4F0,
|
||||
"The size of the UniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384,
|
||||
"UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/// Uniform struct for the Uniform Buffer Object that contains PICA vertex/geometry shader uniforms.
|
||||
// NOTE: the same rule from UniformData also applies here.
|
||||
struct PicaUniformsData {
|
||||
void SetFromRegs(const Pica::ShaderRegs& regs, const Pica::Shader::ShaderSetup& setup);
|
||||
|
||||
struct BoolAligned {
|
||||
alignas(16) int b;
|
||||
};
|
||||
|
||||
std::array<BoolAligned, 16> bools;
|
||||
alignas(16) std::array<Common::Vec4u, 4> i;
|
||||
alignas(16) std::array<Common::Vec4f, 96> f;
|
||||
};
|
||||
|
||||
struct VSUniformData {
|
||||
PicaUniformsData uniforms;
|
||||
};
|
||||
static_assert(sizeof(VSUniformData) == 1856,
|
||||
"The size of the VSUniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(VSUniformData) < 16384,
|
||||
"VSUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
class Driver;
|
||||
class OpenGLState;
|
||||
|
||||
/// A class that manage different shader stages and configures them with given config data.
|
||||
class ShaderProgramManager {
|
||||
public:
|
||||
ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable, bool is_amd);
|
||||
ShaderProgramManager(Frontend::EmuWindow& emu_window, const Driver& driver, bool separable);
|
||||
~ShaderProgramManager();
|
||||
|
||||
void LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
@ -131,5 +50,6 @@ private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
|
||||
Frontend::EmuWindow& emu_window;
|
||||
const Driver& driver;
|
||||
};
|
||||
} // namespace OpenGL
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_opengl/gl_driver.h"
|
||||
#include "video_core/renderer_opengl/gl_stream_buffer.h"
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
|
||||
@ -12,19 +13,14 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool array_buffer_for_amd,
|
||||
OGLStreamBuffer::OGLStreamBuffer(Driver& driver, GLenum target, GLsizeiptr size,
|
||||
bool prefer_coherent)
|
||||
: gl_target(target), buffer_size(size) {
|
||||
gl_buffer.Create();
|
||||
glBindBuffer(gl_target, gl_buffer.handle);
|
||||
|
||||
GLsizeiptr allocate_size = size;
|
||||
if (array_buffer_for_amd) {
|
||||
// On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer
|
||||
// read position is near the end and is an out-of-bound access to the vertex buffer. This is
|
||||
// probably a bug in the driver and is related to the usage of vec3<byte> attributes in the
|
||||
// vertex array. Doubling the allocation size for the vertex buffer seems to avoid the
|
||||
// crash.
|
||||
if (driver.HasBug(DriverBug::VertexArrayOutOfBound) && target == GL_ARRAY_BUFFER) {
|
||||
allocate_size *= 2;
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,17 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class Driver;
|
||||
|
||||
class OGLStreamBuffer : private NonCopyable {
|
||||
public:
|
||||
explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool array_buffer_for_amd,
|
||||
explicit OGLStreamBuffer(Driver& driver, GLenum target, GLsizeiptr size,
|
||||
bool prefer_coherent = false);
|
||||
~OGLStreamBuffer();
|
||||
|
||||
|
@ -13,8 +13,6 @@
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hw/lcd.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/tracer/recorder.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
@ -352,14 +350,17 @@ static std::array<GLfloat, 3 * 2> MakeOrthographicMatrix(const float width, cons
|
||||
return matrix;
|
||||
}
|
||||
|
||||
RendererOpenGL::RendererOpenGL(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window)
|
||||
: RendererBase{window, secondary_window},
|
||||
frame_dumper(Core::System::GetInstance().VideoDumper(), window) {
|
||||
RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window,
|
||||
Frontend::EmuWindow* secondary_window)
|
||||
: VideoCore::RendererBase{system, window, secondary_window}, driver{system.TelemetrySession()},
|
||||
frame_dumper{system.VideoDumper(), window} {
|
||||
window.mailbox = std::make_unique<OGLTextureMailbox>();
|
||||
if (secondary_window) {
|
||||
secondary_window->mailbox = std::make_unique<OGLTextureMailbox>();
|
||||
}
|
||||
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
|
||||
InitOpenGLObjects();
|
||||
rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), render_window, driver);
|
||||
}
|
||||
|
||||
RendererOpenGL::~RendererOpenGL() = default;
|
||||
@ -374,7 +375,6 @@ void RendererOpenGL::SwapBuffers() {
|
||||
state.Apply();
|
||||
|
||||
PrepareRendertarget();
|
||||
|
||||
RenderScreenshot();
|
||||
|
||||
const auto& main_layout = render_window.GetFramebufferLayout();
|
||||
@ -396,26 +396,12 @@ void RendererOpenGL::SwapBuffers() {
|
||||
}
|
||||
}
|
||||
|
||||
m_current_frame++;
|
||||
|
||||
Core::System::GetInstance().perf_stats->EndSystemFrame();
|
||||
|
||||
render_window.PollEvents();
|
||||
|
||||
Core::System::GetInstance().frame_limiter.DoFrameLimiting(
|
||||
Core::System::GetInstance().CoreTiming().GetGlobalTimeUs());
|
||||
Core::System::GetInstance().perf_stats->BeginSystemFrame();
|
||||
|
||||
EndFrame();
|
||||
prev_state.Apply();
|
||||
RefreshRasterizerSetting();
|
||||
|
||||
if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
|
||||
Pica::g_debug_context->recorder->FrameFinished();
|
||||
}
|
||||
}
|
||||
|
||||
void RendererOpenGL::RenderScreenshot() {
|
||||
if (VideoCore::g_renderer_screenshot_requested) {
|
||||
if (renderer_settings.screenshot_requested.exchange(false)) {
|
||||
// Draw this frame to the screenshot framebuffer
|
||||
screenshot_framebuffer.Create();
|
||||
GLuint old_read_fb = state.draw.read_framebuffer;
|
||||
@ -423,7 +409,7 @@ void RendererOpenGL::RenderScreenshot() {
|
||||
state.draw.read_framebuffer = state.draw.draw_framebuffer = screenshot_framebuffer.handle;
|
||||
state.Apply();
|
||||
|
||||
Layout::FramebufferLayout layout{VideoCore::g_screenshot_framebuffer_layout};
|
||||
const auto layout{renderer_settings.screenshot_framebuffer_layout};
|
||||
|
||||
GLuint renderbuffer;
|
||||
glGenRenderbuffers(1, &renderbuffer);
|
||||
@ -435,7 +421,7 @@ void RendererOpenGL::RenderScreenshot() {
|
||||
DrawScreens(layout, false);
|
||||
|
||||
glReadPixels(0, 0, layout.width, layout.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
VideoCore::g_screenshot_bits);
|
||||
renderer_settings.screenshot_bits);
|
||||
|
||||
screenshot_framebuffer.Release();
|
||||
state.draw.read_framebuffer = old_read_fb;
|
||||
@ -443,8 +429,7 @@ void RendererOpenGL::RenderScreenshot() {
|
||||
state.Apply();
|
||||
glDeleteRenderbuffers(1, &renderbuffer);
|
||||
|
||||
VideoCore::g_screenshot_complete_callback();
|
||||
VideoCore::g_renderer_screenshot_requested = false;
|
||||
renderer_settings.screenshot_complete_callback();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1226,109 +1211,8 @@ void RendererOpenGL::CleanupVideoDumping() {
|
||||
mailbox->free_cv.notify_one();
|
||||
}
|
||||
|
||||
static const char* GetSource(GLenum source) {
|
||||
#define RET(s) \
|
||||
case GL_DEBUG_SOURCE_##s: \
|
||||
return #s
|
||||
switch (source) {
|
||||
RET(API);
|
||||
RET(WINDOW_SYSTEM);
|
||||
RET(SHADER_COMPILER);
|
||||
RET(THIRD_PARTY);
|
||||
RET(APPLICATION);
|
||||
RET(OTHER);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
void RendererOpenGL::Sync() {
|
||||
rasterizer->SyncEntireState();
|
||||
}
|
||||
#undef RET
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char* GetType(GLenum type) {
|
||||
#define RET(t) \
|
||||
case GL_DEBUG_TYPE_##t: \
|
||||
return #t
|
||||
switch (type) {
|
||||
RET(ERROR);
|
||||
RET(DEPRECATED_BEHAVIOR);
|
||||
RET(UNDEFINED_BEHAVIOR);
|
||||
RET(PORTABILITY);
|
||||
RET(PERFORMANCE);
|
||||
RET(OTHER);
|
||||
RET(MARKER);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
#undef RET
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
|
||||
GLsizei length, const GLchar* message, const void* user_param) {
|
||||
Log::Level level;
|
||||
switch (severity) {
|
||||
case GL_DEBUG_SEVERITY_HIGH:
|
||||
level = Log::Level::Critical;
|
||||
break;
|
||||
case GL_DEBUG_SEVERITY_MEDIUM:
|
||||
level = Log::Level::Warning;
|
||||
break;
|
||||
case GL_DEBUG_SEVERITY_NOTIFICATION:
|
||||
case GL_DEBUG_SEVERITY_LOW:
|
||||
level = Log::Level::Debug;
|
||||
break;
|
||||
}
|
||||
LOG_GENERIC(Log::Class::Render_OpenGL, level, "{} {} {}: {}", GetSource(source), GetType(type),
|
||||
id, message);
|
||||
}
|
||||
|
||||
/// Initialize the renderer
|
||||
VideoCore::ResultStatus RendererOpenGL::Init() {
|
||||
#ifndef ANDROID
|
||||
if (!gladLoadGL()) {
|
||||
return VideoCore::ResultStatus::ErrorBelowGL43;
|
||||
}
|
||||
|
||||
// Qualcomm has some spammy info messages that are marked as errors but not important
|
||||
// https://developer.qualcomm.com/comment/11845
|
||||
if (GLAD_GL_KHR_debug) {
|
||||
glEnable(GL_DEBUG_OUTPUT);
|
||||
glDebugMessageCallback(DebugHandler, nullptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string_view gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
|
||||
const std::string_view gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
const std::string_view gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
|
||||
|
||||
LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
|
||||
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
|
||||
LOG_INFO(Render_OpenGL, "GL_RENDERER: {}", gpu_model);
|
||||
|
||||
auto& telemetry_session = Core::System::GetInstance().TelemetrySession();
|
||||
constexpr auto user_system = Common::Telemetry::FieldType::UserSystem;
|
||||
telemetry_session.AddField(user_system, "GPU_Vendor", std::string(gpu_vendor));
|
||||
telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model));
|
||||
telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
|
||||
|
||||
if (gpu_vendor == "GDI Generic") {
|
||||
return VideoCore::ResultStatus::ErrorGenericDrivers;
|
||||
}
|
||||
|
||||
if (!(GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1)) {
|
||||
return VideoCore::ResultStatus::ErrorBelowGL43;
|
||||
}
|
||||
|
||||
InitOpenGLObjects();
|
||||
|
||||
RefreshRasterizerSetting();
|
||||
|
||||
return VideoCore::ResultStatus::Success;
|
||||
}
|
||||
|
||||
/// Shutdown the renderer
|
||||
void RendererOpenGL::ShutDown() {}
|
||||
|
||||
} // namespace OpenGL
|
||||
|
@ -8,6 +8,8 @@
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
#include "video_core/renderer_opengl/gl_driver.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
@ -15,6 +17,10 @@ namespace Layout {
|
||||
struct FramebufferLayout;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
struct Frame {
|
||||
@ -48,35 +54,21 @@ struct ScreenInfo {
|
||||
TextureInfo texture;
|
||||
};
|
||||
|
||||
struct PresentationTexture {
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
OGLTexture texture;
|
||||
};
|
||||
|
||||
class RendererOpenGL : public RendererBase {
|
||||
class RendererOpenGL : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererOpenGL(Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window);
|
||||
explicit RendererOpenGL(Core::System& system, Frontend::EmuWindow& window,
|
||||
Frontend::EmuWindow* secondary_window);
|
||||
~RendererOpenGL() override;
|
||||
|
||||
/// Initialize the renderer
|
||||
VideoCore::ResultStatus Init() override;
|
||||
[[nodiscard]] VideoCore::RasterizerInterface* Rasterizer() const override {
|
||||
return rasterizer.get();
|
||||
}
|
||||
|
||||
/// Shutdown the renderer
|
||||
void ShutDown() override;
|
||||
|
||||
/// Finalizes rendering the guest frame
|
||||
void SwapBuffers() override;
|
||||
|
||||
/// Draws the latest frame from texture mailbox to the currently bound draw framebuffer in this
|
||||
/// context
|
||||
void TryPresent(int timeout_ms, bool is_secondary) override;
|
||||
|
||||
/// Prepares for video dumping (e.g. create necessary buffers, etc)
|
||||
void PrepareVideoDumping() override;
|
||||
|
||||
/// Cleans up after video dumping is ended
|
||||
void CleanupVideoDumping() override;
|
||||
void Sync() override;
|
||||
|
||||
private:
|
||||
void InitOpenGLObjects();
|
||||
@ -111,7 +103,10 @@ private:
|
||||
// Fills active OpenGL texture with the given RGB color.
|
||||
void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture);
|
||||
|
||||
private:
|
||||
Driver driver;
|
||||
OpenGLState state;
|
||||
std::unique_ptr<RasterizerOpenGL> rasterizer;
|
||||
|
||||
// OpenGL object IDs
|
||||
OGLVertexArray vertex_array;
|
||||
|
@ -22,12 +22,12 @@
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_rasterizer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_software/rasterizer.h"
|
||||
#include "video_core/renderer_software/sw_framebuffer.h"
|
||||
#include "video_core/renderer_software/sw_lighting.h"
|
||||
#include "video_core/renderer_software/sw_proctex.h"
|
||||
#include "video_core/renderer_software/sw_texturing.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
#include "video_core/swrasterizer/framebuffer.h"
|
||||
#include "video_core/swrasterizer/lighting.h"
|
||||
#include "video_core/swrasterizer/proctex.h"
|
||||
#include "video_core/swrasterizer/rasterizer.h"
|
||||
#include "video_core/swrasterizer/texturing.h"
|
||||
#include "video_core/texture/texture_decode.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "video_core/video_core.h"
|
19
src/video_core/renderer_software/renderer_software.cpp
Normal file
19
src/video_core/renderer_software/renderer_software.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_software/renderer_software.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
RendererSoftware::RendererSoftware(Core::System& system, Frontend::EmuWindow& window)
|
||||
: VideoCore::RendererBase{system, window, nullptr},
|
||||
rasterizer{std::make_unique<RasterizerSoftware>()} {}
|
||||
|
||||
RendererSoftware::~RendererSoftware() = default;
|
||||
|
||||
void RendererSoftware::SwapBuffers() {
|
||||
EndFrame();
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
33
src/video_core/renderer_software/renderer_software.h
Normal file
33
src/video_core/renderer_software/renderer_software.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_software/sw_rasterizer.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class RendererSoftware : public VideoCore::RendererBase {
|
||||
public:
|
||||
explicit RendererSoftware(Core::System& system, Frontend::EmuWindow& window);
|
||||
~RendererSoftware() override;
|
||||
|
||||
[[nodiscard]] VideoCore::RasterizerInterface* Rasterizer() const override {
|
||||
return rasterizer.get();
|
||||
}
|
||||
|
||||
void SwapBuffers() override;
|
||||
void TryPresent(int timeout_ms, bool is_secondary) override {}
|
||||
void Sync() override {}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RasterizerSoftware> rasterizer;
|
||||
};
|
||||
|
||||
} // namespace VideoCore
|
@ -12,9 +12,9 @@
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/pica_types.h"
|
||||
#include "video_core/renderer_software/rasterizer.h"
|
||||
#include "video_core/renderer_software/sw_clipper.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
#include "video_core/swrasterizer/clipper.h"
|
||||
#include "video_core/swrasterizer/rasterizer.h"
|
||||
|
||||
using Pica::Rasterizer::Vertex;
|
||||
|
@ -12,7 +12,7 @@
|
||||
#include "core/memory.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/swrasterizer/framebuffer.h"
|
||||
#include "video_core/renderer_software/sw_framebuffer.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
@ -3,7 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "video_core/swrasterizer/lighting.h"
|
||||
#include "video_core/renderer_software/sw_lighting.h"
|
||||
|
||||
namespace Pica {
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/swrasterizer/proctex.h"
|
||||
#include "video_core/renderer_software/sw_proctex.h"
|
||||
|
||||
namespace Pica::Rasterizer {
|
||||
|
16
src/video_core/renderer_software/sw_rasterizer.cpp
Normal file
16
src/video_core/renderer_software/sw_rasterizer.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_software/sw_clipper.h"
|
||||
#include "video_core/renderer_software/sw_rasterizer.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
void RasterizerSoftware::AddTriangle(const Pica::Shader::OutputVertex& v0,
|
||||
const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) {
|
||||
Pica::Clipper::ProcessTriangle(v0, v1, v2);
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
@ -13,7 +13,7 @@ struct OutputVertex;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class SWRasterizer : public RasterizerInterface {
|
||||
class RasterizerSoftware : public RasterizerInterface {
|
||||
void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) override;
|
||||
void DrawTriangles() override {}
|
@ -7,7 +7,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/swrasterizer/texturing.h"
|
||||
#include "video_core/renderer_software/sw_texturing.h"
|
||||
|
||||
namespace Pica::Rasterizer {
|
||||
|
@ -86,7 +86,7 @@ void UnitState::LoadInput(const ShaderRegs& config, const AttributeBuffer& input
|
||||
}
|
||||
}
|
||||
|
||||
static void CopyRegistersToOutput(const Common::Vec4<float24>* regs, u32 mask,
|
||||
static void CopyRegistersToOutput(std::span<Common::Vec4<float24>, 16> regs, u32 mask,
|
||||
AttributeBuffer& buffer) {
|
||||
int output_i = 0;
|
||||
for (int reg : Common::BitSet<u32>(mask)) {
|
||||
@ -108,7 +108,7 @@ GSEmitter::~GSEmitter() {
|
||||
delete handlers;
|
||||
}
|
||||
|
||||
void GSEmitter::Emit(Common::Vec4<float24> (&output_regs)[16]) {
|
||||
void GSEmitter::Emit(std::span<Common::Vec4<float24>, 16> output_regs) {
|
||||
ASSERT(vertex_id < 3);
|
||||
// TODO: This should be merged with UnitState::WriteOutput somehow
|
||||
CopyRegistersToOutput(output_regs, output_mask, buffer[vertex_id]);
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
#include <boost/serialization/access.hpp>
|
||||
#include <boost/serialization/array.hpp>
|
||||
@ -113,7 +114,7 @@ struct GSEmitter {
|
||||
|
||||
GSEmitter();
|
||||
~GSEmitter();
|
||||
void Emit(Common::Vec4<float24> (&output_regs)[16]);
|
||||
void Emit(std::span<Common::Vec4<float24>, 16> output_regs);
|
||||
|
||||
private:
|
||||
friend class boost::serialization::access;
|
||||
@ -140,9 +141,9 @@ struct UnitState {
|
||||
struct Registers {
|
||||
// The registers are accessed by the shader JIT using SSE instructions, and are therefore
|
||||
// required to be 16-byte aligned.
|
||||
alignas(16) Common::Vec4<float24> input[16];
|
||||
alignas(16) Common::Vec4<float24> temporary[16];
|
||||
alignas(16) Common::Vec4<float24> output[16];
|
||||
alignas(16) std::array<Common::Vec4<float24>, 16> input;
|
||||
alignas(16) std::array<Common::Vec4<float24>, 16> temporary;
|
||||
alignas(16) std::array<Common::Vec4<float24>, 16> output;
|
||||
|
||||
private:
|
||||
friend class boost::serialization::access;
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <boost/range/algorithm/fill.hpp>
|
||||
#include <nihstro/shader_bytecode.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
@ -688,7 +687,7 @@ DebugData<true> InterpreterEngine::ProduceDebugInfo(const ShaderSetup& setup,
|
||||
DebugData<true> debug_data;
|
||||
|
||||
// Setup input register table
|
||||
boost::fill(state.registers.input, Common::Vec4<float24>::AssignToAll(float24::Zero()));
|
||||
state.registers.input.fill(Common::Vec4<float24>::AssignToAll(float24::Zero()));
|
||||
state.LoadInput(config, input);
|
||||
RunInterpreter(setup, state, debug_data, setup.engine_data.entry_point);
|
||||
return debug_data;
|
||||
|
78
src/video_core/shader/shader_uniforms.cpp
Normal file
78
src/video_core/shader/shader_uniforms.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include "video_core/shader/shader.h"
|
||||
#include "video_core/shader/shader_uniforms.h"
|
||||
|
||||
namespace Pica::Shader {
|
||||
|
||||
void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs,
|
||||
const Pica::Shader::ShaderSetup& setup) {
|
||||
std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools),
|
||||
[](bool value) -> BoolAligned { return {value ? 1 : 0}; });
|
||||
std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i),
|
||||
[](const auto& value) -> Common::Vec4u {
|
||||
return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()};
|
||||
});
|
||||
std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f),
|
||||
[](const auto& value) -> Common::Vec4f {
|
||||
return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(),
|
||||
value.w.ToFloat32()};
|
||||
});
|
||||
}
|
||||
|
||||
constexpr std::string_view UniformBlockDefFormat = R"(
|
||||
#define NUM_TEV_STAGES 6
|
||||
#define NUM_LIGHTS 8
|
||||
#define NUM_LIGHTING_SAMPLERS 24
|
||||
struct LightSrc {{
|
||||
vec3 specular_0;
|
||||
vec3 specular_1;
|
||||
vec3 diffuse;
|
||||
vec3 ambient;
|
||||
vec3 position;
|
||||
vec3 spot_direction;
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
}};
|
||||
layout ({}std140) uniform shader_data {{
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
float depth_offset;
|
||||
float shadow_bias_constant;
|
||||
float shadow_bias_linear;
|
||||
int scissor_x1;
|
||||
int scissor_y1;
|
||||
int scissor_x2;
|
||||
int scissor_y2;
|
||||
int fog_lut_offset;
|
||||
int proctex_noise_lut_offset;
|
||||
int proctex_color_map_offset;
|
||||
int proctex_alpha_map_offset;
|
||||
int proctex_lut_offset;
|
||||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
bool enable_clip1;
|
||||
ivec4 lighting_lut_offset[NUM_LIGHTING_SAMPLERS / 4];
|
||||
vec3 fog_color;
|
||||
vec2 proctex_noise_f;
|
||||
vec2 proctex_noise_a;
|
||||
vec2 proctex_noise_p;
|
||||
vec3 lighting_global_ambient;
|
||||
LightSrc light_src[NUM_LIGHTS];
|
||||
vec4 const_color[NUM_TEV_STAGES];
|
||||
vec4 tev_combiner_buffer_color;
|
||||
vec3 tex_lod_bias;
|
||||
vec4 clip_coef;
|
||||
}};
|
||||
)";
|
||||
|
||||
std::string BuildShaderUniformDefinitions(const std::string& extra_layout_parameters) {
|
||||
return fmt::format(UniformBlockDefFormat, extra_layout_parameters);
|
||||
}
|
||||
|
||||
} // namespace Pica::Shader
|
101
src/video_core/shader/shader_uniforms.h
Normal file
101
src/video_core/shader/shader_uniforms.h
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
|
||||
namespace Pica {
|
||||
struct ShaderRegs;
|
||||
}
|
||||
|
||||
namespace Pica::Shader {
|
||||
|
||||
struct ShaderSetup;
|
||||
|
||||
enum class UniformBindings : u32 { Common, VS, GS };
|
||||
|
||||
struct LightSrc {
|
||||
alignas(16) Common::Vec3f specular_0;
|
||||
alignas(16) Common::Vec3f specular_1;
|
||||
alignas(16) Common::Vec3f diffuse;
|
||||
alignas(16) Common::Vec3f ambient;
|
||||
alignas(16) Common::Vec3f position;
|
||||
alignas(16) Common::Vec3f spot_direction; // negated
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
};
|
||||
|
||||
/**
|
||||
* Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
|
||||
* NOTE: Always keep a vec4 at the end. The GL spec is not clear wether the alignment at
|
||||
* the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
||||
* Not following that rule will cause problems on some AMD drivers.
|
||||
*/
|
||||
struct UniformData {
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
float depth_offset;
|
||||
float shadow_bias_constant;
|
||||
float shadow_bias_linear;
|
||||
int scissor_x1;
|
||||
int scissor_y1;
|
||||
int scissor_x2;
|
||||
int scissor_y2;
|
||||
int fog_lut_offset;
|
||||
int proctex_noise_lut_offset;
|
||||
int proctex_color_map_offset;
|
||||
int proctex_alpha_map_offset;
|
||||
int proctex_lut_offset;
|
||||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
alignas(4) bool enable_clip1;
|
||||
alignas(16) Common::Vec4i lighting_lut_offset[LightingRegs::NumLightingSampler / 4];
|
||||
alignas(16) Common::Vec3f fog_color;
|
||||
alignas(8) Common::Vec2f proctex_noise_f;
|
||||
alignas(8) Common::Vec2f proctex_noise_a;
|
||||
alignas(8) Common::Vec2f proctex_noise_p;
|
||||
alignas(16) Common::Vec3f lighting_global_ambient;
|
||||
LightSrc light_src[8];
|
||||
alignas(16) Common::Vec4f const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) Common::Vec4f tev_combiner_buffer_color;
|
||||
alignas(16) Common::Vec3f tex_lod_bias;
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
};
|
||||
|
||||
static_assert(sizeof(UniformData) == 0x500,
|
||||
"The size of the UniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384,
|
||||
"UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
/**
|
||||
* Uniform struct for the Uniform Buffer Object that contains PICA vertex/geometry shader uniforms.
|
||||
* NOTE: the same rule from UniformData also applies here.
|
||||
*/
|
||||
struct PicaUniformsData {
|
||||
void SetFromRegs(const ShaderRegs& regs, const ShaderSetup& setup);
|
||||
|
||||
struct BoolAligned {
|
||||
alignas(16) int b;
|
||||
};
|
||||
|
||||
std::array<BoolAligned, 16> bools;
|
||||
alignas(16) std::array<Common::Vec4u, 4> i;
|
||||
alignas(16) std::array<Common::Vec4f, 96> f;
|
||||
};
|
||||
|
||||
struct VSUniformData {
|
||||
PicaUniformsData uniforms;
|
||||
};
|
||||
static_assert(sizeof(VSUniformData) == 1856,
|
||||
"The size of the VSUniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(VSUniformData) < 16384,
|
||||
"VSUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
std::string BuildShaderUniformDefinitions(const std::string& extra_layout_parameters = "");
|
||||
|
||||
} // namespace Pica::Shader
|
@ -1,16 +0,0 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/swrasterizer/clipper.h"
|
||||
#include "video_core/swrasterizer/swrasterizer.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
void SWRasterizer::AddTriangle(const Pica::Shader::OutputVertex& v0,
|
||||
const Pica::Shader::OutputVertex& v1,
|
||||
const Pica::Shader::OutputVertex& v2) {
|
||||
Pica::Clipper::ProcessTriangle(v0, v1, v2);
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
@ -1,5 +1,4 @@
|
||||
#include <memory>
|
||||
#include <boost/range/algorithm/fill.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
@ -23,7 +22,7 @@ void VertexLoader::Setup(const PipelineRegs& regs) {
|
||||
const auto& attribute_config = regs.vertex_attributes;
|
||||
num_total_attributes = attribute_config.GetNumTotalAttributes();
|
||||
|
||||
boost::fill(vertex_attribute_sources, 0xdeadbeef);
|
||||
vertex_attribute_sources.fill(0xdeadbeef);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
vertex_attribute_is_default[i] = attribute_config.IsDefaultAttribute(i);
|
||||
|
@ -6,11 +6,13 @@
|
||||
#include "common/archives.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/pica.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/renderer_opengl.h"
|
||||
#include "video_core/renderer_software/renderer_software.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -18,9 +20,8 @@
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
|
||||
std::unique_ptr<RendererBase> g_renderer{}; ///< Renderer plugin
|
||||
|
||||
std::atomic<bool> g_hw_renderer_enabled;
|
||||
std::atomic<bool> g_shader_jit_enabled;
|
||||
std::atomic<bool> g_hw_shader_enabled;
|
||||
std::atomic<bool> g_separable_shader_enabled;
|
||||
@ -30,65 +31,49 @@ std::atomic<bool> g_renderer_bg_color_update_requested;
|
||||
std::atomic<bool> g_renderer_sampler_update_requested;
|
||||
std::atomic<bool> g_renderer_shader_update_requested;
|
||||
std::atomic<bool> g_texture_filter_update_requested;
|
||||
// Screenshot
|
||||
std::atomic<bool> g_renderer_screenshot_requested;
|
||||
void* g_screenshot_bits;
|
||||
std::function<void()> g_screenshot_complete_callback;
|
||||
Layout::FramebufferLayout g_screenshot_framebuffer_layout;
|
||||
|
||||
Memory::MemorySystem* g_memory;
|
||||
|
||||
/// Initialize the video core
|
||||
ResultStatus Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window,
|
||||
Memory::MemorySystem& memory) {
|
||||
g_memory = &memory;
|
||||
void Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window,
|
||||
Core::System& system) {
|
||||
g_memory = &system.Memory();
|
||||
Pica::Init();
|
||||
|
||||
const Settings::GraphicsAPI graphics_api = Settings::values.graphics_api.GetValue();
|
||||
OpenGL::GLES = Settings::values.use_gles.GetValue();
|
||||
|
||||
g_renderer = std::make_unique<OpenGL::RendererOpenGL>(emu_window, secondary_window);
|
||||
ResultStatus result = g_renderer->Init();
|
||||
|
||||
if (result != ResultStatus::Success) {
|
||||
LOG_ERROR(Render, "initialization failed !");
|
||||
} else {
|
||||
LOG_DEBUG(Render, "initialized OK");
|
||||
switch (graphics_api) {
|
||||
case Settings::GraphicsAPI::Software:
|
||||
g_renderer = std::make_unique<VideoCore::RendererSoftware>(system, emu_window);
|
||||
break;
|
||||
case Settings::GraphicsAPI::OpenGL:
|
||||
g_renderer = std::make_unique<OpenGL::RendererOpenGL>(system, emu_window, secondary_window);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Render, "Unknown graphics API {}, using OpenGL", graphics_api);
|
||||
g_renderer = std::make_unique<OpenGL::RendererOpenGL>(system, emu_window, secondary_window);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Shutdown the video core
|
||||
void Shutdown() {
|
||||
Pica::Shutdown();
|
||||
|
||||
g_renderer->ShutDown();
|
||||
g_renderer.reset();
|
||||
|
||||
LOG_DEBUG(Render, "shutdown OK");
|
||||
}
|
||||
|
||||
void RequestScreenshot(void* data, std::function<void()> callback,
|
||||
const Layout::FramebufferLayout& layout) {
|
||||
if (g_renderer_screenshot_requested) {
|
||||
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
|
||||
return;
|
||||
}
|
||||
g_screenshot_bits = data;
|
||||
g_screenshot_complete_callback = std::move(callback);
|
||||
g_screenshot_framebuffer_layout = layout;
|
||||
g_renderer_screenshot_requested = true;
|
||||
}
|
||||
|
||||
u16 GetResolutionScaleFactor() {
|
||||
if (g_hw_renderer_enabled) {
|
||||
return Settings::values.resolution_factor.GetValue()
|
||||
? Settings::values.resolution_factor.GetValue()
|
||||
: g_renderer->GetRenderWindow().GetFramebufferLayout().GetScalingRatio();
|
||||
} else {
|
||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||
if (graphics_api == Settings::GraphicsAPI::Software) {
|
||||
// Software renderer always render at native resolution
|
||||
return 1;
|
||||
}
|
||||
|
||||
return Settings::values.resolution_factor.GetValue()
|
||||
? Settings::values.resolution_factor.GetValue()
|
||||
: g_renderer->GetRenderWindow().GetFramebufferLayout().GetScalingRatio();
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
|
@ -14,7 +14,9 @@ namespace Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
class RendererBase;
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Memory {
|
||||
class MemorySystem;
|
||||
@ -25,11 +27,12 @@ class MemorySystem;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class RendererBase;
|
||||
|
||||
extern std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
|
||||
|
||||
// TODO: Wrap these in a user settings struct along with any other graphics settings (often set from
|
||||
// qt ui)
|
||||
extern std::atomic<bool> g_hw_renderer_enabled;
|
||||
extern std::atomic<bool> g_shader_jit_enabled;
|
||||
extern std::atomic<bool> g_hw_shader_enabled;
|
||||
extern std::atomic<bool> g_separable_shader_enabled;
|
||||
@ -39,31 +42,16 @@ extern std::atomic<bool> g_renderer_bg_color_update_requested;
|
||||
extern std::atomic<bool> g_renderer_sampler_update_requested;
|
||||
extern std::atomic<bool> g_renderer_shader_update_requested;
|
||||
extern std::atomic<bool> g_texture_filter_update_requested;
|
||||
// Screenshot
|
||||
extern std::atomic<bool> g_renderer_screenshot_requested;
|
||||
extern void* g_screenshot_bits;
|
||||
extern std::function<void()> g_screenshot_complete_callback;
|
||||
extern Layout::FramebufferLayout g_screenshot_framebuffer_layout;
|
||||
|
||||
extern Memory::MemorySystem* g_memory;
|
||||
|
||||
enum class ResultStatus {
|
||||
Success,
|
||||
ErrorGenericDrivers,
|
||||
ErrorBelowGL43,
|
||||
};
|
||||
|
||||
/// Initialize the video core
|
||||
ResultStatus Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window,
|
||||
Memory::MemorySystem& memory);
|
||||
void Init(Frontend::EmuWindow& emu_window, Frontend::EmuWindow* secondary_window,
|
||||
Core::System& system);
|
||||
|
||||
/// Shutdown the video core
|
||||
void Shutdown();
|
||||
|
||||
/// Request a screenshot of the next frame
|
||||
void RequestScreenshot(void* data, std::function<void()> callback,
|
||||
const Layout::FramebufferLayout& layout);
|
||||
|
||||
u16 GetResolutionScaleFactor();
|
||||
|
||||
template <class Archive>
|
||||
|
Loading…
Reference in New Issue
Block a user