Compare commits

...

38 Commits

Author SHA1 Message Date
bunnei
a587270489 Merge cda72cde54 into c7f2fb2151 2018-08-23 06:25:14 +00:00
bunnei
c7f2fb2151 Merge pull request #1157 from lioncash/vec
gl_shader_gen: Use a std::vector to represent program code instead of std::array
2018-08-23 02:19:00 -04:00
bunnei
232b0d9d2a Merge pull request #1156 from Lakumakkara/lop3
gl_shader_decompiler: Implement LOP3
2018-08-23 02:16:49 -04:00
literalmente-game
74e08b4800 Swap "Plus" with "Minus" on the controller GUI (#1150)
* Swap "Plus" with "Minus" on the controller GUI

Major fix /s
2018-08-22 18:47:07 -06:00
James Rowe
a1bdc597e9 Merge pull request #1159 from lioncash/fmt
externals: Update fmt to 6201052
2018-08-22 18:46:22 -06:00
bunnei
c5ea6db02d Merge pull request #1137 from lioncash/namespace
renderer_opengl: Namespace OpenGL code
2018-08-22 18:14:48 -04:00
James Rowe
c7c4e6dcba Merge pull request #1158 from lioncash/boost
externals/boost: Update to 1.68.0
2018-08-22 15:38:12 -06:00
Lioncash
c5c0da41b4 externals: Update fmt to 6201052
Previously, we'd get warnings like:

"
c:\projects\yuzu\externals\fmt\include\fmt\format.h(2868): warning
C4127: conditional expression is constant
[C:\projects\yuzu\msvc_build\externals\dynarmic\src\dynarmic.vcxproj]
"

spamming the build output when compiling on Windows. This updates fmt to
include the upstreamed fix that silences this warning.
2018-08-22 17:32:53 -04:00
Lioncash
f835349364 externals/boost: Update to 1.68.0
This updates the submodule to use 1.68.0. Notably, it gets rid of the
silly

"Info: Boost.Config is older than your compiler version - probably
nothing bad will happen - but you may wish to look for an update Boost
version. Define BOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE to suppress this
message."

message that spams the output of the build process on Windows.
2018-08-22 17:15:44 -04:00
Lioncash
12ba80a86c gl_shader_gen: Make ShaderSetup's constructor explicit
Prevents implicit conversions.
2018-08-22 17:04:44 -04:00
Lioncash
1fd979f50a gl_shader_gen: Use a std::vector to represent program code instead of std::array
While convenient as a std::array, it's also quite a large set of data as
well (32KB). It being an array also means data cannot be std::moved. Any
situation where the code is being set or relocated means that a full
copy of that 32KB data must be done.

If we use a std::vector we do need to allocate on the heap, however, it
does allow us to std::move the data we have within the std::vector into
another std::vector instance, eliminating the need to always copy the
program data (as std::move in this case would just transfer the pointers
and bare necessities over to the new vector instance).
2018-08-22 17:04:44 -04:00
Laku
b2ca8089ce more fixes 2018-08-23 00:01:40 +03:00
Laku
e70a3c5a5d fixes 2018-08-22 21:33:32 +03:00
bunnei
d1b1c42c07 Merge pull request #1155 from tech4me/icon-fix
config: Fixed icon size get set to 0
2018-08-22 13:45:22 -04:00
Lioncash
dd35b4b18a renderer_opengl: Namespace OpenGL code
Namespaces all OpenGL code under the OpenGL namespace.

Prevents polluting the global namespace and allows clear distinction
between other renderers' code in the future.
2018-08-22 06:14:47 -04:00
Laku
4877e6c2f6 remove debug logging 2018-08-22 11:45:28 +03:00
Laku
8e8326595f implement lop3 2018-08-22 10:09:44 +03:00
tech4me
8ce02d85e9 config: Fixed icon size get set to 0 2018-08-21 22:36:29 -07:00
bunnei
b38d67d940 Merge pull request #1136 from tech4me/master
qt/main: Port part of citra(#3411), open savedata works
2018-08-22 01:30:08 -04:00
bunnei
cea627b0fc Merge pull request #840 from FearlessTobi/port-3353
Port #3353 from Citra: "citra-qt: Add customizable speed limit target "
2018-08-22 01:19:50 -04:00
bunnei
5abf71fe65 Merge pull request #1154 from OatmealDome/topology-lines
maxwell_to_gl: Implement PrimitiveTopology::Lines
2018-08-22 01:08:34 -04:00
bunnei
eef0c93643 Merge pull request #1141 from FearlessTobi/port-3902
Port #3902 from Citra: "Add restart hotkey & menu option"
2018-08-22 01:07:59 -04:00
bunnei
125d7122ac Merge pull request #1124 from Subv/logic_ops
GPU: Implemented logic ops.
2018-08-22 01:05:25 -04:00
OatmealDome
ad1220e1b3 maxwell_to_gl: Implement PrimitiveTopology::Lines
Used by Splatoon 2's debug menu.
2018-08-22 01:01:06 -04:00
bunnei
92b85fad70 Merge pull request #1147 from lioncash/warn
logging/text_formatter: Use empty braces for initializing CONSOLE_SCREEN_BUFFER_INFO instance
2018-08-22 00:37:59 -04:00
bunnei
cb8b371570 Merge pull request #1151 from bunnei/revert-4a2ee191
Revert "Shader: Use the right sampler type in the TEX, TEXS and TLDS …"
2018-08-22 00:37:30 -04:00
bunnei
38517241ec Merge pull request #1152 from ogniK5377/plu-include
Added missing include for pl:u
2018-08-21 22:41:09 -04:00
bunnei
d63b1d21f1 Revert "Shader: Use the right sampler type in the TEX, TEXS and TLDS instructions."
- This reverts commit 3ef4b3d4b4.
- This commit had broken a lot of games. We really should do a full implementation of this in one change.
2018-08-21 20:07:40 -04:00
Lioncash
5a53d75313 logging/text_formatter: Use empty braces for initializing CONSOLE_SCREEN_BUFFER_INFO instance
The previous form of initializing done here is a C-ism, an empty set of
braces is sufficient for initializing (and doesn't potentially cause
missing brace warnings, given the first member of the struct is a COORD
struct).
2018-08-21 11:31:05 -04:00
fearlessTobi
f2d5b100c2 Port #3902 from Citra: "Add restart hotkey & menu option" 2018-08-21 13:24:55 +02:00
tech4me
cc71832b19 qt/main: Port part of citra(#3411), open savedata works 2018-08-21 02:04:33 -07:00
bunnei
cda72cde54 gl_rasterizer_cache: Remove assert for RecreateSurface type. 2018-08-21 01:07:44 -04:00
bunnei
537376dbd4 gl_rasterizer_cache: Implement compressed texture copies. 2018-08-21 01:07:44 -04:00
bunnei
bce9aafd10 gl_rasterizer_cache: Use CPU address for surface cache key. 2018-08-21 01:07:43 -04:00
Subv
2b9eee4d1e GPU: Implemented the logic op functionality of the GPU.
This will ASSERT if blending is enabled at the same time as logic ops.
2018-08-20 18:44:47 -05:00
Subv
f24ab6d9e6 GLState: Allow enabling/disabling GL_COLOR_LOGIC_OP independently from blending. 2018-08-20 18:43:11 -05:00
Subv
6bcdf37d4f GPU: Added registers for the logicop functionality. 2018-08-20 18:42:36 -05:00
fearlessTobi
ba8ff096fd Port #3353 from Citra 2018-08-21 01:14:06 +02:00
47 changed files with 568 additions and 375 deletions

2
externals/fmt vendored

View File

@@ -42,7 +42,7 @@ void PrintColoredMessage(const Entry& entry) {
return;
}
CONSOLE_SCREEN_BUFFER_INFO original_info = {0};
CONSOLE_SCREEN_BUFFER_INFO original_info = {};
GetConsoleScreenBufferInfo(console_handle, &original_info);
WORD color = 0;

View File

@@ -73,7 +73,7 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
}
std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id) const {
u128 user_id, u64 save_id) {
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
if (type == SaveDataType::SaveData && title_id == 0)

View File

@@ -49,11 +49,11 @@ public:
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id);
private:
VirtualDir dir;
std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id,
u64 save_id) const;
};
} // namespace FileSys

View File

@@ -10,6 +10,7 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "video_core/memory_manager.h"
namespace Service::Nvidia::Devices {

View File

@@ -251,8 +251,8 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) {
return string;
}
void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached) {
if (gpu_addr == 0) {
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
if (vaddr == 0) {
return;
}
@@ -261,19 +261,8 @@ void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached)
// CPU pages, hence why we iterate on a CPU page basis (note: GPU page size is different). This
// assumes the specified GPU address region is contiguous as well.
u64 num_pages = ((gpu_addr + size - 1) >> PAGE_BITS) - (gpu_addr >> PAGE_BITS) + 1;
for (unsigned i = 0; i < num_pages; ++i, gpu_addr += PAGE_SIZE) {
boost::optional<VAddr> maybe_vaddr =
Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(gpu_addr);
// The GPU <-> CPU virtual memory mapping is not 1:1
if (!maybe_vaddr) {
LOG_ERROR(HW_Memory,
"Trying to flush a cached region to an invalid physical address {:016X}",
gpu_addr);
continue;
}
VAddr vaddr = *maybe_vaddr;
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
if (cached) {
@@ -344,29 +333,19 @@ void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
const VAddr overlap_start = std::max(start, region_start);
const VAddr overlap_end = std::min(end, region_end);
const std::vector<Tegra::GPUVAddr> gpu_addresses =
system_instance.GPU().memory_manager->CpuToGpuAddress(overlap_start);
if (gpu_addresses.empty()) {
return;
}
const u64 overlap_size = overlap_end - overlap_start;
for (const auto& gpu_address : gpu_addresses) {
auto& rasterizer = system_instance.Renderer().Rasterizer();
switch (mode) {
case FlushMode::Flush:
rasterizer.FlushRegion(gpu_address, overlap_size);
break;
case FlushMode::Invalidate:
rasterizer.InvalidateRegion(gpu_address, overlap_size);
break;
case FlushMode::FlushAndInvalidate:
rasterizer.FlushAndInvalidateRegion(gpu_address, overlap_size);
break;
}
auto& rasterizer = system_instance.Renderer().Rasterizer();
switch (mode) {
case FlushMode::Flush:
rasterizer.FlushRegion(overlap_start, overlap_size);
break;
case FlushMode::Invalidate:
rasterizer.InvalidateRegion(overlap_start, overlap_size);
break;
case FlushMode::FlushAndInvalidate:
rasterizer.FlushAndInvalidateRegion(overlap_start, overlap_size);
break;
}
};

View File

@@ -11,7 +11,6 @@
#include <boost/icl/interval_map.hpp>
#include "common/common_types.h"
#include "core/memory_hook.h"
#include "video_core/memory_manager.h"
namespace Kernel {
class Process;
@@ -179,7 +178,7 @@ enum class FlushMode {
/**
* Mark each page touching the region as cached.
*/
void RasterizerMarkRegionCached(Tegra::GPUVAddr gpu_addr, u64 size, bool cached);
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
/**
* Flushes and invalidates any externally cached rasterizer resources touching the given virtual

View File

@@ -78,20 +78,29 @@ void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
// values increase the time needed to recover and limit framerate again after spikes.
constexpr microseconds MAX_LAG_TIME_US = 25000us;
if (!Settings::values.toggle_framelimit) {
if (!Settings::values.use_frame_limit) {
return;
}
auto now = Clock::now();
frame_limiting_delta_err += current_system_time_us - previous_system_time_us;
const double sleep_scale = Settings::values.frame_limit / 100.0;
// Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current
// speed percent or it will clamp too much and prevent this from properly limiting to that
// percent. High values means it'll take longer after a slow frame to recover and start
// limiting
const microseconds max_lag_time_us = duration_cast<microseconds>(
std::chrono::duration<double, std::chrono::microseconds::period>(25ms / sleep_scale));
frame_limiting_delta_err += duration_cast<microseconds>(
std::chrono::duration<double, std::chrono::microseconds::period>(
(current_system_time_us - previous_system_time_us) / sleep_scale));
frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime);
frame_limiting_delta_err =
std::clamp(frame_limiting_delta_err, -MAX_LAG_TIME_US, MAX_LAG_TIME_US);
std::clamp(frame_limiting_delta_err, -max_lag_time_us, max_lag_time_us);
if (frame_limiting_delta_err > microseconds::zero()) {
std::this_thread::sleep_for(frame_limiting_delta_err);
auto now_after_sleep = Clock::now();
frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now);
now = now_after_sleep;

View File

@@ -130,7 +130,8 @@ struct Values {
// Renderer
float resolution_factor;
bool toggle_framelimit;
bool use_frame_limit;
u16 frame_limit;
bool use_accurate_framebuffers;
float bg_red;

View File

@@ -106,8 +106,9 @@ TelemetrySession::TelemetrySession() {
Settings::values.use_multi_core);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
Settings::values.resolution_factor);
AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
Settings::values.toggle_framelimit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
Settings::values.use_frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers",
Settings::values.use_accurate_framebuffers);
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",

View File

@@ -311,6 +311,25 @@ public:
AlwaysOld = 8,
};
enum class LogicOperation : u32 {
Clear = 0x1500,
And = 0x1501,
AndReverse = 0x1502,
Copy = 0x1503,
AndInverted = 0x1504,
NoOp = 0x1505,
Xor = 0x1506,
Or = 0x1507,
Nor = 0x1508,
Equiv = 0x1509,
Invert = 0x150A,
OrReverse = 0x150B,
CopyInverted = 0x150C,
OrInverted = 0x150D,
Nand = 0x150E,
Set = 0x150F,
};
struct Cull {
enum class FrontFace : u32 {
ClockWise = 0x0900,
@@ -695,7 +714,14 @@ public:
Cull cull;
INSERT_PADDING_WORDS(0x2B);
INSERT_PADDING_WORDS(0x28);
struct {
u32 enable;
LogicOperation operation;
} logic_op;
INSERT_PADDING_WORDS(0x1);
union {
u32 raw;
@@ -942,6 +968,7 @@ ASSERT_REG_POSITION(draw, 0x585);
ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
ASSERT_REG_POSITION(cull, 0x646);
ASSERT_REG_POSITION(logic_op, 0x671);
ASSERT_REG_POSITION(clear_buffers, 0x674);
ASSERT_REG_POSITION(query, 0x6C0);
ASSERT_REG_POSITION(vertex_array[0], 0x700);

View File

@@ -280,6 +280,19 @@ union Instruction {
BitField<56, 1, u64> invert_b;
} lop32i;
union {
BitField<28, 8, u64> imm_lut28;
BitField<48, 8, u64> imm_lut48;
u32 GetImmLut28() const {
return static_cast<u32>(imm_lut28);
}
u32 GetImmLut48() const {
return static_cast<u32>(imm_lut48);
}
} lop3;
u32 GetImm20_19() const {
u32 imm{static_cast<u32>(imm20_19)};
imm <<= 12;
@@ -650,6 +663,9 @@ public:
LOP_R,
LOP_IMM,
LOP32I,
LOP3_C,
LOP3_R,
LOP3_IMM,
MOV_C,
MOV_R,
MOV_IMM,
@@ -872,6 +888,9 @@ private:
INST("0101110001000---", Id::LOP_R, Type::ArithmeticInteger, "LOP_R"),
INST("0011100001000---", Id::LOP_IMM, Type::ArithmeticInteger, "LOP_IMM"),
INST("000001----------", Id::LOP32I, Type::ArithmeticIntegerImmediate, "LOP32I"),
INST("0000001---------", Id::LOP3_C, Type::ArithmeticInteger, "LOP3_C"),
INST("0101101111100---", Id::LOP3_R, Type::ArithmeticInteger, "LOP3_R"),
INST("0011110---------", Id::LOP3_IMM, Type::ArithmeticInteger, "LOP3_IMM"),
INST("0100110001001---", Id::SHL_C, Type::Shift, "SHL_C"),
INST("0101110001001---", Id::SHL_R, Type::Shift, "SHL_R"),
INST("0011100-01001---", Id::SHL_IMM, Type::Shift, "SHL_IMM"),

View File

@@ -27,14 +27,14 @@ public:
virtual void FlushAll() = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
virtual void FlushRegion(Tegra::GPUVAddr addr, u64 size) = 0;
virtual void FlushRegion(VAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be invalidated
virtual void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) = 0;
virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
/// and invalidated
virtual void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) = 0;
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
/// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
virtual bool AccelerateDisplayTransfer(const void* config) {

View File

@@ -18,7 +18,7 @@ RendererBase::~RendererBase() = default;
void RendererBase::RefreshBaseSettings() {
UpdateCurrentFramebufferLayout();
renderer_settings.use_framelimiter = Settings::values.toggle_framelimit;
renderer_settings.use_framelimiter = Settings::values.use_frame_limit;
}
void RendererBase::UpdateCurrentFramebufferLayout() {

View File

@@ -25,6 +25,8 @@
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
namespace OpenGL {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using PixelFormat = SurfaceParams::PixelFormat;
using SurfaceType = SurfaceParams::SurfaceType;
@@ -179,7 +181,7 @@ static GLShader::ProgramCode GetShaderProgramCode(Maxwell::ShaderProgram program
auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
// Fetch program code from memory
GLShader::ProgramCode program_code;
GLShader::ProgramCode program_code(GLShader::MAX_PROGRAM_CODE_LENGTH);
auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)];
const u64 gpu_address{gpu.regs.code_address.CodeAddress() + shader_config.offset};
const boost::optional<VAddr> cpu_address{gpu.memory_manager.GpuToCpuAddress(gpu_address)};
@@ -450,6 +452,7 @@ void RasterizerOpenGL::DrawArrays() {
SyncDepthTestState();
SyncBlendState();
SyncLogicOpState();
SyncCullMode();
// TODO(bunnei): Sync framebuffer_scale uniform here
@@ -542,17 +545,17 @@ void RasterizerOpenGL::FlushAll() {
res_cache.FlushRegion(0, Kernel::VMManager::MAX_ADDRESS);
}
void RasterizerOpenGL::FlushRegion(Tegra::GPUVAddr addr, u64 size) {
void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
res_cache.FlushRegion(addr, size);
}
void RasterizerOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
res_cache.InvalidateRegion(addr, size);
}
void RasterizerOpenGL::FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) {
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
res_cache.FlushRegion(addr, size);
res_cache.InvalidateRegion(addr, size);
@@ -847,6 +850,9 @@ void RasterizerOpenGL::SyncBlendState() {
if (!state.blend.enabled)
return;
ASSERT_MSG(regs.logic_op.enable == 0,
"Blending and logic op can't be enabled at the same time.");
ASSERT_MSG(regs.independent_blend_enable == 1, "Only independent blending is implemented");
ASSERT_MSG(!regs.independent_blend[0].separate_alpha, "Unimplemented");
state.blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[0].equation_rgb);
@@ -856,3 +862,19 @@ void RasterizerOpenGL::SyncBlendState() {
state.blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_source_a);
state.blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[0].factor_dest_a);
}
void RasterizerOpenGL::SyncLogicOpState() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
// TODO(Subv): Support more than just render target 0.
state.logic_op.enabled = regs.logic_op.enable != 0;
if (!state.logic_op.enabled)
return;
ASSERT_MSG(regs.blend.enable == 0, "Blending and logic op can't be enabled at the same time.");
state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
}
} // namespace OpenGL

View File

@@ -22,12 +22,14 @@
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
struct ScreenInfo;
namespace Core::Frontend {
class EmuWindow;
}
namespace OpenGL {
struct ScreenInfo;
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
public:
explicit RasterizerOpenGL(Core::Frontend::EmuWindow& renderer, ScreenInfo& info);
@@ -37,9 +39,9 @@ public:
void Clear() override;
void NotifyMaxwellRegisterChanged(u32 method) override;
void FlushAll() override;
void FlushRegion(Tegra::GPUVAddr addr, u64 size) override;
void InvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
void FlushAndInvalidateRegion(Tegra::GPUVAddr addr, u64 size) override;
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
bool AccelerateDisplayTransfer(const void* config) override;
bool AccelerateTextureCopy(const void* config) override;
bool AccelerateFill(const void* config) override;
@@ -142,6 +144,9 @@ private:
/// Syncs the blend state to match the guest state
void SyncBlendState();
/// Syncs the LogicOp state to match the guest state
void SyncLogicOpState();
bool has_ARB_direct_state_access = false;
bool has_ARB_separate_shader_objects = false;
bool has_ARB_vertex_attrib_binding = false;
@@ -181,3 +186,5 @@ private:
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;
};
} // namespace OpenGL

View File

@@ -19,6 +19,8 @@
#include "video_core/textures/decoders.h"
#include "video_core/utils.h"
namespace OpenGL {
using SurfaceType = SurfaceParams::SurfaceType;
using PixelFormat = SurfaceParams::PixelFormat;
using ComponentType = SurfaceParams::ComponentType;
@@ -33,9 +35,9 @@ struct FormatTuple {
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
const Tegra::Texture::FullTextureInfo& config) {
const auto& gpu = Core::System::GetInstance().GPU();
SurfaceParams params{};
params.addr = config.tic.Address();
params.cpu_addr = *gpu.memory_manager->GpuToCpuAddress(config.tic.Address());
params.is_tiled = config.tic.IsTiled();
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
params.pixel_format =
@@ -53,9 +55,9 @@ struct FormatTuple {
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(
const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config) {
const auto& gpu = Core::System::GetInstance().GPU();
SurfaceParams params{};
params.addr = config.Address();
params.cpu_addr = *gpu.memory_manager->GpuToCpuAddress(config.Address());
params.is_tiled = true;
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
@@ -73,9 +75,9 @@ struct FormatTuple {
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
Tegra::GPUVAddr zeta_address,
Tegra::DepthFormat format) {
const auto& gpu = Core::System::GetInstance().GPU();
SurfaceParams params{};
params.addr = zeta_address;
params.cpu_addr = *gpu.memory_manager->GpuToCpuAddress(zeta_address);
params.is_tiled = true;
params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
params.pixel_format = PixelFormatFromDepthFormat(format);
@@ -165,11 +167,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
return format;
}
VAddr SurfaceParams::GetCpuAddr() const {
const auto& gpu = Core::System::GetInstance().GPU();
return *gpu.memory_manager->GpuToCpuAddress(addr);
}
static bool IsPixelFormatASTC(PixelFormat format) {
switch (format) {
case PixelFormat::ASTC_2D_4X4:
@@ -214,33 +211,28 @@ static bool IsFormatBCn(PixelFormat format) {
}
template <bool morton_to_gl, PixelFormat format>
void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer,
Tegra::GPUVAddr addr) {
void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer, VAddr addr) {
constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
const auto& gpu = Core::System::GetInstance().GPU();
if (morton_to_gl) {
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
// pixel values.
const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
const std::vector<u8> data =
Tegra::Texture::UnswizzleTexture(*gpu.memory_manager->GpuToCpuAddress(addr), tile_size,
bytes_per_pixel, stride, height, block_height);
const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
addr, tile_size, bytes_per_pixel, stride, height, block_height);
const size_t size_to_copy{std::min(gl_buffer.size(), data.size())};
gl_buffer.assign(data.begin(), data.begin() + size_to_copy);
} else {
// TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
// check the configuration for this and perform more generic un/swizzle
LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
VideoCore::MortonCopyPixels128(
stride, height, bytes_per_pixel, gl_bytes_per_pixel,
Memory::GetPointer(*gpu.memory_manager->GpuToCpuAddress(addr)), gl_buffer.data(),
morton_to_gl);
VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
Memory::GetPointer(addr), gl_buffer.data(), morton_to_gl);
}
}
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
SurfaceParams::MaxPixelFormat>
morton_to_gl_fns = {
// clang-format off
@@ -295,7 +287,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPU
// clang-format on
};
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, Tegra::GPUVAddr),
static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr),
SurfaceParams::MaxPixelFormat>
gl_to_morton_fns = {
// clang-format off
@@ -530,7 +522,7 @@ MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64
void CachedSurface::LoadGLBuffer() {
ASSERT(params.type != SurfaceType::Fill);
const u8* const texture_src_data = Memory::GetPointer(params.GetCpuAddr());
const u8* const texture_src_data = Memory::GetPointer(params.cpu_addr);
ASSERT(texture_src_data);
@@ -543,7 +535,7 @@ void CachedSurface::LoadGLBuffer() {
gl_buffer.resize(copy_size);
morton_to_gl_fns[static_cast<size_t>(params.pixel_format)](
params.width, params.block_height, params.height, gl_buffer, params.addr);
params.width, params.block_height, params.height, gl_buffer, params.cpu_addr);
} else {
const u8* const texture_src_data_end = texture_src_data + copy_size;
@@ -555,7 +547,7 @@ void CachedSurface::LoadGLBuffer() {
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer() {
u8* const dst_buffer = Memory::GetPointer(params.GetCpuAddr());
u8* const dst_buffer = Memory::GetPointer(params.cpu_addr);
ASSERT(dst_buffer);
ASSERT(gl_buffer.size() ==
@@ -570,7 +562,7 @@ void CachedSurface::FlushGLBuffer() {
std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes);
} else {
gl_to_morton_fns[static_cast<size_t>(params.pixel_format)](
params.width, params.block_height, params.height, gl_buffer, params.addr);
params.width, params.block_height, params.height, gl_buffer, params.cpu_addr);
}
}
@@ -754,17 +746,12 @@ void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
if (params.addr == 0 || params.height * params.width == 0) {
if (params.cpu_addr == 0 || params.height * params.width == 0) {
return {};
}
const auto& gpu = Core::System::GetInstance().GPU();
// Don't try to create any entries in the cache if the address of the texture is invalid.
if (gpu.memory_manager->GpuToCpuAddress(params.addr) == boost::none)
return {};
// Look up surface in the cache based on address
const auto& search{surface_cache.find(params.addr)};
const auto& search{surface_cache.find(params.cpu_addr)};
Surface surface;
if (search != surface_cache.end()) {
surface = search->second;
@@ -797,9 +784,6 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
const auto& params{surface->GetSurfaceParams()};
ASSERT(params.type == new_params.type);
ASSERT_MSG(params.GetCompressionFactor(params.pixel_format) == 1,
"Compressed texture reinterpretation is not supported");
// Create a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{std::make_shared<CachedSurface>(new_params)};
@@ -816,9 +800,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type,
params.SizeInBytes(), nullptr);
if (source_format.compressed) {
glGetCompressedTextureImage(surface->Texture().handle, 0, params.SizeInBytes(), nullptr);
} else {
glGetTextureImage(surface->Texture().handle, 0, source_format.format, source_format.type,
params.SizeInBytes(), nullptr);
}
// If the new texture is bigger than the previous one, we need to fill in the rest with data
// from the CPU.
if (params.SizeInBytes() < new_params.SizeInBytes()) {
@@ -832,10 +819,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
"reinterpretation but the texture is tiled.");
}
size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes();
auto address = Core::System::GetInstance().GPU().memory_manager->GpuToCpuAddress(
new_params.addr + params.SizeInBytes());
std::vector<u8> data(remaining_size);
Memory::ReadBlock(*address, data.data(), data.size());
Memory::ReadBlock(new_params.cpu_addr + params.SizeInBytes(), data.data(), data.size());
glBufferSubData(GL_PIXEL_PACK_BUFFER, params.SizeInBytes(), remaining_size, data.data());
}
@@ -844,9 +829,32 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface,
const auto& dest_rect{new_params.GetRect()};
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle);
glTextureSubImage2D(
new_surface->Texture().handle, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()),
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, dest_format.type, nullptr);
if (dest_format.compressed) {
OpenGLState cur_state = OpenGLState::GetCurState();
GLuint old_tex = cur_state.texture_units[0].texture_2d;
cur_state.texture_units[0].texture_2d = new_surface->Texture().handle;
cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(new_params.width * CachedSurface::GetGLBytesPerPixel(new_params.pixel_format) % 4 ==
0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width));
glActiveTexture(GL_TEXTURE0);
glCompressedTexImage2D(GL_TEXTURE_2D, 0, dest_format.internal_format,
static_cast<GLsizei>(dest_rect.GetWidth()),
static_cast<GLsizei>(dest_rect.GetHeight()), 0,
static_cast<GLsizei>(new_params.SizeInBytes()), nullptr);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
cur_state.texture_units[0].texture_2d = old_tex;
cur_state.Apply();
} else {
glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0,
static_cast<GLsizei>(dest_rect.GetWidth()),
static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format,
dest_format.type, nullptr);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
pbo.Release();
@@ -868,9 +876,8 @@ Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr cpu_addr) const {
std::vector<Surface> surfaces;
for (const auto& surface : surface_cache) {
const auto& params = surface.second->GetSurfaceParams();
const VAddr surface_cpu_addr = params.GetCpuAddr();
if (cpu_addr >= surface_cpu_addr && cpu_addr < (surface_cpu_addr + params.size_in_bytes)) {
ASSERT_MSG(cpu_addr == surface_cpu_addr, "overlapping surfaces are unsupported");
if (cpu_addr >= params.cpu_addr && cpu_addr < (params.cpu_addr + params.size_in_bytes)) {
ASSERT_MSG(cpu_addr == params.cpu_addr, "overlapping surfaces are unsupported");
surfaces.push_back(surface.second);
}
}
@@ -884,13 +891,13 @@ Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr cpu_addr) const {
return surfaces[0];
}
void RasterizerCacheOpenGL::FlushRegion(Tegra::GPUVAddr /*addr*/, size_t /*size*/) {
void RasterizerCacheOpenGL::FlushRegion(VAddr /*addr*/, size_t /*size*/) {
// TODO(bunnei): This is unused in the current implementation of the rasterizer cache. We should
// probably implement this in the future, but for now, the `use_accurate_framebufers` setting
// can be used to always flush.
}
void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, size_t size) {
void RasterizerCacheOpenGL::InvalidateRegion(VAddr addr, size_t size) {
for (auto iter = surface_cache.cbegin(); iter != surface_cache.cend();) {
const auto& surface{iter->second};
const auto& params{surface->GetSurfaceParams()};
@@ -905,27 +912,27 @@ void RasterizerCacheOpenGL::InvalidateRegion(Tegra::GPUVAddr addr, size_t size)
void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) {
const auto& params{surface->GetSurfaceParams()};
const auto& search{surface_cache.find(params.addr)};
const auto& search{surface_cache.find(params.cpu_addr)};
if (search != surface_cache.end()) {
// Registered already
return;
}
surface_cache[params.addr] = surface;
UpdatePagesCachedCount(params.addr, params.size_in_bytes, 1);
surface_cache[params.cpu_addr] = surface;
UpdatePagesCachedCount(params.cpu_addr, params.size_in_bytes, 1);
}
void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) {
const auto& params{surface->GetSurfaceParams()};
const auto& search{surface_cache.find(params.addr)};
const auto& search{surface_cache.find(params.cpu_addr)};
if (search == surface_cache.end()) {
// Unregistered already
return;
}
UpdatePagesCachedCount(params.addr, params.size_in_bytes, -1);
UpdatePagesCachedCount(params.cpu_addr, params.size_in_bytes, -1);
surface_cache.erase(search);
}
@@ -934,10 +941,10 @@ constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
}
void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
const u64 num_pages = ((addr + size - 1) >> Tegra::MemoryManager::PAGE_BITS) -
(addr >> Tegra::MemoryManager::PAGE_BITS) + 1;
const u64 page_start = addr >> Tegra::MemoryManager::PAGE_BITS;
void RasterizerCacheOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
const u64 num_pages =
((addr + size - 1) >> Memory::PAGE_BITS) - (addr >> Memory::PAGE_BITS) + 1;
const u64 page_start = addr >> Memory::PAGE_BITS;
const u64 page_end = page_start + num_pages;
// Interval maps will erase segments if count reaches 0, so if delta is negative we have to
@@ -950,10 +957,8 @@ void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 siz
const auto interval = pair.first & pages_interval;
const int count = pair.second;
const Tegra::GPUVAddr interval_start_addr = boost::icl::first(interval)
<< Tegra::MemoryManager::PAGE_BITS;
const Tegra::GPUVAddr interval_end_addr = boost::icl::last_next(interval)
<< Tegra::MemoryManager::PAGE_BITS;
const VAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS;
const VAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS;
const u64 interval_size = interval_end_addr - interval_start_addr;
if (delta > 0 && count == delta)
@@ -967,3 +972,5 @@ void RasterizerCacheOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 siz
if (delta < 0)
cached_pages.add({pages_interval, delta});
}
} // namespace OpenGL

View File

@@ -16,6 +16,8 @@
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/textures/texture.h"
namespace OpenGL {
class CachedSurface;
using Surface = std::shared_ptr<CachedSurface>;
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
@@ -626,12 +628,9 @@ struct SurfaceParams {
GetFormatBpp(pixel_format) / CHAR_BIT;
}
/// Returns the CPU virtual address for this surface
VAddr GetCpuAddr() const;
/// Returns true if the specified region overlaps with this surface's region in Switch memory
bool IsOverlappingRegion(Tegra::GPUVAddr region_addr, size_t region_size) const {
return addr <= (region_addr + region_size) && region_addr <= (addr + size_in_bytes);
bool IsOverlappingRegion(VAddr region_addr, size_t region_size) const {
return cpu_addr <= (region_addr + region_size) && region_addr <= (cpu_addr + size_in_bytes);
}
/// Creates SurfaceParams from a texture configuration
@@ -647,9 +646,9 @@ struct SurfaceParams {
Tegra::DepthFormat format);
bool operator==(const SurfaceParams& other) const {
return std::tie(addr, is_tiled, block_height, pixel_format, component_type, type, width,
return std::tie(cpu_addr, is_tiled, block_height, pixel_format, component_type, type, width,
height, unaligned_height, size_in_bytes) ==
std::tie(other.addr, other.is_tiled, other.block_height, other.pixel_format,
std::tie(other.cpu_addr, other.is_tiled, other.block_height, other.pixel_format,
other.component_type, other.type, other.width, other.height,
other.unaligned_height, other.size_in_bytes);
}
@@ -664,7 +663,7 @@ struct SurfaceParams {
std::tie(other.pixel_format, other.type, other.cache_width, other.cache_height);
}
Tegra::GPUVAddr addr;
VAddr cpu_addr;
bool is_tiled;
u32 block_height;
PixelFormat pixel_format;
@@ -732,10 +731,10 @@ public:
Surface TryFindFramebufferSurface(VAddr cpu_addr) const;
/// Write any cached resources overlapping the region back to memory (if dirty)
void FlushRegion(Tegra::GPUVAddr addr, size_t size);
void FlushRegion(VAddr addr, size_t size);
/// Mark the specified region as being invalidated
void InvalidateRegion(Tegra::GPUVAddr addr, size_t size);
void InvalidateRegion(VAddr addr, size_t size);
private:
void LoadSurface(const Surface& surface);
@@ -751,11 +750,13 @@ private:
void UnregisterSurface(const Surface& surface);
/// Increase/decrease the number of surface in pages touching the specified region
void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta);
void UpdatePagesCachedCount(VAddr addr, u64 size, int delta);
std::unordered_map<Tegra::GPUVAddr, Surface> surface_cache;
std::unordered_map<VAddr, Surface> surface_cache;
PageMap cached_pages;
OGLFramebuffer read_framebuffer;
OGLFramebuffer draw_framebuffer;
};
} // namespace OpenGL

View File

@@ -10,6 +10,8 @@
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_state.h"
namespace OpenGL {
class OGLTexture : private NonCopyable {
public:
OGLTexture() = default;
@@ -331,3 +333,5 @@ public:
GLuint handle = 0;
};
} // namespace OpenGL

View File

@@ -15,7 +15,7 @@
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
namespace GLShader::Decompiler {
namespace OpenGL::GLShader::Decompiler {
using Tegra::Shader::Attribute;
using Tegra::Shader::Instruction;
@@ -440,12 +440,13 @@ public:
}
declarations.AddNewLine();
const auto& samplers = GetSamplers();
for (const auto& sampler : samplers) {
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
';');
// Append the sampler2D array for the used textures.
size_t num_samplers = GetSamplers().size();
if (num_samplers > 0) {
declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' +
std::to_string(num_samplers) + "];");
declarations.AddNewLine();
}
declarations.AddNewLine();
}
/// Returns a list of constant buffer declarations
@@ -457,14 +458,13 @@ public:
}
/// Returns a list of samplers used in the shader
const std::vector<SamplerEntry>& GetSamplers() const {
std::vector<SamplerEntry> GetSamplers() const {
return used_samplers;
}
/// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
/// necessary.
std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
bool is_array) {
std::string AccessSampler(const Sampler& sampler) {
size_t offset = static_cast<size_t>(sampler.index.Value());
// If this sampler has already been used, return the existing mapping.
@@ -473,13 +473,12 @@ public:
[&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
if (itr != used_samplers.end()) {
ASSERT(itr->GetType() == type && itr->IsArray() == is_array);
return itr->GetName();
}
// Otherwise create a new mapping for this sampler
size_t next_index = used_samplers.size();
SamplerEntry entry{stage, offset, next_index, type, is_array};
SamplerEntry entry{stage, offset, next_index};
used_samplers.emplace_back(entry);
return entry.GetName();
}
@@ -657,8 +656,8 @@ private:
}
/// Generates code representing a texture sampler.
std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) {
return regs.AccessSampler(sampler, type, is_array);
std::string GetSampler(const Sampler& sampler) {
return regs.AccessSampler(sampler);
}
/**
@@ -850,6 +849,33 @@ private:
}
}
void WriteLop3Instruction(Register dest, const std::string& op_a, const std::string& op_b,
const std::string& op_c, const std::string& imm_lut) {
if (dest == Tegra::Shader::Register::ZeroIndex) {
return;
}
static constexpr std::array<const char*, 32> shift_amounts = {
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21",
"22", "23", "24", "25", "26", "27", "28", "29", "30", "31"};
std::string result;
result += '(';
for (size_t i = 0; i < shift_amounts.size(); ++i) {
if (i)
result += '|';
result += "(((" + imm_lut + " >> (((" + op_c + " >> " + shift_amounts[i] +
") & 1) | ((" + op_b + " >> " + shift_amounts[i] + ") & 1) << 1 | ((" + op_a +
" >> " + shift_amounts[i] + ") & 1) << 2)) & 1) << " + shift_amounts[i] + ")";
}
result += ')';
regs.SetRegisterToInteger(dest, true, 0, result, 1, 1);
}
void WriteTexsInstruction(const Instruction& instr, const std::string& coord,
const std::string& texture) {
// Add an extra scope and declare the texture coords inside to prevent
@@ -1298,6 +1324,20 @@ private:
instr.alu.lop.pred_result_mode, instr.alu.lop.pred48);
break;
}
case OpCode::Id::LOP3_C:
case OpCode::Id::LOP3_R:
case OpCode::Id::LOP3_IMM: {
std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
std::string lut;
if (opcode->GetId() == OpCode::Id::LOP3_R) {
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')';
} else {
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut48()) + ')';
}
WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut);
break;
}
case OpCode::Id::IMNMX_C:
case OpCode::Id::IMNMX_R:
case OpCode::Id::IMNMX_IMM: {
@@ -1556,39 +1596,10 @@ private:
break;
}
case OpCode::Id::TEX: {
ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented");
std::string coord{};
switch (instr.tex.texture_type) {
case Tegra::Shader::TextureType::Texture2D: {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
break;
}
case Tegra::Shader::TextureType::Texture3D: {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
std::string z = regs.GetRegisterAsFloat(instr.gpr20);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
break;
}
case Tegra::Shader::TextureType::TextureCube: {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
ASSERT(instr.gpr20.Value() == Register::ZeroIndex);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
break;
}
default:
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
static_cast<u32>(instr.tex.texture_type.Value()));
UNREACHABLE();
}
const std::string sampler =
GetSampler(instr.sampler, instr.tex.texture_type, instr.tex.array);
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
const std::string sampler = GetSampler(instr.sampler);
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
shader.AddLine("{");
@@ -1610,72 +1621,20 @@ private:
break;
}
case OpCode::Id::TEXS: {
std::string coord{};
switch (instr.texs.GetTextureType()) {
case Tegra::Shader::TextureType::Texture2D: {
if (instr.texs.IsArrayTexture()) {
std::string index = regs.GetRegisterAsInteger(instr.gpr8);
std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
std::string y = regs.GetRegisterAsFloat(instr.gpr20);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");";
} else {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr20);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
}
break;
}
case Tegra::Shader::TextureType::Texture3D: {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr20);
std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
break;
}
case Tegra::Shader::TextureType::TextureCube: {
std::string x = regs.GetRegisterAsFloat(instr.gpr8);
std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
std::string z = regs.GetRegisterAsFloat(instr.gpr20);
coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
break;
}
default:
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
static_cast<u32>(instr.texs.GetTextureType()));
UNREACHABLE();
}
const std::string sampler = GetSampler(instr.sampler, instr.texs.GetTextureType(),
instr.texs.IsArrayTexture());
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
const std::string sampler = GetSampler(instr.sampler);
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
const std::string texture = "texture(" + sampler + ", coords)";
WriteTexsInstruction(instr, coord, texture);
break;
}
case OpCode::Id::TLDS: {
ASSERT(instr.tlds.GetTextureType() == Tegra::Shader::TextureType::Texture2D);
ASSERT(instr.tlds.IsArrayTexture() == false);
std::string coord{};
switch (instr.tlds.GetTextureType()) {
case Tegra::Shader::TextureType::Texture2D: {
if (instr.tlds.IsArrayTexture()) {
LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture");
UNREACHABLE();
} else {
std::string x = regs.GetRegisterAsInteger(instr.gpr8);
std::string y = regs.GetRegisterAsInteger(instr.gpr20);
coord = "ivec2 coords = ivec2(" + x + ", " + y + ");";
}
break;
}
default:
LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
static_cast<u32>(instr.tlds.GetTextureType()));
UNREACHABLE();
}
const std::string sampler = GetSampler(instr.sampler, instr.tlds.GetTextureType(),
instr.tlds.IsArrayTexture());
const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
const std::string op_b = regs.GetRegisterAsInteger(instr.gpr20);
const std::string sampler = GetSampler(instr.sampler);
const std::string coord = "ivec2 coords = ivec2(" + op_a + ", " + op_b + ");";
const std::string texture = "texelFetch(" + sampler + ", coords, 0)";
WriteTexsInstruction(instr, coord, texture);
break;
@@ -1698,8 +1657,7 @@ private:
UNREACHABLE();
}
const std::string sampler =
GetSampler(instr.sampler, instr.tld4.texture_type, instr.tld4.array);
const std::string sampler = GetSampler(instr.sampler);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
shader.AddLine("{");
@@ -1725,8 +1683,7 @@ private:
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
const std::string sampler =
GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
const std::string sampler = GetSampler(instr.sampler);
const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4s.component) + ')';
@@ -2260,4 +2217,4 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
return boost::none;
}
} // namespace GLShader::Decompiler
} // namespace OpenGL::GLShader::Decompiler

View File

@@ -12,7 +12,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
namespace GLShader::Decompiler {
namespace OpenGL::GLShader::Decompiler {
using Tegra::Engines::Maxwell3D;
@@ -22,4 +22,4 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
Maxwell3D::Regs::ShaderStage stage,
const std::string& suffix);
} // namespace GLShader::Decompiler
} // namespace OpenGL::GLShader::Decompiler

View File

@@ -7,7 +7,7 @@
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
namespace GLShader {
namespace OpenGL::GLShader {
using Tegra::Engines::Maxwell3D;
@@ -103,4 +103,4 @@ void main() {
return {out, program.second};
}
} // namespace GLShader
} // namespace OpenGL::GLShader

View File

@@ -9,15 +9,14 @@
#include <type_traits>
#include <utility>
#include <vector>
#include <boost/functional/hash.hpp>
#include "common/common_types.h"
#include "common/hash.h"
#include "video_core/engines/shader_bytecode.h"
namespace GLShader {
namespace OpenGL::GLShader {
constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x1000};
using ProgramCode = std::array<u64, MAX_PROGRAM_CODE_LENGTH>;
using ProgramCode = std::vector<u64>;
class ConstBufferEntry {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
@@ -73,9 +72,8 @@ class SamplerEntry {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
public:
SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index,
Tegra::Shader::TextureType type, bool is_array)
: offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {}
SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index)
: offset(offset), stage(stage), sampler_index(index) {}
size_t GetOffset() const {
return offset;
@@ -90,41 +88,8 @@ public:
}
std::string GetName() const {
return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '_' +
std::to_string(sampler_index);
}
std::string GetTypeString() const {
using Tegra::Shader::TextureType;
std::string glsl_type;
switch (type) {
case TextureType::Texture1D:
glsl_type = "sampler1D";
break;
case TextureType::Texture2D:
glsl_type = "sampler2D";
break;
case TextureType::Texture3D:
glsl_type = "sampler3D";
break;
case TextureType::TextureCube:
glsl_type = "samplerCube";
break;
default:
UNIMPLEMENTED();
}
if (is_array)
glsl_type += "Array";
return glsl_type;
}
Tegra::Shader::TextureType GetType() const {
return type;
}
bool IsArray() const {
return is_array;
return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' +
std::to_string(sampler_index) + ']';
}
static std::string GetArrayName(Maxwell::ShaderStage stage) {
@@ -135,14 +100,11 @@ private:
static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = {
"tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs",
};
/// Offset in TSC memory from which to read the sampler object, as specified by the sampling
/// instruction.
size_t offset;
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc)
bool is_array; ///< Whether the texture is being sampled as an array texture or not.
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
};
struct ShaderEntries {
@@ -153,8 +115,8 @@ struct ShaderEntries {
using ProgramResult = std::pair<std::string, ShaderEntries>;
struct ShaderSetup {
ShaderSetup(const ProgramCode& program_code) {
program.code = program_code;
explicit ShaderSetup(ProgramCode program_code) {
program.code = std::move(program_code);
}
struct {
@@ -173,8 +135,8 @@ struct ShaderSetup {
}
/// Used in scenarios where we have a dual vertex shaders
void SetProgramB(const ProgramCode& program_b) {
program.code_b = program_b;
void SetProgramB(ProgramCode program_b) {
program.code_b = std::move(program_b);
has_program_b = true;
}
@@ -184,13 +146,18 @@ struct ShaderSetup {
private:
u64 GetNewHash() const {
size_t hash = 0;
const u64 hash_a = Common::ComputeHash64(program.code.data(), program.code.size());
boost::hash_combine(hash, hash_a);
if (has_program_b) {
// Compute hash over dual shader programs
return Common::ComputeHash64(&program, sizeof(program));
} else {
// Compute hash over a single shader program
return Common::ComputeHash64(&program.code, program.code.size());
const u64 hash_b = Common::ComputeHash64(program.code_b.data(), program.code_b.size());
boost::hash_combine(hash, hash_b);
}
return hash;
}
u64 program_code_hash{};
@@ -229,20 +196,20 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup, const MaxwellVSConf
*/
ProgramResult GenerateFragmentShader(const ShaderSetup& setup, const MaxwellFSConfig& config);
} // namespace GLShader
} // namespace OpenGL::GLShader
namespace std {
template <>
struct hash<GLShader::MaxwellVSConfig> {
size_t operator()(const GLShader::MaxwellVSConfig& k) const {
struct hash<OpenGL::GLShader::MaxwellVSConfig> {
size_t operator()(const OpenGL::GLShader::MaxwellVSConfig& k) const {
return k.Hash();
}
};
template <>
struct hash<GLShader::MaxwellFSConfig> {
size_t operator()(const GLShader::MaxwellFSConfig& k) const {
struct hash<OpenGL::GLShader::MaxwellFSConfig> {
size_t operator()(const OpenGL::GLShader::MaxwellFSConfig& k) const {
return k.Hash();
}
};

View File

@@ -7,7 +7,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
namespace GLShader {
namespace OpenGL::GLShader {
namespace Impl {
static void SetShaderUniformBlockBinding(GLuint shader, const char* name,
@@ -49,4 +49,4 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh
instance_id[0] = state.current_instance;
}
} // namespace GLShader
} // namespace OpenGL::GLShader

View File

@@ -12,7 +12,7 @@
#include "video_core/renderer_opengl/gl_shader_gen.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"
namespace GLShader {
namespace OpenGL::GLShader {
/// Number of OpenGL texture samplers that can be used in the fragment shader
static constexpr size_t NumTextureSamplers = 32;
@@ -171,4 +171,4 @@ private:
OGLPipeline pipeline;
};
} // namespace GLShader
} // namespace OpenGL::GLShader

View File

@@ -8,7 +8,7 @@
#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
namespace GLShader {
namespace OpenGL::GLShader {
GLuint LoadShader(const char* source, GLenum type) {
const char* debug_type;
@@ -47,4 +47,4 @@ GLuint LoadShader(const char* source, GLenum type) {
return shader_id;
}
} // namespace GLShader
} // namespace OpenGL::GLShader

View File

@@ -10,7 +10,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
namespace GLShader {
namespace OpenGL::GLShader {
/**
* Utility function to log the source code of a list of shaders.
@@ -89,4 +89,4 @@ GLuint LoadProgram(bool separable_program, T... shaders) {
return program_id;
}
} // namespace GLShader
} // namespace OpenGL::GLShader

View File

@@ -7,6 +7,8 @@
#include "common/logging/log.h"
#include "video_core/renderer_opengl/gl_state.h"
namespace OpenGL {
OpenGLState OpenGLState::cur_state;
OpenGLState::OpenGLState() {
@@ -45,7 +47,8 @@ OpenGLState::OpenGLState() {
blend.color.blue = 0.0f;
blend.color.alpha = 0.0f;
logic_op = GL_COPY;
logic_op.enabled = false;
logic_op.operation = GL_COPY;
for (auto& texture_unit : texture_units) {
texture_unit.Reset();
@@ -148,11 +151,10 @@ void OpenGLState::Apply() const {
// Blending
if (blend.enabled != cur_state.blend.enabled) {
if (blend.enabled) {
ASSERT(!logic_op.enabled);
glEnable(GL_BLEND);
glDisable(GL_COLOR_LOGIC_OP);
} else {
glDisable(GL_BLEND);
glEnable(GL_COLOR_LOGIC_OP);
}
}
@@ -176,8 +178,18 @@ void OpenGLState::Apply() const {
glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
}
if (logic_op != cur_state.logic_op) {
glLogicOp(logic_op);
// Logic Operation
if (logic_op.enabled != cur_state.logic_op.enabled) {
if (logic_op.enabled) {
ASSERT(!blend.enabled);
glEnable(GL_COLOR_LOGIC_OP);
} else {
glDisable(GL_COLOR_LOGIC_OP);
}
}
if (logic_op.operation != cur_state.logic_op.operation) {
glLogicOp(logic_op.operation);
}
// Textures
@@ -328,3 +340,5 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) {
}
return *this;
}
} // namespace OpenGL

View File

@@ -9,6 +9,8 @@
#include "video_core/engines/maxwell_3d.h"
namespace OpenGL {
using Regs = Tegra::Engines::Maxwell3D::Regs;
namespace TextureUnits {
@@ -83,7 +85,10 @@ public:
} color; // GL_BLEND_COLOR
} blend;
GLenum logic_op; // GL_LOGIC_OP_MODE
struct {
bool enabled; // GL_LOGIC_OP_MODE
GLenum operation;
} logic_op;
// 3 texture units - one for each that is used in PICA fragment shader emulation
struct TextureUnit {
@@ -160,3 +165,5 @@ public:
private:
static OpenGLState cur_state;
};
} // namespace OpenGL

View File

@@ -9,6 +9,8 @@
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
namespace OpenGL {
OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent)
: gl_target(target), buffer_size(size) {
gl_buffer.Create();
@@ -97,3 +99,5 @@ void OGLStreamBuffer::Unmap(GLsizeiptr size) {
buffer_pos += size;
}
} // namespace OpenGL

View File

@@ -9,6 +9,8 @@
#include "common/common_types.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
class OGLStreamBuffer : private NonCopyable {
public:
explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent = false);
@@ -42,3 +44,5 @@ private:
GLsizeiptr mapped_size = 0;
u8* mapped_ptr = nullptr;
};
} // namespace OpenGL

View File

@@ -10,6 +10,8 @@
#include "common/logging/log.h"
#include "video_core/engines/maxwell_3d.h"
namespace OpenGL {
using GLvec2 = std::array<GLfloat, 2>;
using GLvec3 = std::array<GLfloat, 3>;
using GLvec4 = std::array<GLfloat, 4>;
@@ -107,6 +109,8 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
switch (topology) {
case Maxwell::PrimitiveTopology::Points:
return GL_POINTS;
case Maxwell::PrimitiveTopology::Lines:
return GL_LINES;
case Maxwell::PrimitiveTopology::LineStrip:
return GL_LINE_STRIP;
case Maxwell::PrimitiveTopology::Triangles:
@@ -317,4 +321,45 @@ inline GLenum CullFace(Maxwell::Cull::CullFace cull_face) {
return {};
}
inline GLenum LogicOp(Maxwell::LogicOperation operation) {
switch (operation) {
case Maxwell::LogicOperation::Clear:
return GL_CLEAR;
case Maxwell::LogicOperation::And:
return GL_AND;
case Maxwell::LogicOperation::AndReverse:
return GL_AND_REVERSE;
case Maxwell::LogicOperation::Copy:
return GL_COPY;
case Maxwell::LogicOperation::AndInverted:
return GL_AND_INVERTED;
case Maxwell::LogicOperation::NoOp:
return GL_NOOP;
case Maxwell::LogicOperation::Xor:
return GL_XOR;
case Maxwell::LogicOperation::Or:
return GL_OR;
case Maxwell::LogicOperation::Nor:
return GL_NOR;
case Maxwell::LogicOperation::Equiv:
return GL_EQUIV;
case Maxwell::LogicOperation::Invert:
return GL_INVERT;
case Maxwell::LogicOperation::OrReverse:
return GL_OR_REVERSE;
case Maxwell::LogicOperation::CopyInverted:
return GL_COPY_INVERTED;
case Maxwell::LogicOperation::OrInverted:
return GL_OR_INVERTED;
case Maxwell::LogicOperation::Nand:
return GL_NAND;
case Maxwell::LogicOperation::Set:
return GL_SET;
}
LOG_CRITICAL(Render_OpenGL, "Unimplemented logic operation={}", static_cast<u32>(operation));
UNREACHABLE();
return {};
}
} // namespace MaxwellToGL
} // namespace OpenGL

View File

@@ -20,6 +20,8 @@
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/utils.h"
namespace OpenGL {
static const char vertex_shader[] = R"(
#version 150 core
@@ -476,3 +478,5 @@ bool RendererOpenGL::Init() {
/// Shutdown the renderer
void RendererOpenGL::ShutDown() {}
} // namespace OpenGL

View File

@@ -16,6 +16,8 @@ namespace Core::Frontend {
class EmuWindow;
}
namespace OpenGL {
/// Structure used for storing information about the textures for the Switch screen
struct TextureInfo {
OGLTexture resource;
@@ -98,3 +100,5 @@ private:
Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags;
MathUtil::Rectangle<int> framebuffer_crop_rect;
};
} // namespace OpenGL

View File

@@ -10,7 +10,7 @@
namespace VideoCore {
std::unique_ptr<RendererBase> CreateRenderer(Core::Frontend::EmuWindow& emu_window) {
return std::make_unique<RendererOpenGL>(emu_window);
return std::make_unique<OpenGL::RendererOpenGL>(emu_window);
}
} // namespace VideoCore

View File

@@ -83,7 +83,8 @@ void Config::ReadValues() {
qt_config->beginGroup("Renderer");
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool();
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
Settings::values.use_accurate_framebuffers =
qt_config->value("use_accurate_framebuffers", false).toBool();
@@ -124,7 +125,7 @@ void Config::ReadValues() {
qt_config->beginGroup("UIGameList");
UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
UISettings::values.icon_size = qt_config->value("icon_size", 48).toUInt();
UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 0).toUInt();
UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 3).toUInt();
qt_config->endGroup();
@@ -203,7 +204,8 @@ void Config::SaveValues() {
qt_config->beginGroup("Renderer");
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
qt_config->setValue("toggle_framelimit", Settings::values.toggle_framelimit);
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
qt_config->setValue("frame_limit", Settings::values.frame_limit);
qt_config->setValue("use_accurate_framebuffers", Settings::values.use_accurate_framebuffers);
// Cast to double because Qt's written float values are not human-readable

View File

@@ -12,6 +12,10 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
ui->setupUi(this);
this->setConfiguration();
ui->frame_limit->setEnabled(Settings::values.use_frame_limit);
connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit,
&QSpinBox::setEnabled);
}
ConfigureGraphics::~ConfigureGraphics() = default;
@@ -58,13 +62,15 @@ Resolution FromResolutionFactor(float factor) {
void ConfigureGraphics::setConfiguration() {
ui->resolution_factor_combobox->setCurrentIndex(
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_framelimit->setChecked(Settings::values.toggle_framelimit);
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setValue(Settings::values.frame_limit);
ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers);
}
void ConfigureGraphics::applyConfiguration() {
Settings::values.resolution_factor =
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.toggle_framelimit = ui->toggle_framelimit->isChecked();
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked();
}

View File

@@ -23,11 +23,31 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_framelimit">
<property name="text">
<string>Limit framerate</string>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_frame_limit">
<property name="text">
<string>Limit Speed Percent</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="frame_limit">
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="use_accurate_framebuffers">

View File

@@ -29,24 +29,6 @@
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
<item>
<widget class="QLabel" name="labelPlus">
<property name="text">
<string>Plus:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonPlus">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout">
<item>
<widget class="QLabel" name="labelMinus">
@@ -64,6 +46,24 @@
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout">
<item>
<widget class="QLabel" name="labelPlus">
<property name="text">
<string>Plus:</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonPlus">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout">
<item>

View File

@@ -327,7 +327,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
open_save_location->setEnabled(program_id != 0);
connect(open_save_location, &QAction::triggered,
[&]() { emit OpenSaveFolderRequested(program_id); });
[&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}

View File

@@ -21,6 +21,8 @@
class GameListWorker;
enum class GameListOpenTarget { SaveData };
class GameList : public QWidget {
Q_OBJECT
@@ -76,7 +78,7 @@ public:
signals:
void GameChosen(QString game_path);
void ShouldCancelWorker();
void OpenSaveFolderRequested(u64 program_id);
void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
private slots:
void onTextChanged(const QString& newText);
@@ -99,3 +101,5 @@ private:
GameListWorker* current_worker = nullptr;
QFileSystemWatcher* watcher = nullptr;
};
Q_DECLARE_METATYPE(GameListOpenTarget);

View File

@@ -30,6 +30,7 @@
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
@@ -231,11 +232,16 @@ void GMainWindow::InitializeHotkeys() {
hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
hotkey_registry.RegisterHotkey("Main Window", "Restart", QKeySequence(Qt::Key_F5));
hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
Qt::ApplicationShortcut);
hotkey_registry.LoadHotkeys();
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
@@ -252,6 +258,12 @@ void GMainWindow::InitializeHotkeys() {
}
}
});
connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this,
[this] {
if (!Core::System::GetInstance().IsPoweredOn())
return;
BootGame(QString(game_path));
});
connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
&QShortcut::activated, ui.action_Fullscreen, &QAction::trigger);
connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
@@ -265,9 +277,24 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this),
&QShortcut::activated, this, [&] {
Settings::values.toggle_framelimit = !Settings::values.toggle_framelimit;
Settings::values.use_frame_limit = !Settings::values.use_frame_limit;
UpdateStatusBar();
});
constexpr u16 SPEED_LIMIT_STEP = 5;
connect(hotkey_registry.GetHotkey("Main Window", "Increase Speed Limit", this),
&QShortcut::activated, this, [&] {
if (Settings::values.frame_limit < 9999 - SPEED_LIMIT_STEP) {
Settings::values.frame_limit += SPEED_LIMIT_STEP;
UpdateStatusBar();
}
});
connect(hotkey_registry.GetHotkey("Main Window", "Decrease Speed Limit", this),
&QShortcut::activated, this, [&] {
if (Settings::values.frame_limit > SPEED_LIMIT_STEP) {
Settings::values.frame_limit -= SPEED_LIMIT_STEP;
UpdateStatusBar();
}
});
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -311,8 +338,7 @@ void GMainWindow::RestoreUIState() {
void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
connect(game_list, &GameList::OpenSaveFolderRequested, this,
&GMainWindow::OnGameListOpenSaveFolder);
connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
@@ -336,6 +362,7 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);
connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame);
connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
// View
@@ -545,6 +572,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Start->setText(tr("Start"));
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -596,8 +624,37 @@ void GMainWindow::OnGameListLoadFile(QString game_path) {
BootGame(game_path);
}
void GMainWindow::OnGameListOpenSaveFolder(u64 program_id) {
UNIMPLEMENTED();
void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target) {
std::string path;
std::string open_target;
switch (target) {
case GameListOpenTarget::SaveData: {
open_target = "Save Data";
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
// TODO(tech4me): Update this to work with arbitrary user profile
// Refer to core/hle/service/acc/profile_manager.cpp ProfileManager constructor
constexpr u128 user_id = {1, 0};
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData,
program_id, user_id, 0);
break;
}
default:
UNIMPLEMENTED();
}
const QString qpath = QString::fromStdString(path);
const QDir dir(qpath);
if (!dir.exists()) {
QMessageBox::warning(this,
tr("Error Opening %1 Folder").arg(QString::fromStdString(open_target)),
tr("Folder does not exist!"));
return;
}
LOG_INFO(Frontend, "Opening {} path for program_id={:016x}", open_target, program_id);
QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
}
void GMainWindow::OnMenuLoadFile() {
@@ -823,6 +880,7 @@ void GMainWindow::OnPauseGame() {
ui.action_Start->setEnabled(true);
ui.action_Pause->setEnabled(false);
ui.action_Stop->setEnabled(true);
ui.action_Restart->setEnabled(true);
}
void GMainWindow::OnStopGame() {
@@ -924,7 +982,13 @@ void GMainWindow::UpdateStatusBar() {
auto results = Core::System::GetInstance().GetAndResetPerfStats();
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
if (Settings::values.use_frame_limit) {
emu_speed_label->setText(tr("Speed: %1% / %2%")
.arg(results.emulation_speed * 100.0, 0, 'f', 0)
.arg(Settings::values.frame_limit));
} else {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
}
game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));

View File

@@ -21,6 +21,7 @@ class GRenderWindow;
class MicroProfileDialog;
class ProfilerWidget;
class WaitTreeWidget;
enum class GameListOpenTarget;
namespace Tegra {
class DebugContext;
@@ -122,7 +123,7 @@ private slots:
void OnStopGame();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
void OnGameListOpenSaveFolder(u64 program_id);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
void OnMenuLoadFile();
void OnMenuLoadFolder();
void OnMenuInstallToNAND();

View File

@@ -211,6 +211,14 @@
<string>Fullscreen</string>
</property>
</action>
<action name="action_Restart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restart</string>
</property>
</action>
</widget>
<resources/>
</ui>

View File

@@ -96,8 +96,9 @@ void Config::ReadValues() {
// Renderer
Settings::values.resolution_factor =
(float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
Settings::values.toggle_framelimit =
sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true);
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
Settings::values.use_accurate_framebuffers =
sdl2_config->GetBoolean("Renderer", "use_accurate_framebuffers", false);

View File

@@ -102,6 +102,14 @@ resolution_factor =
# 0 (default): Off, 1: On
use_vsync =
# Turns on the frame limiter, which will limit frames output to the target game speed
# 0: Off, 1: On (default)
use_frame_limit =
# Limits the speed of the game to run no faster than this value as a percentage of target speed
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
frame_limit =
# Whether to use accurate framebuffers
# 0 (default): Off (fast), 1 : On (slow)
use_accurate_framebuffers =
@@ -132,10 +140,6 @@ custom_bottom_top =
custom_bottom_right =
custom_bottom_bottom =
# Whether to toggle frame limiter on or off.
# 0: Off, 1 (default): On
toggle_framelimit =
# Swaps the prominent screen with the other screen.
# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen.
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent