summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/scope_exit.h2
-rw-r--r--src/common/swap.h96
-rw-r--r--src/core/frontend/emu_window.h39
-rw-r--r--src/core/hle/kernel/thread.cpp5
-rw-r--r--src/core/hle/kernel/thread.h6
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp15
-rw-r--r--src/core/hle/service/ldr/ldr.cpp150
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp16
-rw-r--r--src/core/settings.h1
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp24
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp34
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h61
-rw-r--r--src/video_core/surface.cpp2
-rw-r--r--src/yuzu/bootmanager.cpp208
-rw-r--r--src/yuzu/bootmanager.h29
-rw-r--r--src/yuzu/configuration/config.cpp10
-rw-r--r--src/yuzu/configuration/configure_general.cpp4
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp3
-rw-r--r--src/yuzu/configuration/configure_graphics.ui7
-rw-r--r--src/yuzu/game_list.cpp5
-rw-r--r--src/yuzu/main.cpp39
-rw-r--r--src/yuzu/main.h4
-rw-r--r--src/yuzu/ui_settings.h4
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp37
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h2
25 files changed, 489 insertions, 314 deletions
diff --git a/src/common/scope_exit.h b/src/common/scope_exit.h
index baf1f1c9e..1176a72b1 100644
--- a/src/common/scope_exit.h
+++ b/src/common/scope_exit.h
@@ -20,7 +20,7 @@ struct ScopeExitHelper {
20 20
21template <typename Func> 21template <typename Func>
22ScopeExitHelper<Func> ScopeExit(Func&& func) { 22ScopeExitHelper<Func> ScopeExit(Func&& func) {
23 return ScopeExitHelper<Func>(std::move(func)); 23 return ScopeExitHelper<Func>(std::forward<Func>(func));
24} 24}
25} // namespace detail 25} // namespace detail
26 26
diff --git a/src/common/swap.h b/src/common/swap.h
index b3eab1324..71932c2bb 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -21,11 +21,6 @@
21 21
22#if defined(_MSC_VER) 22#if defined(_MSC_VER)
23#include <cstdlib> 23#include <cstdlib>
24#elif defined(__linux__)
25#include <byteswap.h>
26#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || \
27 defined(__NetBSD__) || defined(__OpenBSD__)
28#include <sys/endian.h>
29#endif 24#endif
30#include <cstring> 25#include <cstring>
31#include "common/common_types.h" 26#include "common/common_types.h"
@@ -62,86 +57,49 @@
62namespace Common { 57namespace Common {
63 58
64#ifdef _MSC_VER 59#ifdef _MSC_VER
65inline u16 swap16(u16 _data) { 60[[nodiscard]] inline u16 swap16(u16 data) noexcept {
66 return _byteswap_ushort(_data); 61 return _byteswap_ushort(data);
67} 62}
68inline u32 swap32(u32 _data) { 63[[nodiscard]] inline u32 swap32(u32 data) noexcept {
69 return _byteswap_ulong(_data); 64 return _byteswap_ulong(data);
70} 65}
71inline u64 swap64(u64 _data) { 66[[nodiscard]] inline u64 swap64(u64 data) noexcept {
72 return _byteswap_uint64(_data); 67 return _byteswap_uint64(data);
73} 68}
74#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6) 69#elif defined(__clang__) || defined(__GNUC__)
75inline u16 swap16(u16 _data) { 70#if defined(__Bitrig__) || defined(__OpenBSD__)
76 u32 data = _data;
77 __asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
78 return (u16)data;
79}
80inline u32 swap32(u32 _data) {
81 __asm__("rev %0, %1\n" : "=l"(_data) : "l"(_data));
82 return _data;
83}
84inline u64 swap64(u64 _data) {
85 return ((u64)swap32(_data) << 32) | swap32(_data >> 32);
86}
87#elif __linux__
88inline u16 swap16(u16 _data) {
89 return bswap_16(_data);
90}
91inline u32 swap32(u32 _data) {
92 return bswap_32(_data);
93}
94inline u64 swap64(u64 _data) {
95 return bswap_64(_data);
96}
97#elif __APPLE__
98inline __attribute__((always_inline)) u16 swap16(u16 _data) {
99 return (_data >> 8) | (_data << 8);
100}
101inline __attribute__((always_inline)) u32 swap32(u32 _data) {
102 return __builtin_bswap32(_data);
103}
104inline __attribute__((always_inline)) u64 swap64(u64 _data) {
105 return __builtin_bswap64(_data);
106}
107#elif defined(__Bitrig__) || defined(__OpenBSD__)
108// redefine swap16, swap32, swap64 as inline functions 71// redefine swap16, swap32, swap64 as inline functions
109#undef swap16 72#undef swap16
110#undef swap32 73#undef swap32
111#undef swap64 74#undef swap64
112inline u16 swap16(u16 _data) { 75#endif
113 return __swap16(_data); 76[[nodiscard]] inline u16 swap16(u16 data) noexcept {
114} 77 return __builtin_bswap16(data);
115inline u32 swap32(u32 _data) {
116 return __swap32(_data);
117}
118inline u64 swap64(u64 _data) {
119 return __swap64(_data);
120}
121#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
122inline u16 swap16(u16 _data) {
123 return bswap16(_data);
124} 78}
125inline u32 swap32(u32 _data) { 79[[nodiscard]] inline u32 swap32(u32 data) noexcept {
126 return bswap32(_data); 80 return __builtin_bswap32(data);
127} 81}
128inline u64 swap64(u64 _data) { 82[[nodiscard]] inline u64 swap64(u64 data) noexcept {
129 return bswap64(_data); 83 return __builtin_bswap64(data);
130} 84}
131#else 85#else
132// Slow generic implementation. 86// Generic implementation.
133inline u16 swap16(u16 data) { 87[[nodiscard]] inline u16 swap16(u16 data) noexcept {
134 return (data >> 8) | (data << 8); 88 return (data >> 8) | (data << 8);
135} 89}
136inline u32 swap32(u32 data) { 90[[nodiscard]] inline u32 swap32(u32 data) noexcept {
137 return (swap16(data) << 16) | swap16(data >> 16); 91 return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) |
92 ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
138} 93}
139inline u64 swap64(u64 data) { 94[[nodiscard]] inline u64 swap64(u64 data) noexcept {
140 return ((u64)swap32(data) << 32) | swap32(data >> 32); 95 return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
96 ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
97 ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
98 ((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
141} 99}
142#endif 100#endif
143 101
144inline float swapf(float f) { 102[[nodiscard]] inline float swapf(float f) noexcept {
145 static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t."); 103 static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
146 104
147 u32 value; 105 u32 value;
@@ -153,7 +111,7 @@ inline float swapf(float f) {
153 return f; 111 return f;
154} 112}
155 113
156inline double swapd(double f) { 114[[nodiscard]] inline double swapd(double f) noexcept {
157 static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t."); 115 static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
158 116
159 u64 value; 117 u64 value;
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index d0bcb4660..70a522556 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -13,6 +13,23 @@
13namespace Core::Frontend { 13namespace Core::Frontend {
14 14
15/** 15/**
16 * Represents a graphics context that can be used for background computation or drawing. If the
17 * graphics backend doesn't require the context, then the implementation of these methods can be
18 * stubs
19 */
20class GraphicsContext {
21public:
22 /// Makes the graphics context current for the caller thread
23 virtual void MakeCurrent() = 0;
24
25 /// Releases (dunno if this is the "right" word) the context from the caller thread
26 virtual void DoneCurrent() = 0;
27
28 /// Swap buffers to display the next frame
29 virtual void SwapBuffers() = 0;
30};
31
32/**
16 * Abstraction class used to provide an interface between emulation code and the frontend 33 * Abstraction class used to provide an interface between emulation code and the frontend
17 * (e.g. SDL, QGLWidget, GLFW, etc...). 34 * (e.g. SDL, QGLWidget, GLFW, etc...).
18 * 35 *
@@ -30,7 +47,7 @@ namespace Core::Frontend {
30 * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please 47 * - DO NOT TREAT THIS CLASS AS A GUI TOOLKIT ABSTRACTION LAYER. That's not what it is. Please
31 * re-read the upper points again and think about it if you don't see this. 48 * re-read the upper points again and think about it if you don't see this.
32 */ 49 */
33class EmuWindow { 50class EmuWindow : public GraphicsContext {
34public: 51public:
35 /// Data structure to store emuwindow configuration 52 /// Data structure to store emuwindow configuration
36 struct WindowConfig { 53 struct WindowConfig {
@@ -40,17 +57,21 @@ public:
40 std::pair<unsigned, unsigned> min_client_area_size; 57 std::pair<unsigned, unsigned> min_client_area_size;
41 }; 58 };
42 59
43 /// Swap buffers to display the next frame
44 virtual void SwapBuffers() = 0;
45
46 /// Polls window events 60 /// Polls window events
47 virtual void PollEvents() = 0; 61 virtual void PollEvents() = 0;
48 62
49 /// Makes the graphics context current for the caller thread 63 /**
50 virtual void MakeCurrent() = 0; 64 * Returns a GraphicsContext that the frontend provides that is shared with the emu window. This
51 65 * context can be used from other threads for background graphics computation. If the frontend
52 /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread 66 * is using a graphics backend that doesn't need anything specific to run on a different thread,
53 virtual void DoneCurrent() = 0; 67 * then it can use a stubbed implemenation for GraphicsContext.
68 *
69 * If the return value is null, then the core should assume that the frontend cannot provide a
70 * Shared Context
71 */
72 virtual std::unique_ptr<GraphicsContext> CreateSharedContext() const {
73 return nullptr;
74 }
54 75
55 /** 76 /**
56 * Signal that a touch pressed event has occurred (e.g. mouse click pressed) 77 * Signal that a touch pressed event has occurred (e.g. mouse click pressed)
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 1b891f632..ca52267b2 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -220,11 +220,6 @@ void Thread::SetPriority(u32 priority) {
220 UpdatePriority(); 220 UpdatePriority();
221} 221}
222 222
223void Thread::BoostPriority(u32 priority) {
224 scheduler->SetThreadPriority(this, priority);
225 current_priority = priority;
226}
227
228void Thread::SetWaitSynchronizationResult(ResultCode result) { 223void Thread::SetWaitSynchronizationResult(ResultCode result) {
229 context.cpu_registers[0] = result.raw; 224 context.cpu_registers[0] = result.raw;
230} 225}
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index 83c83e45a..32026d7f0 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -136,12 +136,6 @@ public:
136 */ 136 */
137 void SetPriority(u32 priority); 137 void SetPriority(u32 priority);
138 138
139 /**
140 * Temporarily boosts the thread's priority until the next time it is scheduled
141 * @param priority The new priority
142 */
143 void BoostPriority(u32 priority);
144
145 /// Adds a thread to the list of threads that are waiting for a lock held by this thread. 139 /// Adds a thread to the list of threads that are waiting for a lock held by this thread.
146 void AddMutexWaiter(SharedPtr<Thread> thread); 140 void AddMutexWaiter(SharedPtr<Thread> thread);
147 141
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 657baddb8..0249b6992 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -115,11 +115,12 @@ private:
115 115
116 void Read(Kernel::HLERequestContext& ctx) { 116 void Read(Kernel::HLERequestContext& ctx) {
117 IPC::RequestParser rp{ctx}; 117 IPC::RequestParser rp{ctx};
118 const u64 unk = rp.Pop<u64>(); 118 const u64 option = rp.Pop<u64>();
119 const s64 offset = rp.Pop<s64>(); 119 const s64 offset = rp.Pop<s64>();
120 const s64 length = rp.Pop<s64>(); 120 const s64 length = rp.Pop<s64>();
121 121
122 LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); 122 LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset,
123 length);
123 124
124 // Error checking 125 // Error checking
125 if (length < 0) { 126 if (length < 0) {
@@ -148,11 +149,12 @@ private:
148 149
149 void Write(Kernel::HLERequestContext& ctx) { 150 void Write(Kernel::HLERequestContext& ctx) {
150 IPC::RequestParser rp{ctx}; 151 IPC::RequestParser rp{ctx};
151 const u64 unk = rp.Pop<u64>(); 152 const u64 option = rp.Pop<u64>();
152 const s64 offset = rp.Pop<s64>(); 153 const s64 offset = rp.Pop<s64>();
153 const s64 length = rp.Pop<s64>(); 154 const s64 length = rp.Pop<s64>();
154 155
155 LOG_DEBUG(Service_FS, "called, offset=0x{:X}, length={}", offset, length); 156 LOG_DEBUG(Service_FS, "called, option={}, offset=0x{:X}, length={}", option, offset,
157 length);
156 158
157 // Error checking 159 // Error checking
158 if (length < 0) { 160 if (length < 0) {
@@ -250,10 +252,7 @@ private:
250 u64 next_entry_index = 0; 252 u64 next_entry_index = 0;
251 253
252 void Read(Kernel::HLERequestContext& ctx) { 254 void Read(Kernel::HLERequestContext& ctx) {
253 IPC::RequestParser rp{ctx}; 255 LOG_DEBUG(Service_FS, "called.");
254 const u64 unk = rp.Pop<u64>();
255
256 LOG_DEBUG(Service_FS, "called, unk=0x{:X}", unk);
257 256
258 // Calculate how many entries we can fit in the output buffer 257 // Calculate how many entries we can fit in the output buffer
259 const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry); 258 const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index d65693fc7..609102f2c 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -93,12 +93,18 @@ public:
93 } 93 }
94 94
95 void LoadNrr(Kernel::HLERequestContext& ctx) { 95 void LoadNrr(Kernel::HLERequestContext& ctx) {
96 struct Parameters {
97 u64_le process_id;
98 u64_le nrr_address;
99 u64_le nrr_size;
100 };
101
96 IPC::RequestParser rp{ctx}; 102 IPC::RequestParser rp{ctx};
97 rp.Skip(2, false); 103 const auto [process_id, nrr_address, nrr_size] = rp.PopRaw<Parameters>();
98 const VAddr nrr_addr{rp.Pop<VAddr>()}; 104
99 const u64 nrr_size{rp.Pop<u64>()}; 105 LOG_DEBUG(Service_LDR,
100 LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}, nrr_size={:016X}", nrr_addr, 106 "called with process_id={:016X}, nrr_address={:016X}, nrr_size={:016X}",
101 nrr_size); 107 process_id, nrr_address, nrr_size);
102 108
103 if (!initialized) { 109 if (!initialized) {
104 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); 110 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
@@ -116,24 +122,26 @@ public:
116 } 122 }
117 123
118 // NRR Address does not fall on 0x1000 byte boundary 124 // NRR Address does not fall on 0x1000 byte boundary
119 if (!Common::Is4KBAligned(nrr_addr)) { 125 if (!Common::Is4KBAligned(nrr_address)) {
120 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); 126 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
127 nrr_address);
121 IPC::ResponseBuilder rb{ctx, 2}; 128 IPC::ResponseBuilder rb{ctx, 2};
122 rb.Push(ERROR_INVALID_ALIGNMENT); 129 rb.Push(ERROR_INVALID_ALIGNMENT);
123 return; 130 return;
124 } 131 }
125 132
126 // NRR Size is zero or causes overflow 133 // NRR Size is zero or causes overflow
127 if (nrr_addr + nrr_size <= nrr_addr || nrr_size == 0 || !Common::Is4KBAligned(nrr_size)) { 134 if (nrr_address + nrr_size <= nrr_address || nrr_size == 0 ||
135 !Common::Is4KBAligned(nrr_size)) {
128 LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})", 136 LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})",
129 nrr_addr, nrr_size); 137 nrr_address, nrr_size);
130 IPC::ResponseBuilder rb{ctx, 2}; 138 IPC::ResponseBuilder rb{ctx, 2};
131 rb.Push(ERROR_INVALID_SIZE); 139 rb.Push(ERROR_INVALID_SIZE);
132 return; 140 return;
133 } 141 }
134 // Read NRR data from memory 142 // Read NRR data from memory
135 std::vector<u8> nrr_data(nrr_size); 143 std::vector<u8> nrr_data(nrr_size);
136 Memory::ReadBlock(nrr_addr, nrr_data.data(), nrr_size); 144 Memory::ReadBlock(nrr_address, nrr_data.data(), nrr_size);
137 NRRHeader header; 145 NRRHeader header;
138 std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader)); 146 std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader));
139 147
@@ -174,7 +182,7 @@ public:
174 hashes.emplace_back(hash); 182 hashes.emplace_back(hash);
175 } 183 }
176 184
177 nrr.insert_or_assign(nrr_addr, std::move(hashes)); 185 nrr.insert_or_assign(nrr_address, std::move(hashes));
178 186
179 IPC::ResponseBuilder rb{ctx, 2}; 187 IPC::ResponseBuilder rb{ctx, 2};
180 rb.Push(RESULT_SUCCESS); 188 rb.Push(RESULT_SUCCESS);
@@ -188,23 +196,30 @@ public:
188 return; 196 return;
189 } 197 }
190 198
199 struct Parameters {
200 u64_le process_id;
201 u64_le nrr_address;
202 };
203
191 IPC::RequestParser rp{ctx}; 204 IPC::RequestParser rp{ctx};
192 rp.Skip(2, false); 205 const auto [process_id, nrr_address] = rp.PopRaw<Parameters>();
193 const auto nrr_addr{rp.Pop<VAddr>()}; 206
194 LOG_DEBUG(Service_LDR, "called with nrr_addr={:016X}", nrr_addr); 207 LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nrr_addr={:016X}", process_id,
208 nrr_address);
195 209
196 if (!Common::Is4KBAligned(nrr_addr)) { 210 if (!Common::Is4KBAligned(nrr_address)) {
197 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); 211 LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!",
212 nrr_address);
198 IPC::ResponseBuilder rb{ctx, 2}; 213 IPC::ResponseBuilder rb{ctx, 2};
199 rb.Push(ERROR_INVALID_ALIGNMENT); 214 rb.Push(ERROR_INVALID_ALIGNMENT);
200 return; 215 return;
201 } 216 }
202 217
203 const auto iter = nrr.find(nrr_addr); 218 const auto iter = nrr.find(nrr_address);
204 if (iter == nrr.end()) { 219 if (iter == nrr.end()) {
205 LOG_ERROR(Service_LDR, 220 LOG_ERROR(Service_LDR,
206 "Attempting to unload NRR which has not been loaded! (addr={:016X})", 221 "Attempting to unload NRR which has not been loaded! (addr={:016X})",
207 nrr_addr); 222 nrr_address);
208 IPC::ResponseBuilder rb{ctx, 2}; 223 IPC::ResponseBuilder rb{ctx, 2};
209 rb.Push(ERROR_INVALID_NRR_ADDRESS); 224 rb.Push(ERROR_INVALID_NRR_ADDRESS);
210 return; 225 return;
@@ -216,16 +231,22 @@ public:
216 } 231 }
217 232
218 void LoadNro(Kernel::HLERequestContext& ctx) { 233 void LoadNro(Kernel::HLERequestContext& ctx) {
234 struct Parameters {
235 u64_le process_id;
236 u64_le image_address;
237 u64_le image_size;
238 u64_le bss_address;
239 u64_le bss_size;
240 };
241
219 IPC::RequestParser rp{ctx}; 242 IPC::RequestParser rp{ctx};
220 rp.Skip(2, false); 243 const auto [process_id, nro_address, nro_size, bss_address, bss_size] =
221 const VAddr nro_addr{rp.Pop<VAddr>()}; 244 rp.PopRaw<Parameters>();
222 const u64 nro_size{rp.Pop<u64>()}; 245
223 const VAddr bss_addr{rp.Pop<VAddr>()}; 246 LOG_DEBUG(Service_LDR,
224 const u64 bss_size{rp.Pop<u64>()}; 247 "called with pid={:016X}, nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, "
225 LOG_DEBUG( 248 "bss_size={:016X}",
226 Service_LDR, 249 process_id, nro_address, nro_size, bss_address, bss_size);
227 "called with nro_addr={:016X}, nro_size={:016X}, bss_addr={:016X}, bss_size={:016X}",
228 nro_addr, nro_size, bss_addr, bss_size);
229 250
230 if (!initialized) { 251 if (!initialized) {
231 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); 252 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
@@ -243,8 +264,9 @@ public:
243 } 264 }
244 265
245 // NRO Address does not fall on 0x1000 byte boundary 266 // NRO Address does not fall on 0x1000 byte boundary
246 if (!Common::Is4KBAligned(nro_addr)) { 267 if (!Common::Is4KBAligned(nro_address)) {
247 LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr); 268 LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!",
269 nro_address);
248 IPC::ResponseBuilder rb{ctx, 2}; 270 IPC::ResponseBuilder rb{ctx, 2};
249 rb.Push(ERROR_INVALID_ALIGNMENT); 271 rb.Push(ERROR_INVALID_ALIGNMENT);
250 return; 272 return;
@@ -252,15 +274,15 @@ public:
252 274
253 // NRO Size or BSS Size is zero or causes overflow 275 // NRO Size or BSS Size is zero or causes overflow
254 const auto nro_size_valid = 276 const auto nro_size_valid =
255 nro_size != 0 && nro_addr + nro_size > nro_addr && Common::Is4KBAligned(nro_size); 277 nro_size != 0 && nro_address + nro_size > nro_address && Common::Is4KBAligned(nro_size);
256 const auto bss_size_valid = 278 const auto bss_size_valid = nro_size + bss_size >= nro_size &&
257 nro_size + bss_size >= nro_size && (bss_size == 0 || bss_addr + bss_size > bss_addr); 279 (bss_size == 0 || bss_address + bss_size > bss_address);
258 280
259 if (!nro_size_valid || !bss_size_valid) { 281 if (!nro_size_valid || !bss_size_valid) {
260 LOG_ERROR(Service_LDR, 282 LOG_ERROR(Service_LDR,
261 "NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, " 283 "NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, "
262 "bss_address={:016X}, bss_size={:016X})", 284 "bss_address={:016X}, bss_size={:016X})",
263 nro_addr, nro_size, bss_addr, bss_size); 285 nro_address, nro_size, bss_address, bss_size);
264 IPC::ResponseBuilder rb{ctx, 2}; 286 IPC::ResponseBuilder rb{ctx, 2};
265 rb.Push(ERROR_INVALID_SIZE); 287 rb.Push(ERROR_INVALID_SIZE);
266 return; 288 return;
@@ -268,7 +290,7 @@ public:
268 290
269 // Read NRO data from memory 291 // Read NRO data from memory
270 std::vector<u8> nro_data(nro_size); 292 std::vector<u8> nro_data(nro_size);
271 Memory::ReadBlock(nro_addr, nro_data.data(), nro_size); 293 Memory::ReadBlock(nro_address, nro_data.data(), nro_size);
272 294
273 SHA256Hash hash{}; 295 SHA256Hash hash{};
274 mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0); 296 mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0);
@@ -318,17 +340,18 @@ public:
318 return; 340 return;
319 } 341 }
320 342
321 ASSERT(vm_manager 343 ASSERT(
322 .MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode) 344 vm_manager
323 .IsSuccess()); 345 .MirrorMemory(*map_address, nro_address, nro_size, Kernel::MemoryState::ModuleCode)
324 ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess()); 346 .IsSuccess());
347 ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
325 348
326 if (bss_size > 0) { 349 if (bss_size > 0) {
327 ASSERT(vm_manager 350 ASSERT(vm_manager
328 .MirrorMemory(*map_address + nro_size, bss_addr, bss_size, 351 .MirrorMemory(*map_address + nro_size, bss_address, bss_size,
329 Kernel::MemoryState::ModuleCode) 352 Kernel::MemoryState::ModuleCode)
330 .IsSuccess()); 353 .IsSuccess());
331 ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess()); 354 ASSERT(vm_manager.UnmapRange(bss_address, bss_size).IsSuccess());
332 } 355 }
333 356
334 vm_manager.ReprotectRange(*map_address, header.text_size, 357 vm_manager.ReprotectRange(*map_address, header.text_size,
@@ -348,13 +371,6 @@ public:
348 } 371 }
349 372
350 void UnloadNro(Kernel::HLERequestContext& ctx) { 373 void UnloadNro(Kernel::HLERequestContext& ctx) {
351 IPC::RequestParser rp{ctx};
352 rp.Skip(2, false);
353 const VAddr mapped_addr{rp.PopRaw<VAddr>()};
354 const VAddr heap_addr{rp.PopRaw<VAddr>()};
355 LOG_DEBUG(Service_LDR, "called with mapped_addr={:016X}, heap_addr={:016X}", mapped_addr,
356 heap_addr);
357
358 if (!initialized) { 374 if (!initialized) {
359 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); 375 LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!");
360 IPC::ResponseBuilder rb{ctx, 2}; 376 IPC::ResponseBuilder rb{ctx, 2};
@@ -362,22 +378,30 @@ public:
362 return; 378 return;
363 } 379 }
364 380
365 if (!Common::Is4KBAligned(mapped_addr) || !Common::Is4KBAligned(heap_addr)) { 381 struct Parameters {
366 LOG_ERROR(Service_LDR, 382 u64_le process_id;
367 "NRO/BSS Address has invalid alignment (actual nro_addr={:016X}, " 383 u64_le nro_address;
368 "bss_addr={:016X})!", 384 };
369 mapped_addr, heap_addr); 385
386 IPC::RequestParser rp{ctx};
387 const auto [process_id, nro_address] = rp.PopRaw<Parameters>();
388 LOG_DEBUG(Service_LDR, "called with process_id={:016X}, nro_address=0x{:016X}", process_id,
389 nro_address);
390
391 if (!Common::Is4KBAligned(nro_address)) {
392 LOG_ERROR(Service_LDR, "NRO address has invalid alignment (nro_address=0x{:016X})",
393 nro_address);
370 IPC::ResponseBuilder rb{ctx, 2}; 394 IPC::ResponseBuilder rb{ctx, 2};
371 rb.Push(ERROR_INVALID_ALIGNMENT); 395 rb.Push(ERROR_INVALID_ALIGNMENT);
372 return; 396 return;
373 } 397 }
374 398
375 const auto iter = nro.find(mapped_addr); 399 const auto iter = nro.find(nro_address);
376 if (iter == nro.end()) { 400 if (iter == nro.end()) {
377 LOG_ERROR(Service_LDR, 401 LOG_ERROR(Service_LDR,
378 "The NRO attempting to unmap was not mapped or has an invalid address " 402 "The NRO attempting to be unmapped was not mapped or has an invalid address "
379 "(actual {:016X})!", 403 "(nro_address=0x{:016X})!",
380 mapped_addr); 404 nro_address);
381 IPC::ResponseBuilder rb{ctx, 2}; 405 IPC::ResponseBuilder rb{ctx, 2};
382 rb.Push(ERROR_INVALID_NRO_ADDRESS); 406 rb.Push(ERROR_INVALID_NRO_ADDRESS);
383 return; 407 return;
@@ -386,10 +410,7 @@ public:
386 auto& vm_manager = Core::CurrentProcess()->VMManager(); 410 auto& vm_manager = Core::CurrentProcess()->VMManager();
387 const auto& nro_size = iter->second.size; 411 const auto& nro_size = iter->second.size;
388 412
389 ASSERT(vm_manager 413 ASSERT(vm_manager.UnmapRange(nro_address, nro_size).IsSuccess());
390 .MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode)
391 .IsSuccess());
392 ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess());
393 414
394 Core::System::GetInstance().InvalidateCpuInstructionCaches(); 415 Core::System::GetInstance().InvalidateCpuInstructionCaches();
395 416
@@ -459,11 +480,10 @@ private:
459 std::map<VAddr, NROInfo> nro; 480 std::map<VAddr, NROInfo> nro;
460 std::map<VAddr, std::vector<SHA256Hash>> nrr; 481 std::map<VAddr, std::vector<SHA256Hash>> nrr;
461 482
462 bool IsValidNROHash(const SHA256Hash& hash) { 483 bool IsValidNROHash(const SHA256Hash& hash) const {
463 return std::any_of( 484 return std::any_of(nrr.begin(), nrr.end(), [&hash](const auto& p) {
464 nrr.begin(), nrr.end(), [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) { 485 return std::find(p.second.begin(), p.second.end(), hash) != p.second.end();
465 return std::find(p.second.begin(), p.second.end(), hash) != p.second.end(); 486 });
466 });
467 } 487 }
468 488
469 static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) { 489 static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) {
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index c7f5bbf28..3c5c53e24 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -21,12 +21,13 @@
21#include "core/hle/service/vi/display/vi_display.h" 21#include "core/hle/service/vi/display/vi_display.h"
22#include "core/hle/service/vi/layer/vi_layer.h" 22#include "core/hle/service/vi/layer/vi_layer.h"
23#include "core/perf_stats.h" 23#include "core/perf_stats.h"
24#include "core/settings.h"
24#include "video_core/renderer_base.h" 25#include "video_core/renderer_base.h"
25 26
26namespace Service::NVFlinger { 27namespace Service::NVFlinger {
27 28
28constexpr std::size_t SCREEN_REFRESH_RATE = 60; 29constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
29constexpr s64 frame_ticks = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); 30constexpr s64 frame_ticks_30fps = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 30);
30 31
31NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} { 32NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_timing} {
32 displays.emplace_back(0, "Default"); 33 displays.emplace_back(0, "Default");
@@ -36,13 +37,15 @@ NVFlinger::NVFlinger(Core::Timing::CoreTiming& core_timing) : core_timing{core_t
36 displays.emplace_back(4, "Null"); 37 displays.emplace_back(4, "Null");
37 38
38 // Schedule the screen composition events 39 // Schedule the screen composition events
39 composition_event = 40 const auto ticks = Settings::values.force_30fps_mode ? frame_ticks_30fps : frame_ticks;
40 core_timing.RegisterEvent("ScreenComposition", [this](u64 userdata, s64 cycles_late) { 41
42 composition_event = core_timing.RegisterEvent(
43 "ScreenComposition", [this, ticks](u64 userdata, s64 cycles_late) {
41 Compose(); 44 Compose();
42 this->core_timing.ScheduleEvent(frame_ticks - cycles_late, composition_event); 45 this->core_timing.ScheduleEvent(ticks - cycles_late, composition_event);
43 }); 46 });
44 47
45 core_timing.ScheduleEvent(frame_ticks, composition_event); 48 core_timing.ScheduleEvent(ticks, composition_event);
46} 49}
47 50
48NVFlinger::~NVFlinger() { 51NVFlinger::~NVFlinger() {
@@ -62,6 +65,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
62 const auto itr = 65 const auto itr =
63 std::find_if(displays.begin(), displays.end(), 66 std::find_if(displays.begin(), displays.end(),
64 [&](const VI::Display& display) { return display.GetName() == name; }); 67 [&](const VI::Display& display) { return display.GetName() == name; });
68
65 if (itr == displays.end()) { 69 if (itr == displays.end()) {
66 return {}; 70 return {};
67 } 71 }
diff --git a/src/core/settings.h b/src/core/settings.h
index d543eb32f..b84390745 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -393,6 +393,7 @@ struct Values {
393 bool use_disk_shader_cache; 393 bool use_disk_shader_cache;
394 bool use_accurate_gpu_emulation; 394 bool use_accurate_gpu_emulation;
395 bool use_asynchronous_gpu_emulation; 395 bool use_asynchronous_gpu_emulation;
396 bool force_30fps_mode;
396 397
397 float bg_red; 398 float bg_red;
398 float bg_green; 399 float bg_green;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 55b6d8591..7a68b8738 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -112,11 +112,26 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
112 params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), 112 params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
113 params.srgb_conversion); 113 params.srgb_conversion);
114 114
115 if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) { 115 if (config.tsc.depth_compare_enabled) {
116 // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled, 116 // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,
117 // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also 117 // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also
118 // causes GetFormatType to properly return 'Depth' below). 118 // causes GetFormatType to properly return 'Depth' below).
119 params.pixel_format = PixelFormat::Z16; 119 if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) {
120 switch (params.pixel_format) {
121 case PixelFormat::R16S:
122 case PixelFormat::R16U:
123 case PixelFormat::R16F:
124 params.pixel_format = PixelFormat::Z16;
125 break;
126 case PixelFormat::R32F:
127 params.pixel_format = PixelFormat::Z32F;
128 break;
129 default:
130 LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}",
131 static_cast<u32>(params.pixel_format));
132 break;
133 }
134 }
120 } 135 }
121 136
122 params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); 137 params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
@@ -266,10 +281,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
266 params.component_type = ComponentTypeFromRenderTarget(config.format); 281 params.component_type = ComponentTypeFromRenderTarget(config.format);
267 params.type = GetFormatType(params.pixel_format); 282 params.type = GetFormatType(params.pixel_format);
268 params.width = config.width; 283 params.width = config.width;
269 if (!params.is_tiled) { 284 params.pitch = config.pitch;
270 const u32 bpp = params.GetFormatBpp() / 8;
271 params.pitch = config.width * bpp;
272 }
273 params.height = config.height; 285 params.height = config.height;
274 params.unaligned_height = config.height; 286 params.unaligned_height = config.height;
275 params.target = SurfaceTarget::Texture2D; 287 params.target = SurfaceTarget::Texture2D;
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index eaf3e03a0..05ab01dcb 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -2,12 +2,44 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/common_types.h"
6#include "video_core/engines/maxwell_3d.h"
5#include "video_core/renderer_opengl/gl_shader_manager.h" 7#include "video_core/renderer_opengl/gl_shader_manager.h"
6 8
7namespace OpenGL::GLShader { 9namespace OpenGL::GLShader {
8 10
9using Tegra::Engines::Maxwell3D; 11using Tegra::Engines::Maxwell3D;
10 12
13ProgramManager::ProgramManager() {
14 pipeline.Create();
15}
16
17ProgramManager::~ProgramManager() = default;
18
19void ProgramManager::ApplyTo(OpenGLState& state) {
20 UpdatePipeline();
21 state.draw.shader_program = 0;
22 state.draw.program_pipeline = pipeline.handle;
23}
24
25void ProgramManager::UpdatePipeline() {
26 // Avoid updating the pipeline when values have no changed
27 if (old_state == current_state) {
28 return;
29 }
30
31 // Workaround for AMD bug
32 constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
33 GL_FRAGMENT_SHADER_BIT};
34 glUseProgramStages(pipeline.handle, all_used_stages, 0);
35
36 glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
37 glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
38 glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
39
40 old_state = current_state;
41}
42
11void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) { 43void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
12 const auto& regs = maxwell.regs; 44 const auto& regs = maxwell.regs;
13 const auto& state = maxwell.state; 45 const auto& state = maxwell.state;
@@ -16,7 +48,7 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shade
16 viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f; 48 viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
17 viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f; 49 viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
18 50
19 u32 func = static_cast<u32>(regs.alpha_test_func); 51 auto func{static_cast<u32>(regs.alpha_test_func)};
20 // Normalize the gl variants of opCompare to be the same as the normal variants 52 // Normalize the gl variants of opCompare to be the same as the normal variants
21 const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never); 53 const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never);
22 if (func >= op_gl_variant_base) { 54 if (func >= op_gl_variant_base) {
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 37dcfefdb..cec18a832 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -4,6 +4,8 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <cstddef>
8
7#include <glad/glad.h> 9#include <glad/glad.h>
8 10
9#include "video_core/renderer_opengl/gl_resource_manager.h" 11#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -38,55 +40,48 @@ static_assert(sizeof(MaxwellUniformData) < 16384,
38 40
39class ProgramManager { 41class ProgramManager {
40public: 42public:
41 ProgramManager() { 43 explicit ProgramManager();
42 pipeline.Create(); 44 ~ProgramManager();
43 } 45
46 void ApplyTo(OpenGLState& state);
44 47
45 void UseProgrammableVertexShader(GLuint program) { 48 void UseProgrammableVertexShader(GLuint program) {
46 vs = program; 49 current_state.vertex_shader = program;
47 } 50 }
48 51
49 void UseProgrammableGeometryShader(GLuint program) { 52 void UseProgrammableGeometryShader(GLuint program) {
50 gs = program; 53 current_state.geometry_shader = program;
51 } 54 }
52 55
53 void UseProgrammableFragmentShader(GLuint program) { 56 void UseProgrammableFragmentShader(GLuint program) {
54 fs = program; 57 current_state.fragment_shader = program;
55 } 58 }
56 59
57 void UseTrivialGeometryShader() { 60 void UseTrivialGeometryShader() {
58 gs = 0; 61 current_state.geometry_shader = 0;
59 }
60
61 void ApplyTo(OpenGLState& state) {
62 UpdatePipeline();
63 state.draw.shader_program = 0;
64 state.draw.program_pipeline = pipeline.handle;
65 } 62 }
66 63
67private: 64private:
68 void UpdatePipeline() { 65 struct PipelineState {
69 // Avoid updating the pipeline when values have no changed 66 bool operator==(const PipelineState& rhs) const {
70 if (old_vs == vs && old_fs == fs && old_gs == gs) 67 return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
71 return; 68 geometry_shader == rhs.geometry_shader;
72 // Workaround for AMD bug 69 }
73 glUseProgramStages(pipeline.handle, 70
74 GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 71 bool operator!=(const PipelineState& rhs) const {
75 0); 72 return !operator==(rhs);
76 73 }
77 glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs); 74
78 glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs); 75 GLuint vertex_shader{};
79 glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs); 76 GLuint fragment_shader{};
80 77 GLuint geometry_shader{};
81 // Update the old values 78 };
82 old_vs = vs; 79
83 old_fs = fs; 80 void UpdatePipeline();
84 old_gs = gs;
85 }
86 81
87 OGLPipeline pipeline; 82 OGLPipeline pipeline;
88 GLuint vs{}, fs{}, gs{}; 83 PipelineState current_state;
89 GLuint old_vs{}, old_fs{}, old_gs{}; 84 PipelineState old_state;
90}; 85};
91 86
92} // namespace OpenGL::GLShader 87} // namespace OpenGL::GLShader
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index a7ac26d71..3b022a456 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -294,6 +294,8 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
294 return PixelFormat::Z16; 294 return PixelFormat::Z16;
295 case Tegra::Texture::TextureFormat::Z24S8: 295 case Tegra::Texture::TextureFormat::Z24S8:
296 return PixelFormat::Z24S8; 296 return PixelFormat::Z24S8;
297 case Tegra::Texture::TextureFormat::ZF32_X24S8:
298 return PixelFormat::Z32FS8;
297 case Tegra::Texture::TextureFormat::DXT1: 299 case Tegra::Texture::TextureFormat::DXT1:
298 return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1; 300 return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
299 case Tegra::Texture::TextureFormat::DXT23: 301 case Tegra::Texture::TextureFormat::DXT23:
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 7438fbc0a..c29f2d2dc 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,6 +1,13 @@
1// Copyright 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
1#include <QApplication> 5#include <QApplication>
2#include <QHBoxLayout> 6#include <QHBoxLayout>
3#include <QKeyEvent> 7#include <QKeyEvent>
8#include <QOffscreenSurface>
9#include <QOpenGLWindow>
10#include <QPainter>
4#include <QScreen> 11#include <QScreen>
5#include <QWindow> 12#include <QWindow>
6#include <fmt/format.h> 13#include <fmt/format.h>
@@ -82,13 +89,36 @@ void EmuThread::run() {
82 render_window->moveContext(); 89 render_window->moveContext();
83} 90}
84 91
92class GGLContext : public Core::Frontend::GraphicsContext {
93public:
94 explicit GGLContext(QOpenGLContext* shared_context) : surface() {
95 context = std::make_unique<QOpenGLContext>(shared_context);
96 surface.setFormat(shared_context->format());
97 surface.create();
98 }
99
100 void MakeCurrent() override {
101 context->makeCurrent(&surface);
102 }
103
104 void DoneCurrent() override {
105 context->doneCurrent();
106 }
107
108 void SwapBuffers() override {}
109
110private:
111 std::unique_ptr<QOpenGLContext> context;
112 QOffscreenSurface surface;
113};
114
85// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL 115// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL
86// context. 116// context.
87// The corresponding functionality is handled in EmuThread instead 117// The corresponding functionality is handled in EmuThread instead
88class GGLWidgetInternal : public QGLWidget { 118class GGLWidgetInternal : public QOpenGLWindow {
89public: 119public:
90 GGLWidgetInternal(QGLFormat fmt, GRenderWindow* parent) 120 GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context)
91 : QGLWidget(fmt, parent), parent(parent) {} 121 : QOpenGLWindow(shared_context), parent(parent) {}
92 122
93 void paintEvent(QPaintEvent* ev) override { 123 void paintEvent(QPaintEvent* ev) override {
94 if (do_painting) { 124 if (do_painting) {
@@ -101,9 +131,51 @@ public:
101 parent->OnFramebufferSizeChanged(); 131 parent->OnFramebufferSizeChanged();
102 } 132 }
103 133
134 void keyPressEvent(QKeyEvent* event) override {
135 InputCommon::GetKeyboard()->PressKey(event->key());
136 }
137
138 void keyReleaseEvent(QKeyEvent* event) override {
139 InputCommon::GetKeyboard()->ReleaseKey(event->key());
140 }
141
142 void mousePressEvent(QMouseEvent* event) override {
143 if (event->source() == Qt::MouseEventSynthesizedBySystem)
144 return; // touch input is handled in TouchBeginEvent
145
146 const auto pos{event->pos()};
147 if (event->button() == Qt::LeftButton) {
148 const auto [x, y] = parent->ScaleTouch(pos);
149 parent->TouchPressed(x, y);
150 } else if (event->button() == Qt::RightButton) {
151 InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
152 }
153 }
154
155 void mouseMoveEvent(QMouseEvent* event) override {
156 if (event->source() == Qt::MouseEventSynthesizedBySystem)
157 return; // touch input is handled in TouchUpdateEvent
158
159 const auto pos{event->pos()};
160 const auto [x, y] = parent->ScaleTouch(pos);
161 parent->TouchMoved(x, y);
162 InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
163 }
164
165 void mouseReleaseEvent(QMouseEvent* event) override {
166 if (event->source() == Qt::MouseEventSynthesizedBySystem)
167 return; // touch input is handled in TouchEndEvent
168
169 if (event->button() == Qt::LeftButton)
170 parent->TouchReleased();
171 else if (event->button() == Qt::RightButton)
172 InputCommon::GetMotionEmu()->EndTilt();
173 }
174
104 void DisablePainting() { 175 void DisablePainting() {
105 do_painting = false; 176 do_painting = false;
106 } 177 }
178
107 void EnablePainting() { 179 void EnablePainting() {
108 do_painting = true; 180 do_painting = true;
109 } 181 }
@@ -114,7 +186,7 @@ private:
114}; 186};
115 187
116GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) 188GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
117 : QWidget(parent), child(nullptr), emu_thread(emu_thread) { 189 : QWidget(parent), child(nullptr), context(nullptr), emu_thread(emu_thread) {
118 190
119 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") 191 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
120 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); 192 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
@@ -137,19 +209,19 @@ void GRenderWindow::moveContext() {
137 auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) 209 auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr)
138 ? emu_thread 210 ? emu_thread
139 : qApp->thread(); 211 : qApp->thread();
140 child->context()->moveToThread(thread); 212 context->moveToThread(thread);
141} 213}
142 214
143void GRenderWindow::SwapBuffers() { 215void GRenderWindow::SwapBuffers() {
144 // In our multi-threaded QGLWidget use case we shouldn't need to call `makeCurrent`, 216 // In our multi-threaded QWidget use case we shouldn't need to call `makeCurrent`,
145 // since we never call `doneCurrent` in this thread. 217 // since we never call `doneCurrent` in this thread.
146 // However: 218 // However:
147 // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called 219 // - The Qt debug runtime prints a bogus warning on the console if `makeCurrent` wasn't called
148 // since the last time `swapBuffers` was executed; 220 // since the last time `swapBuffers` was executed;
149 // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks. 221 // - On macOS, if `makeCurrent` isn't called explicitely, resizing the buffer breaks.
150 child->makeCurrent(); 222 context->makeCurrent(child);
151 223
152 child->swapBuffers(); 224 context->swapBuffers(child);
153 if (!first_frame) { 225 if (!first_frame) {
154 emit FirstFrameDisplayed(); 226 emit FirstFrameDisplayed();
155 first_frame = true; 227 first_frame = true;
@@ -157,11 +229,11 @@ void GRenderWindow::SwapBuffers() {
157} 229}
158 230
159void GRenderWindow::MakeCurrent() { 231void GRenderWindow::MakeCurrent() {
160 child->makeCurrent(); 232 context->makeCurrent(child);
161} 233}
162 234
163void GRenderWindow::DoneCurrent() { 235void GRenderWindow::DoneCurrent() {
164 child->doneCurrent(); 236 context->doneCurrent();
165} 237}
166 238
167void GRenderWindow::PollEvents() {} 239void GRenderWindow::PollEvents() {}
@@ -174,14 +246,26 @@ void GRenderWindow::PollEvents() {}
174void GRenderWindow::OnFramebufferSizeChanged() { 246void GRenderWindow::OnFramebufferSizeChanged() {
175 // Screen changes potentially incur a change in screen DPI, hence we should update the 247 // Screen changes potentially incur a change in screen DPI, hence we should update the
176 // framebuffer size 248 // framebuffer size
177 qreal pixelRatio = windowPixelRatio(); 249 qreal pixelRatio = GetWindowPixelRatio();
178 unsigned width = child->QPaintDevice::width() * pixelRatio; 250 unsigned width = child->QPaintDevice::width() * pixelRatio;
179 unsigned height = child->QPaintDevice::height() * pixelRatio; 251 unsigned height = child->QPaintDevice::height() * pixelRatio;
180 UpdateCurrentFramebufferLayout(width, height); 252 UpdateCurrentFramebufferLayout(width, height);
181} 253}
182 254
255void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) {
256 if (child) {
257 child->keyPressEvent(event);
258 }
259}
260
261void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) {
262 if (child) {
263 child->keyReleaseEvent(event);
264 }
265}
266
183void GRenderWindow::BackupGeometry() { 267void GRenderWindow::BackupGeometry() {
184 geometry = ((QGLWidget*)this)->saveGeometry(); 268 geometry = ((QWidget*)this)->saveGeometry();
185} 269}
186 270
187void GRenderWindow::RestoreGeometry() { 271void GRenderWindow::RestoreGeometry() {
@@ -199,18 +283,18 @@ QByteArray GRenderWindow::saveGeometry() {
199 // If we are a top-level widget, store the current geometry 283 // If we are a top-level widget, store the current geometry
200 // otherwise, store the last backup 284 // otherwise, store the last backup
201 if (parent() == nullptr) 285 if (parent() == nullptr)
202 return ((QGLWidget*)this)->saveGeometry(); 286 return ((QWidget*)this)->saveGeometry();
203 else 287 else
204 return geometry; 288 return geometry;
205} 289}
206 290
207qreal GRenderWindow::windowPixelRatio() const { 291qreal GRenderWindow::GetWindowPixelRatio() const {
208 // windowHandle() might not be accessible until the window is displayed to screen. 292 // windowHandle() might not be accessible until the window is displayed to screen.
209 return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; 293 return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
210} 294}
211 295
212std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const { 296std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
213 const qreal pixel_ratio = windowPixelRatio(); 297 const qreal pixel_ratio = GetWindowPixelRatio();
214 return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), 298 return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
215 static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; 299 static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
216} 300}
@@ -220,47 +304,6 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
220 QWidget::closeEvent(event); 304 QWidget::closeEvent(event);
221} 305}
222 306
223void GRenderWindow::keyPressEvent(QKeyEvent* event) {
224 InputCommon::GetKeyboard()->PressKey(event->key());
225}
226
227void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
228 InputCommon::GetKeyboard()->ReleaseKey(event->key());
229}
230
231void GRenderWindow::mousePressEvent(QMouseEvent* event) {
232 if (event->source() == Qt::MouseEventSynthesizedBySystem)
233 return; // touch input is handled in TouchBeginEvent
234
235 auto pos = event->pos();
236 if (event->button() == Qt::LeftButton) {
237 const auto [x, y] = ScaleTouch(pos);
238 this->TouchPressed(x, y);
239 } else if (event->button() == Qt::RightButton) {
240 InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
241 }
242}
243
244void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
245 if (event->source() == Qt::MouseEventSynthesizedBySystem)
246 return; // touch input is handled in TouchUpdateEvent
247
248 auto pos = event->pos();
249 const auto [x, y] = ScaleTouch(pos);
250 this->TouchMoved(x, y);
251 InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
252}
253
254void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
255 if (event->source() == Qt::MouseEventSynthesizedBySystem)
256 return; // touch input is handled in TouchEndEvent
257
258 if (event->button() == Qt::LeftButton)
259 this->TouchReleased();
260 else if (event->button() == Qt::RightButton)
261 InputCommon::GetMotionEmu()->EndTilt();
262}
263
264void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { 307void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
265 // TouchBegin always has exactly one touch point, so take the .first() 308 // TouchBegin always has exactly one touch point, so take the .first()
266 const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); 309 const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
@@ -313,35 +356,60 @@ void GRenderWindow::OnClientAreaResized(unsigned width, unsigned height) {
313 NotifyClientAreaSizeChanged(std::make_pair(width, height)); 356 NotifyClientAreaSizeChanged(std::make_pair(width, height));
314} 357}
315 358
359std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
360 return std::make_unique<GGLContext>(shared_context.get());
361}
362
316void GRenderWindow::InitRenderTarget() { 363void GRenderWindow::InitRenderTarget() {
317 if (child) { 364 shared_context.reset();
318 delete child; 365 context.reset();
319 }
320 366
321 if (layout()) { 367 delete child;
322 delete layout(); 368 child = nullptr;
323 } 369
370 delete container;
371 container = nullptr;
372
373 delete layout();
324 374
325 first_frame = false; 375 first_frame = false;
326 376
327 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, 377 // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
328 // WA_DontShowOnScreen, WA_DeleteOnClose 378 // WA_DontShowOnScreen, WA_DeleteOnClose
329 QGLFormat fmt; 379 QSurfaceFormat fmt;
330 fmt.setVersion(4, 3); 380 fmt.setVersion(4, 3);
331 fmt.setProfile(QGLFormat::CoreProfile); 381 fmt.setProfile(QSurfaceFormat::CoreProfile);
382 // TODO: expose a setting for buffer value (ie default/single/double/triple)
383 fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
384 shared_context = std::make_unique<QOpenGLContext>();
385 shared_context->setFormat(fmt);
386 shared_context->create();
387 context = std::make_unique<QOpenGLContext>();
388 context->setShareContext(shared_context.get());
389 context->setFormat(fmt);
390 context->create();
332 fmt.setSwapInterval(false); 391 fmt.setSwapInterval(false);
333 392
334 // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X 393 child = new GGLWidgetInternal(this, shared_context.get());
335 fmt.setOption(QGL::NoDeprecatedFunctions); 394 container = QWidget::createWindowContainer(child, this);
336 395
337 child = new GGLWidgetInternal(fmt, this);
338 QBoxLayout* layout = new QHBoxLayout(this); 396 QBoxLayout* layout = new QHBoxLayout(this);
339 397 layout->addWidget(container);
340 resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
341 layout->addWidget(child);
342 layout->setMargin(0); 398 layout->setMargin(0);
343 setLayout(layout); 399 setLayout(layout);
344 400
401 // Reset minimum size to avoid unwanted resizes when this function is called for a second time.
402 setMinimumSize(1, 1);
403
404 // Show causes the window to actually be created and the OpenGL context as well, but we don't
405 // want the widget to be shown yet, so immediately hide it.
406 show();
407 hide();
408
409 resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
410 child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
411 container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
412
345 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); 413 OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
346 414
347 OnFramebufferSizeChanged(); 415 OnFramebufferSizeChanged();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 3183621bc..9608b959f 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -7,9 +7,9 @@
7#include <atomic> 7#include <atomic>
8#include <condition_variable> 8#include <condition_variable>
9#include <mutex> 9#include <mutex>
10#include <QGLWidget>
11#include <QImage> 10#include <QImage>
12#include <QThread> 11#include <QThread>
12#include <QWidget>
13#include "common/thread.h" 13#include "common/thread.h"
14#include "core/core.h" 14#include "core/core.h"
15#include "core/frontend/emu_window.h" 15#include "core/frontend/emu_window.h"
@@ -21,6 +21,8 @@ class QTouchEvent;
21class GGLWidgetInternal; 21class GGLWidgetInternal;
22class GMainWindow; 22class GMainWindow;
23class GRenderWindow; 23class GRenderWindow;
24class QSurface;
25class QOpenGLContext;
24 26
25namespace VideoCore { 27namespace VideoCore {
26enum class LoadCallbackStage; 28enum class LoadCallbackStage;
@@ -121,25 +123,21 @@ public:
121 void MakeCurrent() override; 123 void MakeCurrent() override;
122 void DoneCurrent() override; 124 void DoneCurrent() override;
123 void PollEvents() override; 125 void PollEvents() override;
126 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
127
128 void ForwardKeyPressEvent(QKeyEvent* event);
129 void ForwardKeyReleaseEvent(QKeyEvent* event);
124 130
125 void BackupGeometry(); 131 void BackupGeometry();
126 void RestoreGeometry(); 132 void RestoreGeometry();
127 void restoreGeometry(const QByteArray& geometry); // overridden 133 void restoreGeometry(const QByteArray& geometry); // overridden
128 QByteArray saveGeometry(); // overridden 134 QByteArray saveGeometry(); // overridden
129 135
130 qreal windowPixelRatio() const; 136 qreal GetWindowPixelRatio() const;
137 std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
131 138
132 void closeEvent(QCloseEvent* event) override; 139 void closeEvent(QCloseEvent* event) override;
133
134 void keyPressEvent(QKeyEvent* event) override;
135 void keyReleaseEvent(QKeyEvent* event) override;
136
137 void mousePressEvent(QMouseEvent* event) override;
138 void mouseMoveEvent(QMouseEvent* event) override;
139 void mouseReleaseEvent(QMouseEvent* event) override;
140
141 bool event(QEvent* event) override; 140 bool event(QEvent* event) override;
142
143 void focusOutEvent(QFocusEvent* event) override; 141 void focusOutEvent(QFocusEvent* event) override;
144 142
145 void OnClientAreaResized(unsigned width, unsigned height); 143 void OnClientAreaResized(unsigned width, unsigned height);
@@ -161,7 +159,6 @@ signals:
161 void FirstFrameDisplayed(); 159 void FirstFrameDisplayed();
162 160
163private: 161private:
164 std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
165 void TouchBeginEvent(const QTouchEvent* event); 162 void TouchBeginEvent(const QTouchEvent* event);
166 void TouchUpdateEvent(const QTouchEvent* event); 163 void TouchUpdateEvent(const QTouchEvent* event);
167 void TouchEndEvent(); 164 void TouchEndEvent();
@@ -169,11 +166,17 @@ private:
169 void OnMinimalClientAreaChangeRequest( 166 void OnMinimalClientAreaChangeRequest(
170 const std::pair<unsigned, unsigned>& minimal_size) override; 167 const std::pair<unsigned, unsigned>& minimal_size) override;
171 168
172 GGLWidgetInternal* child; 169 QWidget* container = nullptr;
170 GGLWidgetInternal* child = nullptr;
173 171
174 QByteArray geometry; 172 QByteArray geometry;
175 173
176 EmuThread* emu_thread; 174 EmuThread* emu_thread;
175 // Context that backs the GGLWidgetInternal (and will be used by core to render)
176 std::unique_ptr<QOpenGLContext> context;
177 // Context that will be shared between all newly created contexts. This should never be made
178 // current
179 std::unique_ptr<QOpenGLContext> shared_context;
177 180
178 /// Temporary storage of the screenshot taken 181 /// Temporary storage of the screenshot taken
179 QImage screenshot_image; 182 QImage screenshot_image;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 802db3945..8725a78dc 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -394,6 +394,7 @@ void Config::ReadValues() {
394 ReadSetting("use_accurate_gpu_emulation", false).toBool(); 394 ReadSetting("use_accurate_gpu_emulation", false).toBool();
395 Settings::values.use_asynchronous_gpu_emulation = 395 Settings::values.use_asynchronous_gpu_emulation =
396 ReadSetting("use_asynchronous_gpu_emulation", false).toBool(); 396 ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
397 Settings::values.force_30fps_mode = ReadSetting("force_30fps_mode", false).toBool();
397 398
398 Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat(); 399 Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
399 Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat(); 400 Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
@@ -523,8 +524,8 @@ void Config::ReadValues() {
523 qt_config->beginGroup("Paths"); 524 qt_config->beginGroup("Paths");
524 UISettings::values.roms_path = ReadSetting("romsPath").toString(); 525 UISettings::values.roms_path = ReadSetting("romsPath").toString();
525 UISettings::values.symbols_path = ReadSetting("symbolsPath").toString(); 526 UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
526 UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString(); 527 UISettings::values.game_directory_path = ReadSetting("gameListRootDir", ".").toString();
527 UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool(); 528 UISettings::values.game_directory_deepscan = ReadSetting("gameListDeepScan", false).toBool();
528 UISettings::values.recent_files = ReadSetting("recentFiles").toStringList(); 529 UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
529 qt_config->endGroup(); 530 qt_config->endGroup();
530 531
@@ -664,6 +665,7 @@ void Config::SaveValues() {
664 WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false); 665 WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
665 WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation, 666 WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
666 false); 667 false);
668 WriteSetting("force_30fps_mode", Settings::values.force_30fps_mode, false);
667 669
668 // Cast to double because Qt's written float values are not human-readable 670 // Cast to double because Qt's written float values are not human-readable
669 WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0); 671 WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
@@ -768,8 +770,8 @@ void Config::SaveValues() {
768 WriteSetting("romsPath", UISettings::values.roms_path); 770 WriteSetting("romsPath", UISettings::values.roms_path);
769 WriteSetting("symbolsPath", UISettings::values.symbols_path); 771 WriteSetting("symbolsPath", UISettings::values.symbols_path);
770 WriteSetting("screenshotPath", UISettings::values.screenshot_path); 772 WriteSetting("screenshotPath", UISettings::values.screenshot_path);
771 WriteSetting("gameListRootDir", UISettings::values.gamedir, "."); 773 WriteSetting("gameListRootDir", UISettings::values.game_directory_path, ".");
772 WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false); 774 WriteSetting("gameListDeepScan", UISettings::values.game_directory_deepscan, false);
773 WriteSetting("recentFiles", UISettings::values.recent_files); 775 WriteSetting("recentFiles", UISettings::values.recent_files);
774 qt_config->endGroup(); 776 qt_config->endGroup();
775 777
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index eeb038afb..e48f4f5a3 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -28,7 +28,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
28ConfigureGeneral::~ConfigureGeneral() = default; 28ConfigureGeneral::~ConfigureGeneral() = default;
29 29
30void ConfigureGeneral::setConfiguration() { 30void ConfigureGeneral::setConfiguration() {
31 ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan); 31 ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
32 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 32 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
33 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); 33 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
34 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 34 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
@@ -36,7 +36,7 @@ void ConfigureGeneral::setConfiguration() {
36} 36}
37 37
38void ConfigureGeneral::applyConfiguration() { 38void ConfigureGeneral::applyConfiguration() {
39 UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); 39 UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
40 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); 40 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
41 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); 41 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
42 UISettings::values.theme = 42 UISettings::values.theme =
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index dd1d67488..0a9883d37 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -77,6 +77,8 @@ void ConfigureGraphics::setConfiguration() {
77 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); 77 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
78 ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn()); 78 ui->use_asynchronous_gpu_emulation->setEnabled(!Core::System::GetInstance().IsPoweredOn());
79 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); 79 ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation);
80 ui->force_30fps_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn());
81 ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode);
80 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, 82 UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
81 Settings::values.bg_blue)); 83 Settings::values.bg_blue));
82} 84}
@@ -90,6 +92,7 @@ void ConfigureGraphics::applyConfiguration() {
90 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); 92 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
91 Settings::values.use_asynchronous_gpu_emulation = 93 Settings::values.use_asynchronous_gpu_emulation =
92 ui->use_asynchronous_gpu_emulation->isChecked(); 94 ui->use_asynchronous_gpu_emulation->isChecked();
95 Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked();
93 Settings::values.bg_red = static_cast<float>(bg_color.redF()); 96 Settings::values.bg_red = static_cast<float>(bg_color.redF());
94 Settings::values.bg_green = static_cast<float>(bg_color.greenF()); 97 Settings::values.bg_green = static_cast<float>(bg_color.greenF());
95 Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); 98 Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index c6767e0ca..15ab18ecd 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -71,6 +71,13 @@
71 </widget> 71 </widget>
72 </item> 72 </item>
73 <item> 73 <item>
74 <widget class="QCheckBox" name="force_30fps_mode">
75 <property name="text">
76 <string>Force 30 FPS mode</string>
77 </property>
78 </widget>
79 </item>
80 <item>
74 <layout class="QHBoxLayout" name="horizontalLayout"> 81 <layout class="QHBoxLayout" name="horizontalLayout">
75 <item> 82 <item>
76 <widget class="QLabel" name="label"> 83 <widget class="QLabel" name="label">
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 4b67656ac..b0ca766ec 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -467,9 +467,10 @@ void GameList::LoadInterfaceLayout() {
467const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; 467const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
468 468
469void GameList::RefreshGameDirectory() { 469void GameList::RefreshGameDirectory() {
470 if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { 470 if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) {
471 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); 471 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
472 search_field->clear(); 472 search_field->clear();
473 PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 473 PopulateAsync(UISettings::values.game_directory_path,
474 UISettings::values.game_directory_deepscan);
474 } 475 }
475} 476}
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index ca231d710..bdee44b04 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -213,7 +213,8 @@ GMainWindow::GMainWindow()
213 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); 213 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
214 214
215 game_list->LoadCompatibilityList(); 215 game_list->LoadCompatibilityList();
216 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 216 game_list->PopulateAsync(UISettings::values.game_directory_path,
217 UISettings::values.game_directory_deepscan);
217 218
218 // Show one-time "callout" messages to the user 219 // Show one-time "callout" messages to the user
219 ShowTelemetryCallout(); 220 ShowTelemetryCallout();
@@ -1278,8 +1279,8 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1278 1279
1279 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1280 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1280 if (reload) { 1281 if (reload) {
1281 game_list->PopulateAsync(UISettings::values.gamedir, 1282 game_list->PopulateAsync(UISettings::values.game_directory_path,
1282 UISettings::values.gamedir_deepscan); 1283 UISettings::values.game_directory_deepscan);
1283 } 1284 }
1284 1285
1285 config->Save(); 1286 config->Save();
@@ -1367,7 +1368,8 @@ void GMainWindow::OnMenuInstallToNAND() {
1367 const auto success = [this]() { 1368 const auto success = [this]() {
1368 QMessageBox::information(this, tr("Successfully Installed"), 1369 QMessageBox::information(this, tr("Successfully Installed"),
1369 tr("The file was successfully installed.")); 1370 tr("The file was successfully installed."));
1370 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 1371 game_list->PopulateAsync(UISettings::values.game_directory_path,
1372 UISettings::values.game_directory_deepscan);
1371 }; 1373 };
1372 1374
1373 const auto failed = [this]() { 1375 const auto failed = [this]() {
@@ -1494,8 +1496,8 @@ void GMainWindow::OnMenuInstallToNAND() {
1494void GMainWindow::OnMenuSelectGameListRoot() { 1496void GMainWindow::OnMenuSelectGameListRoot() {
1495 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); 1497 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1496 if (!dir_path.isEmpty()) { 1498 if (!dir_path.isEmpty()) {
1497 UISettings::values.gamedir = dir_path; 1499 UISettings::values.game_directory_path = dir_path;
1498 game_list->PopulateAsync(dir_path, UISettings::values.gamedir_deepscan); 1500 game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
1499 } 1501 }
1500} 1502}
1501 1503
@@ -1517,7 +1519,8 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
1517 : FileUtil::UserPath::NANDDir, 1519 : FileUtil::UserPath::NANDDir,
1518 dir_path.toStdString()); 1520 dir_path.toStdString());
1519 Service::FileSystem::CreateFactories(*vfs); 1521 Service::FileSystem::CreateFactories(*vfs);
1520 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 1522 game_list->PopulateAsync(UISettings::values.game_directory_path,
1523 UISettings::values.game_directory_deepscan);
1521 } 1524 }
1522} 1525}
1523 1526
@@ -1669,8 +1672,8 @@ void GMainWindow::OnConfigure() {
1669 1672
1670 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1673 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1671 if (reload) { 1674 if (reload) {
1672 game_list->PopulateAsync(UISettings::values.gamedir, 1675 game_list->PopulateAsync(UISettings::values.game_directory_path,
1673 UISettings::values.gamedir_deepscan); 1676 UISettings::values.game_directory_deepscan);
1674 } 1677 }
1675 1678
1676 config->Save(); 1679 config->Save();
@@ -1920,7 +1923,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1920 Service::FileSystem::CreateFactories(*vfs); 1923 Service::FileSystem::CreateFactories(*vfs);
1921 1924
1922 if (behavior == ReinitializeKeyBehavior::Warning) { 1925 if (behavior == ReinitializeKeyBehavior::Warning) {
1923 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 1926 game_list->PopulateAsync(UISettings::values.game_directory_path,
1927 UISettings::values.game_directory_deepscan);
1924 } 1928 }
1925} 1929}
1926 1930
@@ -2027,6 +2031,18 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
2027 event->acceptProposedAction(); 2031 event->acceptProposedAction();
2028} 2032}
2029 2033
2034void GMainWindow::keyPressEvent(QKeyEvent* event) {
2035 if (render_window) {
2036 render_window->ForwardKeyPressEvent(event);
2037 }
2038}
2039
2040void GMainWindow::keyReleaseEvent(QKeyEvent* event) {
2041 if (render_window) {
2042 render_window->ForwardKeyReleaseEvent(event);
2043 }
2044}
2045
2030bool GMainWindow::ConfirmChangeGame() { 2046bool GMainWindow::ConfirmChangeGame() {
2031 if (emu_thread == nullptr) 2047 if (emu_thread == nullptr)
2032 return true; 2048 return true;
@@ -2094,7 +2110,8 @@ int main(int argc, char* argv[]) {
2094 QCoreApplication::setOrganizationName("yuzu team"); 2110 QCoreApplication::setOrganizationName("yuzu team");
2095 QCoreApplication::setApplicationName("yuzu"); 2111 QCoreApplication::setApplicationName("yuzu");
2096 2112
2097 QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); 2113 // Enables the core to make the qt created contexts current on std::threads
2114 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
2098 QApplication app(argc, argv); 2115 QApplication app(argc, argv);
2099 2116
2100 // Qt changes the locale and causes issues in float conversion using std::to_string() when 2117 // Qt changes the locale and causes issues in float conversion using std::to_string() when
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 85e3810f2..ce5045819 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -252,4 +252,8 @@ protected:
252 void dropEvent(QDropEvent* event) override; 252 void dropEvent(QDropEvent* event) override;
253 void dragEnterEvent(QDragEnterEvent* event) override; 253 void dragEnterEvent(QDragEnterEvent* event) override;
254 void dragMoveEvent(QDragMoveEvent* event) override; 254 void dragMoveEvent(QDragMoveEvent* event) override;
255
256 // Overrides used to forward signals to the render window when the focus moves out.
257 void keyPressEvent(QKeyEvent* event) override;
258 void keyReleaseEvent(QKeyEvent* event) override;
255}; 259};
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index 45e705b61..dbd318e20 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -55,8 +55,8 @@ struct Values {
55 QString roms_path; 55 QString roms_path;
56 QString symbols_path; 56 QString symbols_path;
57 QString screenshot_path; 57 QString screenshot_path;
58 QString gamedir; 58 QString game_directory_path;
59 bool gamedir_deepscan; 59 bool game_directory_deepscan;
60 QStringList recent_files; 60 QStringList recent_files;
61 61
62 QString theme; 62 QString theme;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index de7a26e14..68a176032 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -19,6 +19,37 @@
19#include "input_common/sdl/sdl.h" 19#include "input_common/sdl/sdl.h"
20#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 20#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
21 21
22class SDLGLContext : public Core::Frontend::GraphicsContext {
23public:
24 explicit SDLGLContext() {
25 // create a hidden window to make the shared context against
26 window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position
27 SDL_WINDOWPOS_UNDEFINED, // y position
28 Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height,
29 SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN);
30 context = SDL_GL_CreateContext(window);
31 }
32
33 ~SDLGLContext() {
34 SDL_GL_DeleteContext(context);
35 SDL_DestroyWindow(window);
36 }
37
38 void MakeCurrent() override {
39 SDL_GL_MakeCurrent(window, context);
40 }
41
42 void DoneCurrent() override {
43 SDL_GL_MakeCurrent(window, nullptr);
44 }
45
46 void SwapBuffers() override {}
47
48private:
49 SDL_Window* window;
50 SDL_GLContext context;
51};
52
22void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 53void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
23 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); 54 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
24 InputCommon::GetMotionEmu()->Tilt(x, y); 55 InputCommon::GetMotionEmu()->Tilt(x, y);
@@ -153,6 +184,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
153 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); 184 SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
154 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); 185 SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
155 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); 186 SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0);
187 SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
156 188
157 std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname, 189 std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname,
158 Common::g_scm_branch, Common::g_scm_desc); 190 Common::g_scm_branch, Common::g_scm_desc);
@@ -171,7 +203,6 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
171 if (fullscreen) { 203 if (fullscreen) {
172 Fullscreen(); 204 Fullscreen();
173 } 205 }
174
175 gl_context = SDL_GL_CreateContext(render_window); 206 gl_context = SDL_GL_CreateContext(render_window);
176 207
177 if (gl_context == nullptr) { 208 if (gl_context == nullptr) {
@@ -278,3 +309,7 @@ void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(
278 309
279 SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); 310 SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second);
280} 311}
312
313std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2::CreateSharedContext() const {
314 return std::make_unique<SDLGLContext>();
315}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index b0d4116cc..17e98227f 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -27,6 +27,8 @@ public:
27 /// Releases the GL context from the caller thread 27 /// Releases the GL context from the caller thread
28 void DoneCurrent() override; 28 void DoneCurrent() override;
29 29
30 std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
31
30 /// Whether the window is still open, and a close request hasn't yet been sent 32 /// Whether the window is still open, and a close request hasn't yet been sent
31 bool IsOpen() const; 33 bool IsOpen() const;
32 34