Compare commits
73 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a776464a55 | ||
|
|
62cd19e4ae | ||
|
|
7e3d746b06 | ||
|
|
8c99dd055c | ||
|
|
00749f5ab3 | ||
|
|
6ea1576513 | ||
|
|
81a16c073a | ||
|
|
23b1e6eded | ||
|
|
438a9b70cc | ||
|
|
e8bfff7b4b | ||
|
|
f564822e78 | ||
|
|
6cf6fa2842 | ||
|
|
d27279092f | ||
|
|
37fd4e6d9b | ||
|
|
cdd92dc692 | ||
|
|
38d25a4cb2 | ||
|
|
2933521a08 | ||
|
|
f6679ce422 | ||
|
|
5d55403f94 | ||
|
|
0a0233f39f | ||
|
|
9936d1b9e2 | ||
|
|
4fad069870 | ||
|
|
0c688b421c | ||
|
|
cb47abecc6 | ||
|
|
fbef849c04 | ||
|
|
0641950f9a | ||
|
|
b7c64f0ded | ||
|
|
90cddf1996 | ||
|
|
7c181fd4f4 | ||
|
|
d16f83fda3 | ||
|
|
5c82400ef8 | ||
|
|
afdd2f4cad | ||
|
|
df4336a85e | ||
|
|
51d8a2c322 | ||
|
|
049ce242a4 | ||
|
|
b481d8a00d | ||
|
|
06c72b4fcf | ||
|
|
876b805e50 | ||
|
|
2dcb98226b | ||
|
|
9fedfbe141 | ||
|
|
d73c22bf4d | ||
|
|
ba117854f9 | ||
|
|
527c098ff6 | ||
|
|
d57333406d | ||
|
|
1efcba346a | ||
|
|
bb9d39b8fe | ||
|
|
27c0f9e02d | ||
|
|
41faeeeb03 | ||
|
|
63270e588b | ||
|
|
e54ea773fc | ||
|
|
0d64ddc6dd | ||
|
|
9cd87a6352 | ||
|
|
99f9d47d16 | ||
|
|
bbbe34429e | ||
|
|
11568c2ea3 | ||
|
|
888eb345c0 | ||
|
|
4c727d0ba8 | ||
|
|
bdd68fc210 | ||
|
|
f1bded1270 | ||
|
|
49309b5848 | ||
|
|
c02d7c8ce7 | ||
|
|
3957b0c34e | ||
|
|
ca5a4a704b | ||
|
|
15086a22be | ||
|
|
94fecef137 | ||
|
|
d1f9c750a6 | ||
|
|
99f12b05fa | ||
|
|
8df011a57f | ||
|
|
6fcc7e9c36 | ||
|
|
68937a662d | ||
|
|
6306655665 | ||
|
|
0658973a4e | ||
|
|
0d843eaba6 |
@@ -53,7 +53,7 @@ build_script:
|
||||
# https://www.appveyor.com/docs/build-phase
|
||||
msbuild msvc_build/yuzu.sln /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
} else {
|
||||
C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -C mingw_build/ 2>&1'
|
||||
C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -j4 -C mingw_build/ 2>&1'
|
||||
}
|
||||
|
||||
after_build:
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace Log {
|
||||
SUB(Service, FS) \
|
||||
SUB(Service, HID) \
|
||||
SUB(Service, LM) \
|
||||
SUB(Service, MM) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NS) \
|
||||
|
||||
@@ -61,6 +61,7 @@ enum class Class : ClassType {
|
||||
Service_FS, ///< The FS (Filesystem) service
|
||||
Service_HID, ///< The HID (Human interface device) service
|
||||
Service_LM, ///< The LM (Logger) service
|
||||
Service_MM, ///< The MM (Multimedia) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NS, ///< The NS services
|
||||
|
||||
@@ -148,6 +148,8 @@ add_library(core STATIC
|
||||
hle/service/hid/hid.h
|
||||
hle/service/lm/lm.cpp
|
||||
hle/service/lm/lm.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/nifm/nifm.cpp
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nifm/nifm_a.cpp
|
||||
|
||||
@@ -29,7 +29,10 @@ enum class ControlCommand : u32 {
|
||||
};
|
||||
|
||||
enum class CommandType : u32 {
|
||||
Invalid = 0,
|
||||
LegacyRequest = 1,
|
||||
Close = 2,
|
||||
LegacyControl = 3,
|
||||
Request = 4,
|
||||
Control = 5,
|
||||
RequestWithContext = 6,
|
||||
|
||||
@@ -21,7 +21,9 @@ enum {
|
||||
|
||||
// Confirmed Switch OS error codes
|
||||
MisalignedAddress = 102,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
InvalidCombination = 116,
|
||||
Timeout = 117,
|
||||
SynchronizationCanceled = 118,
|
||||
TooLarge = 119,
|
||||
|
||||
@@ -732,7 +732,7 @@ static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask)
|
||||
}
|
||||
|
||||
static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
NGLOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:08X}, core=0x{:X}", thread_handle,
|
||||
NGLOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle,
|
||||
mask, core);
|
||||
|
||||
const SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
@@ -740,6 +740,31 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (core == THREADPROCESSORID_DEFAULT) {
|
||||
ASSERT(thread->owner_process->ideal_processor != THREADPROCESSORID_DEFAULT);
|
||||
// Set the target CPU to the one specified in the process' exheader.
|
||||
core = thread->owner_process->ideal_processor;
|
||||
mask = 1 << core;
|
||||
}
|
||||
|
||||
if (mask == 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);
|
||||
}
|
||||
|
||||
/// This value is used to only change the affinity mask without changing the current ideal core.
|
||||
static constexpr u32 OnlyChangeMask = static_cast<u32>(-3);
|
||||
|
||||
if (core == OnlyChangeMask) {
|
||||
core = thread->ideal_core;
|
||||
} else if (core >= Core::NUM_CPU_CORES && core != -1) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
}
|
||||
|
||||
// Error out if the input core isn't enabled in the input mask.
|
||||
if (core < Core::NUM_CPU_CORES && (mask & (1 << core)) == 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination);
|
||||
}
|
||||
|
||||
thread->ChangeCore(core, mask);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
@@ -133,8 +133,11 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
|
||||
// and don't have a lock owner.
|
||||
ASSERT(lock_owner == nullptr);
|
||||
// and don't have a lock owner unless SignalProcessWideKey was called first and the thread
|
||||
// wasn't awakened due to the mutex already being acquired.
|
||||
if (lock_owner) {
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
}
|
||||
}
|
||||
|
||||
if (resume)
|
||||
@@ -460,13 +463,13 @@ void Thread::UpdatePriority() {
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
ideal_core = core;
|
||||
mask = mask;
|
||||
affinity_mask = mask;
|
||||
|
||||
if (status != THREADSTATUS_READY) {
|
||||
return;
|
||||
}
|
||||
|
||||
boost::optional<s32> new_processor_id{GetNextProcessorId(mask)};
|
||||
boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
|
||||
|
||||
if (!new_processor_id) {
|
||||
new_processor_id = processor_id;
|
||||
@@ -476,7 +479,7 @@ void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
new_processor_id = ideal_core;
|
||||
}
|
||||
|
||||
ASSERT(new_processor_id < 4);
|
||||
ASSERT(*new_processor_id < 4);
|
||||
|
||||
// Add thread to new core's scheduler
|
||||
auto& next_scheduler = Core::System().GetInstance().Scheduler(*new_processor_id);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <stack>
|
||||
#include "core/file_sys/filesystem.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
@@ -154,7 +155,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
RegisterHandlers(functions);
|
||||
|
||||
launchable_event =
|
||||
Kernel::Event::Create(Kernel::ResetType::OneShot, "ISelfController:LaunchableEvent");
|
||||
Kernel::Event::Create(Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent");
|
||||
}
|
||||
|
||||
void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
|
||||
@@ -348,19 +349,100 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) {
|
||||
NGLOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
|
||||
public:
|
||||
explicit IStorageAccessor(std::vector<u8> buffer)
|
||||
: ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IStorageAccessor::GetSize, "GetSize"},
|
||||
{10, &IStorageAccessor::Write, "Write"},
|
||||
{11, &IStorageAccessor::Read, "Read"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> buffer;
|
||||
|
||||
void GetSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u64>(buffer.size()));
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void Write(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
const std::vector<u8> data{ctx.ReadBuffer()};
|
||||
|
||||
ASSERT(offset + data.size() <= buffer.size());
|
||||
|
||||
std::memcpy(&buffer[offset], data.data(), data.size());
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
}
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
const size_t size{ctx.GetWriteBufferSize()};
|
||||
|
||||
ASSERT(offset + size <= buffer.size());
|
||||
|
||||
ctx.WriteBuffer(buffer.data() + offset, size);
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
}
|
||||
};
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
explicit IStorage(std::vector<u8> buffer)
|
||||
: ServiceFramework("IStorage"), buffer(std::move(buffer)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IStorage::Open, "Open"},
|
||||
{1, nullptr, "OpenTransferStorage"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> buffer;
|
||||
|
||||
void Open(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorageAccessor>(buffer);
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
};
|
||||
|
||||
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
|
||||
public:
|
||||
explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
|
||||
{1, nullptr, "IsCompleted"},
|
||||
{10, nullptr, "Start"},
|
||||
{10, &ILibraryAppletAccessor::Start, "Start"},
|
||||
{20, nullptr, "RequestExit"},
|
||||
{25, nullptr, "Terminate"},
|
||||
{30, nullptr, "GetResult"},
|
||||
{30, &ILibraryAppletAccessor::GetResult, "GetResult"},
|
||||
{50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"},
|
||||
{100, nullptr, "PushInData"},
|
||||
{101, nullptr, "PopOutData"},
|
||||
{100, &ILibraryAppletAccessor::PushInData, "PushInData"},
|
||||
{101, &ILibraryAppletAccessor::PopOutData, "PopOutData"},
|
||||
{102, nullptr, "PushExtraStorage"},
|
||||
{103, nullptr, "PushInteractiveInData"},
|
||||
{104, nullptr, "PopInteractiveOutData"},
|
||||
@@ -388,6 +470,41 @@ private:
|
||||
NGLOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void GetResult(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
NGLOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void Start(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
NGLOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
}
|
||||
|
||||
void PushInData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
storage_stack.push(rp.PopIpcInterface<AM::IStorage>());
|
||||
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void PopOutData(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top()));
|
||||
|
||||
storage_stack.pop();
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
std::stack<std::shared_ptr<AM::IStorage>> storage_stack;
|
||||
Kernel::SharedPtr<Kernel::Event> state_changed_event;
|
||||
};
|
||||
|
||||
@@ -396,7 +513,7 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple
|
||||
{0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
|
||||
{1, nullptr, "TerminateAllLibraryApplets"},
|
||||
{2, nullptr, "AreAnyLibraryAppletsLeft"},
|
||||
{10, nullptr, "CreateStorage"},
|
||||
{10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"},
|
||||
{11, nullptr, "CreateTransferMemoryStorage"},
|
||||
{12, nullptr, "CreateHandleStorage"},
|
||||
};
|
||||
@@ -412,72 +529,17 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
|
||||
public:
|
||||
explicit IStorageAccessor(std::vector<u8> buffer)
|
||||
: ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IStorageAccessor::GetSize, "GetSize"},
|
||||
{10, nullptr, "Write"},
|
||||
{11, &IStorageAccessor::Read, "Read"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 size{rp.Pop<u64>()};
|
||||
std::vector<u8> buffer(size);
|
||||
|
||||
private:
|
||||
std::vector<u8> buffer;
|
||||
IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 1)};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorage>(std::move(buffer));
|
||||
|
||||
void GetSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(static_cast<u64>(buffer.size()));
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
u64 offset = rp.Pop<u64>();
|
||||
|
||||
const size_t size{ctx.GetWriteBufferSize()};
|
||||
|
||||
ASSERT(offset + size <= buffer.size());
|
||||
|
||||
ctx.WriteBuffer(buffer.data() + offset, size);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
};
|
||||
|
||||
class IStorage final : public ServiceFramework<IStorage> {
|
||||
public:
|
||||
explicit IStorage(std::vector<u8> buffer)
|
||||
: ServiceFramework("IStorage"), buffer(std::move(buffer)) {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IStorage::Open, "Open"},
|
||||
{1, nullptr, "OpenTransferStorage"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> buffer;
|
||||
|
||||
void Open(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<AM::IStorageAccessor>(buffer);
|
||||
|
||||
NGLOG_DEBUG(Service_AM, "called");
|
||||
}
|
||||
};
|
||||
NGLOG_DEBUG(Service_AM, "called, size={}", size);
|
||||
}
|
||||
|
||||
IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
|
||||
static const FunctionInfo functions[] = {
|
||||
|
||||
@@ -121,6 +121,7 @@ public:
|
||||
|
||||
private:
|
||||
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
|
||||
void CreateStorage(Kernel::HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
|
||||
|
||||
@@ -329,6 +329,7 @@ public:
|
||||
{130, nullptr, "SwapNpadAssignment"},
|
||||
{131, nullptr, "IsUnintendedHomeButtonInputProtectionEnabled"},
|
||||
{132, nullptr, "EnableUnintendedHomeButtonInputProtection"},
|
||||
{133, nullptr, "SetNpadJoyAssignmentModeSingleWithDestination"},
|
||||
{200, &Hid::GetVibrationDeviceInfo, "GetVibrationDeviceInfo"},
|
||||
{201, &Hid::SendVibrationValue, "SendVibrationValue"},
|
||||
{202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
|
||||
@@ -336,12 +337,41 @@ public:
|
||||
{204, nullptr, "PermitVibration"},
|
||||
{205, nullptr, "IsVibrationPermitted"},
|
||||
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
|
||||
{207, nullptr, "SendVibrationGcErmCommand"},
|
||||
{208, nullptr, "GetActualVibrationGcErmCommand"},
|
||||
{209, nullptr, "BeginPermitVibrationSession"},
|
||||
{210, nullptr, "EndPermitVibrationSession"},
|
||||
{300, nullptr, "ActivateConsoleSixAxisSensor"},
|
||||
{301, nullptr, "StartConsoleSixAxisSensor"},
|
||||
{302, nullptr, "StopConsoleSixAxisSensor"},
|
||||
{303, nullptr, "ActivateSevenSixAxisSensor"},
|
||||
{304, nullptr, "StartSevenSixAxisSensor"},
|
||||
{305, nullptr, "StopSevenSixAxisSensor"},
|
||||
{306, nullptr, "InitializeSevenSixAxisSensor"},
|
||||
{307, nullptr, "FinalizeSevenSixAxisSensor"},
|
||||
{308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
|
||||
{309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
|
||||
{400, nullptr, "IsUsbFullKeyControllerEnabled"},
|
||||
{401, nullptr, "EnableUsbFullKeyController"},
|
||||
{402, nullptr, "IsUsbFullKeyControllerConnected"},
|
||||
{403, nullptr, "HasBattery"},
|
||||
{404, nullptr, "HasLeftRightBattery"},
|
||||
{405, nullptr, "GetNpadInterfaceType"},
|
||||
{406, nullptr, "GetNpadLeftRightInterfaceType"},
|
||||
{500, nullptr, "GetPalmaConnectionHandle"},
|
||||
{501, nullptr, "InitializePalma"},
|
||||
{502, nullptr, "AcquirePalmaOperationCompleteEvent"},
|
||||
{503, nullptr, "GetPalmaOperationInfo"},
|
||||
{504, nullptr, "PlayPalmaActivity"},
|
||||
{505, nullptr, "SetPalmaFrModeType"},
|
||||
{506, nullptr, "ReadPalmaStep"},
|
||||
{507, nullptr, "EnablePalmaStep"},
|
||||
{508, nullptr, "SuspendPalmaStep"},
|
||||
{509, nullptr, "ResetPalmaStep"},
|
||||
{510, nullptr, "ReadPalmaApplicationSection"},
|
||||
{511, nullptr, "WritePalmaApplicationSection"},
|
||||
{512, nullptr, "ReadPalmaUniqueCode"},
|
||||
{513, nullptr, "SetPalmaUniqueCodeInvalid"},
|
||||
{1000, nullptr, "SetNpadCommunicationMode"},
|
||||
{1001, nullptr, "GetNpadCommunicationMode"},
|
||||
};
|
||||
|
||||
50
src/core/hle/service/mm/mm_u.cpp
Normal file
50
src/core/hle/service/mm/mm_u.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/service/mm/mm_u.h"
|
||||
|
||||
namespace Service::MM {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
std::make_shared<MM_U>()->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
void MM_U::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
NGLOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void MM_U::SetAndWait(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
min = rp.Pop<u32>();
|
||||
max = rp.Pop<u32>();
|
||||
current = min;
|
||||
|
||||
NGLOG_WARNING(Service_MM, "(STUBBED) called, min=0x{:X}, max=0x{:X}", min, max);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void MM_U::Get(Kernel::HLERequestContext& ctx) {
|
||||
NGLOG_WARNING(Service_MM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(current);
|
||||
}
|
||||
|
||||
MM_U::MM_U() : ServiceFramework("mm:u") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "InitializeOld"}, {1, nullptr, "FinalizeOld"},
|
||||
{2, nullptr, "SetAndWaitOld"}, {3, nullptr, "GetOld"},
|
||||
{4, &MM_U::Initialize, "Initialize"}, {5, nullptr, "Finalize"},
|
||||
{6, &MM_U::SetAndWait, "SetAndWait"}, {7, &MM_U::Get, "Get"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Service::MM
|
||||
29
src/core/hle/service/mm/mm_u.h
Normal file
29
src/core/hle/service/mm/mm_u.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::MM {
|
||||
|
||||
class MM_U final : public ServiceFramework<MM_U> {
|
||||
public:
|
||||
MM_U();
|
||||
~MM_U() = default;
|
||||
|
||||
private:
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
void SetAndWait(Kernel::HLERequestContext& ctx);
|
||||
void Get(Kernel::HLERequestContext& ctx);
|
||||
|
||||
u32 min{0};
|
||||
u32 max{0};
|
||||
u32 current{0};
|
||||
};
|
||||
|
||||
/// Registers all MM services with the specified service manager.
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||
|
||||
} // namespace Service::MM
|
||||
@@ -16,7 +16,11 @@ u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<
|
||||
case IoctlCommand::IocGetConfigCommand:
|
||||
return NvOsGetConfigU32(input, output);
|
||||
case IoctlCommand::IocCtrlEventWaitCommand:
|
||||
return IocCtrlEventWait(input, output);
|
||||
return IocCtrlEventWait(input, output, false);
|
||||
case IoctlCommand::IocCtrlEventWaitAsyncCommand:
|
||||
return IocCtrlEventWait(input, output, true);
|
||||
case IoctlCommand::IocCtrlEventRegisterCommand:
|
||||
return IocCtrlEventRegister(input, output);
|
||||
}
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
return 0;
|
||||
@@ -45,11 +49,13 @@ u32 nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>&
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
|
||||
bool is_async) {
|
||||
IocCtrlEventWaitParams params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||
NGLOG_WARNING(Service_NVDRV, "(STUBBED) called, syncpt_id={} threshold={} timeout={}",
|
||||
params.syncpt_id, params.threshold, params.timeout);
|
||||
NGLOG_WARNING(Service_NVDRV,
|
||||
"(STUBBED) called, syncpt_id={}, threshold={}, timeout={}, is_async={}",
|
||||
params.syncpt_id, params.threshold, params.timeout, is_async);
|
||||
|
||||
// TODO(Subv): Implement actual syncpt waiting.
|
||||
params.value = 0;
|
||||
@@ -57,4 +63,10 @@ u32 nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>&
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
NGLOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
// TODO(bunnei): Implement this.
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -134,7 +134,9 @@ private:
|
||||
|
||||
u32 NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
|
||||
u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
|
||||
|
||||
u32 IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -26,11 +26,19 @@ public:
|
||||
private:
|
||||
enum class IoctlCommand : u32_le {
|
||||
IocSetNVMAPfdCommand = 0x40044801,
|
||||
IocAllocGPFIFOCommand = 0x40084805,
|
||||
IocSetClientDataCommand = 0x40084714,
|
||||
IocGetClientDataCommand = 0x80084715,
|
||||
IocZCullBind = 0xc010480b,
|
||||
IocSetErrorNotifierCommand = 0xC018480C,
|
||||
IocChannelSetPriorityCommand = 0x4004480D,
|
||||
IocEnableCommand = 0x0000480E,
|
||||
IocDisableCommand = 0x0000480F,
|
||||
IocPreemptCommand = 0x00004810,
|
||||
IocForceResetCommand = 0x00004811,
|
||||
IocEventIdControlCommand = 0x40084812,
|
||||
IocGetErrorNotificationCommand = 0xC0104817,
|
||||
IocAllocGPFIFOExCommand = 0x40204818,
|
||||
IocAllocGPFIFOEx2Command = 0xC020481A,
|
||||
IocAllocObjCtxCommand = 0xC0104809,
|
||||
IocChannelGetWaitbaseCommand = 0xC0080003,
|
||||
@@ -56,6 +64,12 @@ private:
|
||||
};
|
||||
static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size");
|
||||
|
||||
struct IoctlAllocGPFIFO {
|
||||
u32_le num_entries;
|
||||
u32_le flags;
|
||||
};
|
||||
static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size");
|
||||
|
||||
struct IoctlClientData {
|
||||
u64_le data;
|
||||
};
|
||||
@@ -76,12 +90,45 @@ private:
|
||||
};
|
||||
static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size");
|
||||
|
||||
struct IoctlChannelSetPriority {
|
||||
u32_le priority;
|
||||
};
|
||||
static_assert(sizeof(IoctlChannelSetPriority) == 4,
|
||||
"IoctlChannelSetPriority is incorrect size");
|
||||
|
||||
struct IoctlEventIdControl {
|
||||
u32_le cmd; // 0=disable, 1=enable, 2=clear
|
||||
u32_le id;
|
||||
};
|
||||
static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size");
|
||||
|
||||
struct IoctlGetErrorNotification {
|
||||
u64_le timestamp;
|
||||
u32_le info32;
|
||||
u16_le info16;
|
||||
u16_le status; // always 0xFFFF
|
||||
};
|
||||
static_assert(sizeof(IoctlGetErrorNotification) == 16,
|
||||
"IoctlGetErrorNotification is incorrect size");
|
||||
|
||||
struct IoctlFence {
|
||||
u32_le id;
|
||||
u32_le value;
|
||||
};
|
||||
static_assert(sizeof(IoctlFence) == 8, "IoctlFence is incorrect size");
|
||||
|
||||
struct IoctlAllocGpfifoEx {
|
||||
u32_le num_entries;
|
||||
u32_le flags;
|
||||
u32_le unk0;
|
||||
u32_le unk1;
|
||||
u32_le unk2;
|
||||
u32_le unk3;
|
||||
u32_le unk4;
|
||||
u32_le unk5;
|
||||
};
|
||||
static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size");
|
||||
|
||||
struct IoctlAllocGpfifoEx2 {
|
||||
u32_le num_entries; // in
|
||||
u32_le flags; // in
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "core/hle/service/friend/friend.h"
|
||||
#include "core/hle/service/hid/hid.h"
|
||||
#include "core/hle/service/lm/lm.h"
|
||||
#include "core/hle/service/mm/mm_u.h"
|
||||
#include "core/hle/service/nfp/nfp.h"
|
||||
#include "core/hle/service/nifm/nifm.h"
|
||||
#include "core/hle/service/ns/ns.h"
|
||||
@@ -191,6 +192,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
|
||||
Friend::InstallInterfaces(*sm);
|
||||
HID::InstallInterfaces(*sm);
|
||||
LM::InstallInterfaces(*sm);
|
||||
MM::InstallInterfaces(*sm);
|
||||
NFP::InstallInterfaces(*sm);
|
||||
NIFM::InstallInterfaces(*sm);
|
||||
NS::InstallInterfaces(*sm);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
@@ -77,7 +78,7 @@ public:
|
||||
{3, nullptr, "LoadLocationNameList"},
|
||||
{4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"},
|
||||
{5, nullptr, "GetTimeZoneRuleVersion"},
|
||||
{100, nullptr, "ToCalendarTime"},
|
||||
{100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},
|
||||
{101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
|
||||
{200, nullptr, "ToPosixTime"},
|
||||
{201, nullptr, "ToPosixTimeWithMyRule"},
|
||||
@@ -86,9 +87,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
LocationName location_name{"UTC"};
|
||||
TimeZoneRule my_time_zone_rule{};
|
||||
|
||||
void GetDeviceLocationName(Kernel::HLERequestContext& ctx) {
|
||||
NGLOG_WARNING(Service_Time, "(STUBBED) called");
|
||||
LocationName location_name{};
|
||||
NGLOG_DEBUG(Service_Time, "called");
|
||||
IPC::ResponseBuilder rb{ctx, (sizeof(LocationName) / 4) + 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(location_name);
|
||||
@@ -103,23 +106,70 @@ private:
|
||||
|
||||
void LoadTimeZoneRule(Kernel::HLERequestContext& ctx) {
|
||||
NGLOG_WARNING(Service_Time, "(STUBBED) called");
|
||||
|
||||
ctx.WriteBuffer(&my_time_zone_rule, sizeof(TimeZoneRule));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void ToCalendarTime(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const u64 posix_time = rp.Pop<u64>();
|
||||
|
||||
NGLOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time);
|
||||
|
||||
TimeZoneRule time_zone_rule{};
|
||||
auto buffer = ctx.ReadBuffer();
|
||||
std::memcpy(&time_zone_rule, buffer.data(), buffer.size());
|
||||
|
||||
CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
|
||||
CalendarAdditionalInfo additional_info{};
|
||||
|
||||
PosixToCalendar(posix_time, calendar_time, additional_info, time_zone_rule);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 10};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(calendar_time);
|
||||
rb.PushRaw(additional_info);
|
||||
}
|
||||
|
||||
void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u64 posix_time = rp.Pop<u64>();
|
||||
const u64 posix_time = rp.Pop<u64>();
|
||||
|
||||
NGLOG_WARNING(Service_Time, "(STUBBED) called, posix_time=0x{:016X}", posix_time);
|
||||
|
||||
CalendarTime calendar_time{2018, 1, 1, 0, 0, 0};
|
||||
CalendarAdditionalInfo additional_info{};
|
||||
|
||||
PosixToCalendar(posix_time, calendar_time, additional_info, my_time_zone_rule);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 10};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw(calendar_time);
|
||||
rb.PushRaw(additional_info);
|
||||
}
|
||||
|
||||
void PosixToCalendar(u64 posix_time, CalendarTime& calendar_time,
|
||||
CalendarAdditionalInfo& additional_info, const TimeZoneRule& /*rule*/) {
|
||||
std::time_t t(posix_time);
|
||||
std::tm* tm = std::localtime(&t);
|
||||
if (!tm) {
|
||||
return;
|
||||
}
|
||||
calendar_time.year = tm->tm_year + 1900;
|
||||
calendar_time.month = tm->tm_mon + 1;
|
||||
calendar_time.day = tm->tm_mday;
|
||||
calendar_time.hour = tm->tm_hour;
|
||||
calendar_time.minute = tm->tm_min;
|
||||
calendar_time.second = tm->tm_sec;
|
||||
|
||||
additional_info.day_of_week = tm->tm_wday;
|
||||
additional_info.day_of_year = tm->tm_yday;
|
||||
std::memcpy(additional_info.name.data(), "UTC", sizeof("UTC"));
|
||||
additional_info.utc_offset = 0;
|
||||
}
|
||||
};
|
||||
|
||||
void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::Time {
|
||||
|
||||
// TODO(Rozelette) RE this structure
|
||||
struct LocationName {
|
||||
INSERT_PADDING_BYTES(0x24);
|
||||
std::array<u8, 0x24> name;
|
||||
};
|
||||
static_assert(sizeof(LocationName) == 0x24, "LocationName is incorrect size");
|
||||
|
||||
@@ -25,26 +25,34 @@ struct CalendarTime {
|
||||
};
|
||||
static_assert(sizeof(CalendarTime) == 0x8, "CalendarTime structure has incorrect size");
|
||||
|
||||
// TODO(Rozelette) RE this structure
|
||||
struct CalendarAdditionalInfo {
|
||||
INSERT_PADDING_BYTES(0x18);
|
||||
u32_le day_of_week;
|
||||
u32_le day_of_year;
|
||||
std::array<u8, 8> name;
|
||||
INSERT_PADDING_BYTES(1);
|
||||
s32_le utc_offset;
|
||||
};
|
||||
static_assert(sizeof(CalendarAdditionalInfo) == 0x18,
|
||||
"CalendarAdditionalInfo structure has incorrect size");
|
||||
|
||||
// TODO(bunnei) RE this structure
|
||||
struct SystemClockContext {
|
||||
INSERT_PADDING_BYTES(0x20);
|
||||
// TODO(mailwl) RE this structure
|
||||
struct TimeZoneRule {
|
||||
INSERT_PADDING_BYTES(0x4000);
|
||||
};
|
||||
static_assert(sizeof(SystemClockContext) == 0x20,
|
||||
"SystemClockContext structure has incorrect size");
|
||||
|
||||
struct SteadyClockTimePoint {
|
||||
u64 value;
|
||||
u64_le value;
|
||||
INSERT_PADDING_WORDS(4);
|
||||
};
|
||||
static_assert(sizeof(SteadyClockTimePoint) == 0x18, "SteadyClockTimePoint is incorrect size");
|
||||
|
||||
struct SystemClockContext {
|
||||
u64_le offset;
|
||||
SteadyClockTimePoint time_point;
|
||||
};
|
||||
static_assert(sizeof(SystemClockContext) == 0x20,
|
||||
"SystemClockContext structure has incorrect size");
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
class Interface : public ServiceFramework<Interface> {
|
||||
|
||||
@@ -156,16 +156,15 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
// TODO(Subv): Support the other query units.
|
||||
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
|
||||
"Units other than CROP are unimplemented");
|
||||
ASSERT_MSG(regs.query.query_get.short_query,
|
||||
"Writing the entire query result structure is unimplemented");
|
||||
|
||||
u32 value = Memory::Read32(*address);
|
||||
u32 result = 0;
|
||||
u64 result = 0;
|
||||
|
||||
// TODO(Subv): Support the other query variables
|
||||
switch (regs.query.query_get.select) {
|
||||
case Regs::QuerySelect::Zero:
|
||||
result = 0;
|
||||
// This seems to actually write the query sequence to the query address.
|
||||
result = regs.query.query_sequence;
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented query select type {}",
|
||||
@@ -174,15 +173,31 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
|
||||
// TODO(Subv): Research and implement how query sync conditions work.
|
||||
|
||||
struct LongQueryResult {
|
||||
u64_le value;
|
||||
u64_le timestamp;
|
||||
};
|
||||
static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
|
||||
|
||||
switch (regs.query.query_get.mode) {
|
||||
case Regs::QueryMode::Write:
|
||||
case Regs::QueryMode::Write2: {
|
||||
// Write the current query sequence to the sequence address.
|
||||
u32 sequence = regs.query.query_sequence;
|
||||
Memory::Write32(*address, sequence);
|
||||
|
||||
// TODO(Subv): Write the proper query response structure to the address when not using short
|
||||
// mode.
|
||||
if (regs.query.query_get.short_query) {
|
||||
// Write the current query sequence to the sequence address.
|
||||
// TODO(Subv): Find out what happens if you use a long query type but mark it as a short
|
||||
// query.
|
||||
Memory::Write32(*address, sequence);
|
||||
} else {
|
||||
// Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
|
||||
// GPU, this command may actually take a while to complete in real hardware due to GPU
|
||||
// wait queues.
|
||||
LongQueryResult query_result{};
|
||||
query_result.value = result;
|
||||
// TODO(Subv): Generate a real GPU timestamp and write it here instead of 0
|
||||
query_result.timestamp = 0;
|
||||
Memory::WriteBlock(*address, &query_result, sizeof(query_result));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -354,10 +354,35 @@ public:
|
||||
f32 scale_x;
|
||||
f32 scale_y;
|
||||
f32 scale_z;
|
||||
u32 translate_x;
|
||||
u32 translate_y;
|
||||
u32 translate_z;
|
||||
f32 translate_x;
|
||||
f32 translate_y;
|
||||
f32 translate_z;
|
||||
INSERT_PADDING_WORDS(2);
|
||||
|
||||
MathUtil::Rectangle<s32> GetRect() const {
|
||||
return {
|
||||
GetX(), // left
|
||||
GetY() + GetHeight(), // top
|
||||
GetX() + GetWidth(), // right
|
||||
GetY() // bottom
|
||||
};
|
||||
};
|
||||
|
||||
s32 GetX() const {
|
||||
return static_cast<s32>(std::max(0.0f, translate_x - std::fabs(scale_x)));
|
||||
}
|
||||
|
||||
s32 GetY() const {
|
||||
return static_cast<s32>(std::max(0.0f, translate_y - std::fabs(scale_y)));
|
||||
}
|
||||
|
||||
s32 GetWidth() const {
|
||||
return static_cast<s32>(translate_x + std::fabs(scale_x)) - GetX();
|
||||
}
|
||||
|
||||
s32 GetHeight() const {
|
||||
return static_cast<s32>(translate_y + std::fabs(scale_y)) - GetY();
|
||||
}
|
||||
} viewport_transform[NumViewports];
|
||||
|
||||
struct {
|
||||
@@ -371,15 +396,6 @@ public:
|
||||
};
|
||||
float depth_range_near;
|
||||
float depth_range_far;
|
||||
|
||||
MathUtil::Rectangle<s32> GetRect() const {
|
||||
return {
|
||||
static_cast<s32>(x), // left
|
||||
static_cast<s32>(y + height), // top
|
||||
static_cast<s32>(x + width), // right
|
||||
static_cast<s32>(y) // bottom
|
||||
};
|
||||
};
|
||||
} viewport[NumViewports];
|
||||
|
||||
INSERT_PADDING_WORDS(0x1D);
|
||||
|
||||
@@ -75,6 +75,10 @@ union Attribute {
|
||||
enum class Index : u64 {
|
||||
Position = 7,
|
||||
Attribute_0 = 8,
|
||||
// This attribute contains a tuple of (~, ~, InstanceId, VertexId) when inside a vertex
|
||||
// shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval
|
||||
// shader.
|
||||
TessCoordInstanceIDVertexID = 47,
|
||||
};
|
||||
|
||||
union {
|
||||
@@ -152,6 +156,13 @@ enum class PredOperation : u64 {
|
||||
Xor = 2,
|
||||
};
|
||||
|
||||
enum class LogicOperation : u64 {
|
||||
And = 0,
|
||||
Or = 1,
|
||||
Xor = 2,
|
||||
PassB = 3,
|
||||
};
|
||||
|
||||
enum class SubOp : u64 {
|
||||
Cos = 0x0,
|
||||
Sin = 0x1,
|
||||
@@ -198,6 +209,12 @@ union Instruction {
|
||||
BitField<42, 1, u64> negate_pred;
|
||||
} fmnmx;
|
||||
|
||||
union {
|
||||
BitField<53, 2, LogicOperation> operation;
|
||||
BitField<55, 1, u64> invert_a;
|
||||
BitField<56, 1, u64> invert_b;
|
||||
} lop;
|
||||
|
||||
float GetImm20_19() const {
|
||||
float result{};
|
||||
u32 imm{static_cast<u32>(imm20_19)};
|
||||
@@ -215,6 +232,22 @@ union Instruction {
|
||||
}
|
||||
} alu;
|
||||
|
||||
union {
|
||||
BitField<39, 5, u64> shift_amount;
|
||||
BitField<20, 19, u64> immediate_low;
|
||||
BitField<56, 1, u64> immediate_high;
|
||||
BitField<48, 1, u64> negate_b;
|
||||
BitField<49, 1, u64> negate_a;
|
||||
|
||||
s32 GetImmediate() const {
|
||||
u32 immediate = static_cast<u32>(immediate_low | (immediate_high << 19));
|
||||
// Sign extend the 20-bit value.
|
||||
u32 mask = 1U << (20 - 1);
|
||||
return static_cast<s32>((immediate ^ mask) - mask);
|
||||
}
|
||||
|
||||
} iscadd;
|
||||
|
||||
union {
|
||||
BitField<48, 1, u64> negate_b;
|
||||
BitField<49, 1, u64> negate_c;
|
||||
@@ -234,6 +267,16 @@ union Instruction {
|
||||
BitField<56, 1, u64> neg_b;
|
||||
} fsetp;
|
||||
|
||||
union {
|
||||
BitField<0, 3, u64> pred0;
|
||||
BitField<3, 3, u64> pred3;
|
||||
BitField<39, 3, u64> pred39;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
BitField<45, 2, PredOperation> op;
|
||||
BitField<48, 1, u64> is_signed;
|
||||
BitField<49, 3, PredCondition> cond;
|
||||
} isetp;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred39;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
@@ -241,9 +284,9 @@ union Instruction {
|
||||
BitField<44, 1, u64> abs_b;
|
||||
BitField<45, 2, PredOperation> op;
|
||||
BitField<48, 4, PredCondition> cond;
|
||||
BitField<52, 1, u64> bf;
|
||||
BitField<53, 1, u64> neg_b;
|
||||
BitField<54, 1, u64> abs_a;
|
||||
BitField<52, 1, u64> bf;
|
||||
BitField<55, 1, u64> ftz;
|
||||
BitField<56, 1, u64> neg_imm;
|
||||
} fset;
|
||||
@@ -257,6 +300,46 @@ union Instruction {
|
||||
BitField<50, 1, u64> saturate_a;
|
||||
} conversion;
|
||||
|
||||
union {
|
||||
BitField<31, 4, u64> component_mask;
|
||||
|
||||
bool IsComponentEnabled(size_t component) const {
|
||||
return ((1 << component) & component_mask) != 0;
|
||||
}
|
||||
} tex;
|
||||
|
||||
union {
|
||||
BitField<50, 3, u64> component_mask_selector;
|
||||
BitField<28, 8, Register> gpr28;
|
||||
|
||||
bool HasTwoDestinations() const {
|
||||
return gpr28.Value() != Register::ZeroIndex;
|
||||
}
|
||||
|
||||
bool IsComponentEnabled(size_t component) const {
|
||||
static constexpr std::array<size_t, 5> one_dest_mask{0x1, 0x2, 0x4, 0x8, 0x3};
|
||||
static constexpr std::array<size_t, 5> two_dest_mask{0x7, 0xb, 0xd, 0xe, 0xf};
|
||||
const auto& mask{HasTwoDestinations() ? two_dest_mask : one_dest_mask};
|
||||
|
||||
ASSERT(component_mask_selector < mask.size());
|
||||
|
||||
return ((1 << component) & mask[component_mask_selector]) != 0;
|
||||
}
|
||||
} texs;
|
||||
|
||||
union {
|
||||
BitField<20, 5, u64> target;
|
||||
BitField<5, 1, u64> constant_buffer;
|
||||
|
||||
s32 GetBranchTarget() const {
|
||||
// Sign extend the branch target offset
|
||||
u32 mask = 1U << (5 - 1);
|
||||
u32 value = static_cast<u32>(target);
|
||||
// The branch offset is relative to the next instruction, so add 1 to it.
|
||||
return static_cast<s32>((value ^ mask) - mask) + 1;
|
||||
}
|
||||
} bra;
|
||||
|
||||
BitField<61, 1, u64> is_b_imm;
|
||||
BitField<60, 1, u64> is_b_gpr;
|
||||
BitField<59, 1, u64> is_c_gpr;
|
||||
@@ -275,8 +358,10 @@ class OpCode {
|
||||
public:
|
||||
enum class Id {
|
||||
KIL,
|
||||
BRA,
|
||||
LD_A,
|
||||
ST_A,
|
||||
TEX,
|
||||
TEXQ, // Texture Query
|
||||
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
|
||||
TLDS, // Texture Load with scalar/non-vec4 source/destinations
|
||||
@@ -293,8 +378,13 @@ public:
|
||||
FMUL_R,
|
||||
FMUL_IMM,
|
||||
FMUL32_IMM,
|
||||
MUFU, // Multi-Function Operator
|
||||
RRO, // Range Reduction Operator
|
||||
ISCADD_C, // Scale and Add
|
||||
ISCADD_R,
|
||||
ISCADD_IMM,
|
||||
MUFU, // Multi-Function Operator
|
||||
RRO_C, // Range Reduction Operator
|
||||
RRO_R,
|
||||
RRO_IMM,
|
||||
F2F_C,
|
||||
F2F_R,
|
||||
F2F_IMM,
|
||||
@@ -333,6 +423,8 @@ public:
|
||||
enum class Type {
|
||||
Trivial,
|
||||
Arithmetic,
|
||||
Logic,
|
||||
ScaledAdd,
|
||||
Ffma,
|
||||
Flow,
|
||||
Memory,
|
||||
@@ -436,8 +528,10 @@ private:
|
||||
std::vector<Matcher> table = {
|
||||
#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name)
|
||||
INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
|
||||
INST("111000100100----", Id::BRA, Type::Flow, "BRA"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
|
||||
INST("1100000000111---", Id::TEX, Type::Memory, "TEX"),
|
||||
INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"),
|
||||
INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
|
||||
@@ -454,15 +548,19 @@ private:
|
||||
INST("0101110001101---", Id::FMUL_R, Type::Arithmetic, "FMUL_R"),
|
||||
INST("0011100-01101---", Id::FMUL_IMM, Type::Arithmetic, "FMUL_IMM"),
|
||||
INST("00011110--------", Id::FMUL32_IMM, Type::Arithmetic, "FMUL32_IMM"),
|
||||
INST("0100110000011---", Id::ISCADD_C, Type::ScaledAdd, "ISCADD_C"),
|
||||
INST("0101110000011---", Id::ISCADD_R, Type::ScaledAdd, "ISCADD_R"),
|
||||
INST("0011100-00011---", Id::ISCADD_IMM, Type::ScaledAdd, "ISCADD_IMM"),
|
||||
INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
|
||||
INST("0101110010010---", Id::RRO, Type::Arithmetic, "RRO"),
|
||||
INST("0100110010101---", Id::F2F_C, Type::Arithmetic, "F2F_C"),
|
||||
INST("0101110010101---", Id::F2F_R, Type::Arithmetic, "F2F_R"),
|
||||
INST("0011100-10101---", Id::F2F_IMM, Type::Arithmetic, "F2F_IMM"),
|
||||
INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
|
||||
INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
|
||||
INST("0011100-10010---", Id::RRO_IMM, Type::Arithmetic, "RRO_IMM"),
|
||||
INST("0100110010101---", Id::F2F_C, Type::Conversion, "F2F_C"),
|
||||
INST("0101110010101---", Id::F2F_R, Type::Conversion, "F2F_R"),
|
||||
INST("0011100-10101---", Id::F2F_IMM, Type::Conversion, "F2F_IMM"),
|
||||
INST("0100110010110---", Id::F2I_C, Type::Arithmetic, "F2I_C"),
|
||||
INST("0101110010110---", Id::F2I_R, Type::Arithmetic, "F2I_R"),
|
||||
INST("0011100-10110---", Id::F2I_IMM, Type::Arithmetic, "F2I_IMM"),
|
||||
INST("000001----------", Id::LOP32I, Type::Arithmetic, "LOP32I"),
|
||||
INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
|
||||
INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
|
||||
INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
|
||||
@@ -473,6 +571,7 @@ private:
|
||||
INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
|
||||
INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
|
||||
INST("0011100-01100---", Id::FMNMX_IMM, Type::Arithmetic, "FMNMX_IMM"),
|
||||
INST("000001----------", Id::LOP32I, Type::Logic, "LOP32I"),
|
||||
INST("0100110011100---", Id::I2I_C, Type::Conversion, "I2I_C"),
|
||||
INST("0101110011100---", Id::I2I_R, Type::Conversion, "I2I_R"),
|
||||
INST("01110001-1000---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"),
|
||||
|
||||
@@ -298,7 +298,7 @@ void RasterizerOpenGL::DrawArrays() {
|
||||
const bool has_stencil = false;
|
||||
const bool using_color_fb = true;
|
||||
const bool using_depth_fb = false;
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()};
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
|
||||
|
||||
const bool write_color_fb =
|
||||
state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE ||
|
||||
@@ -702,7 +702,7 @@ void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface,
|
||||
|
||||
void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect, u16 res_scale) {
|
||||
const auto& regs = Core::System().GetInstance().GPU().Maxwell3D().regs;
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport[0].GetRect()};
|
||||
const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()};
|
||||
|
||||
state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left * res_scale;
|
||||
state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale;
|
||||
|
||||
@@ -45,21 +45,23 @@ struct FormatTuple {
|
||||
|
||||
static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, false}, // ABGR8
|
||||
{GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, false}, // B5G6R5
|
||||
{GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, false}, // B5G6R5
|
||||
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, false}, // A2B10G10R10
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, false}, // A1B5G5R5
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE, false}, // R8
|
||||
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, false}, // RGBA16F
|
||||
{GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT1
|
||||
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT23
|
||||
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, true}, // DXT45
|
||||
{GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, true}, // DXN1
|
||||
}};
|
||||
|
||||
static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) {
|
||||
const SurfaceType type = SurfaceParams::GetFormatType(pixel_format);
|
||||
if (type == SurfaceType::ColorTexture) {
|
||||
ASSERT(static_cast<size_t>(pixel_format) < tex_format_tuples.size());
|
||||
// For now only UNORM components are supported
|
||||
ASSERT(component_type == ComponentType::UNorm);
|
||||
// For now only UNORM components are supported, or RGBA16F which is type FLOAT
|
||||
ASSERT(component_type == ComponentType::UNorm || pixel_format == PixelFormat::RGBA16F);
|
||||
return tex_format_tuples[static_cast<unsigned int>(pixel_format)];
|
||||
} else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
|
||||
// TODO(Subv): Implement depth formats
|
||||
@@ -110,8 +112,9 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra:
|
||||
morton_to_gl_fns = {
|
||||
MortonCopy<true, PixelFormat::ABGR8>, MortonCopy<true, PixelFormat::B5G6R5>,
|
||||
MortonCopy<true, PixelFormat::A2B10G10R10>, MortonCopy<true, PixelFormat::A1B5G5R5>,
|
||||
MortonCopy<true, PixelFormat::R8>, MortonCopy<true, PixelFormat::DXT1>,
|
||||
MortonCopy<true, PixelFormat::DXT23>, MortonCopy<true, PixelFormat::DXT45>,
|
||||
MortonCopy<true, PixelFormat::R8>, MortonCopy<true, PixelFormat::RGBA16F>,
|
||||
MortonCopy<true, PixelFormat::DXT1>, MortonCopy<true, PixelFormat::DXT23>,
|
||||
MortonCopy<true, PixelFormat::DXT45>, MortonCopy<true, PixelFormat::DXN1>,
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra::GPUVAddr,
|
||||
@@ -123,7 +126,9 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, Tegra::GPUVAddr, Tegra:
|
||||
MortonCopy<false, PixelFormat::A2B10G10R10>,
|
||||
MortonCopy<false, PixelFormat::A1B5G5R5>,
|
||||
MortonCopy<false, PixelFormat::R8>,
|
||||
// TODO(Subv): Swizzling the DXT1/DXT23/DXT45 formats is not yet supported
|
||||
MortonCopy<false, PixelFormat::RGBA16F>,
|
||||
// TODO(Subv): Swizzling the DXT1/DXT23/DXT45/DXN1 formats is not yet supported
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
@@ -928,7 +933,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatc
|
||||
// Use GetSurfaceSubRect instead
|
||||
ASSERT(params.width == params.stride);
|
||||
|
||||
ASSERT(!params.is_tiled || (params.width % 8 == 0 && params.height % 8 == 0));
|
||||
ASSERT(!params.is_tiled ||
|
||||
(params.GetActualWidth() % 8 == 0 && params.GetActualHeight() % 8 == 0));
|
||||
|
||||
// Check for an exact match in existing surfaces
|
||||
Surface surface =
|
||||
|
||||
@@ -59,9 +59,11 @@ struct SurfaceParams {
|
||||
A2B10G10R10 = 2,
|
||||
A1B5G5R5 = 3,
|
||||
R8 = 4,
|
||||
DXT1 = 5,
|
||||
DXT23 = 6,
|
||||
DXT45 = 7,
|
||||
RGBA16F = 5,
|
||||
DXT1 = 6,
|
||||
DXT23 = 7,
|
||||
DXT45 = 8,
|
||||
DXN1 = 9, // This is also known as BC4
|
||||
|
||||
Max,
|
||||
Invalid = 255,
|
||||
@@ -102,9 +104,11 @@ struct SurfaceParams {
|
||||
1, // A2B10G10R10
|
||||
1, // A1B5G5R5
|
||||
1, // R8
|
||||
2, // RGBA16F
|
||||
4, // DXT1
|
||||
4, // DXT23
|
||||
4, // DXT45
|
||||
4, // DXN1
|
||||
}};
|
||||
|
||||
ASSERT(static_cast<size_t>(format) < compression_factor_table.size());
|
||||
@@ -124,9 +128,11 @@ struct SurfaceParams {
|
||||
32, // A2B10G10R10
|
||||
16, // A1B5G5R5
|
||||
8, // R8
|
||||
64, // RGBA16F
|
||||
64, // DXT1
|
||||
128, // DXT23
|
||||
128, // DXT45
|
||||
64, // DXN1
|
||||
}};
|
||||
|
||||
ASSERT(static_cast<size_t>(format) < bpp_table.size());
|
||||
@@ -143,6 +149,8 @@ struct SurfaceParams {
|
||||
return PixelFormat::ABGR8;
|
||||
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
|
||||
return PixelFormat::A2B10G10R10;
|
||||
case Tegra::RenderTargetFormat::RGBA16_FLOAT:
|
||||
return PixelFormat::RGBA16F;
|
||||
default:
|
||||
NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
@@ -172,12 +180,16 @@ struct SurfaceParams {
|
||||
return PixelFormat::A1B5G5R5;
|
||||
case Tegra::Texture::TextureFormat::R8:
|
||||
return PixelFormat::R8;
|
||||
case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
|
||||
return PixelFormat::RGBA16F;
|
||||
case Tegra::Texture::TextureFormat::DXT1:
|
||||
return PixelFormat::DXT1;
|
||||
case Tegra::Texture::TextureFormat::DXT23:
|
||||
return PixelFormat::DXT23;
|
||||
case Tegra::Texture::TextureFormat::DXT45:
|
||||
return PixelFormat::DXT45;
|
||||
case Tegra::Texture::TextureFormat::DXN1:
|
||||
return PixelFormat::DXN1;
|
||||
default:
|
||||
NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
@@ -197,12 +209,16 @@ struct SurfaceParams {
|
||||
return Tegra::Texture::TextureFormat::A1B5G5R5;
|
||||
case PixelFormat::R8:
|
||||
return Tegra::Texture::TextureFormat::R8;
|
||||
case PixelFormat::RGBA16F:
|
||||
return Tegra::Texture::TextureFormat::R16_G16_B16_A16;
|
||||
case PixelFormat::DXT1:
|
||||
return Tegra::Texture::TextureFormat::DXT1;
|
||||
case PixelFormat::DXT23:
|
||||
return Tegra::Texture::TextureFormat::DXT23;
|
||||
case PixelFormat::DXT45:
|
||||
return Tegra::Texture::TextureFormat::DXT45;
|
||||
case PixelFormat::DXN1:
|
||||
return Tegra::Texture::TextureFormat::DXN1;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -226,6 +242,8 @@ struct SurfaceParams {
|
||||
case Tegra::RenderTargetFormat::RGBA8_SRGB:
|
||||
case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
|
||||
return ComponentType::UNorm;
|
||||
case Tegra::RenderTargetFormat::RGBA16_FLOAT:
|
||||
return ComponentType::Float;
|
||||
default:
|
||||
NGLOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
|
||||
UNREACHABLE();
|
||||
|
||||
@@ -88,6 +88,20 @@ private:
|
||||
return *subroutines.insert(std::move(subroutine)).first;
|
||||
}
|
||||
|
||||
/// Merges exit method of two parallel branches.
|
||||
static ExitMethod ParallelExit(ExitMethod a, ExitMethod b) {
|
||||
if (a == ExitMethod::Undetermined) {
|
||||
return b;
|
||||
}
|
||||
if (b == ExitMethod::Undetermined) {
|
||||
return a;
|
||||
}
|
||||
if (a == b) {
|
||||
return a;
|
||||
}
|
||||
return ExitMethod::Conditional;
|
||||
}
|
||||
|
||||
/// Scans a range of code for labels and determines the exit method.
|
||||
ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) {
|
||||
auto [iter, inserted] =
|
||||
@@ -97,10 +111,27 @@ private:
|
||||
return exit_method;
|
||||
|
||||
for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
|
||||
if (const auto opcode = OpCode::Decode({program_code[offset]})) {
|
||||
const Instruction instr = {program_code[offset]};
|
||||
if (const auto opcode = OpCode::Decode(instr)) {
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::EXIT: {
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
// The EXIT instruction can be predicated, which means that the shader can
|
||||
// conditionally end on this instruction. We have to consider the case where the
|
||||
// condition is not met and check the exit method of that other basic block.
|
||||
using Tegra::Shader::Pred;
|
||||
if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
} else {
|
||||
ExitMethod not_met = Scan(offset + 1, end, labels);
|
||||
return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met);
|
||||
}
|
||||
}
|
||||
case OpCode::Id::BRA: {
|
||||
u32 target = offset + instr.bra.GetBranchTarget();
|
||||
labels.insert(target);
|
||||
ExitMethod no_jmp = Scan(offset + 1, end, labels);
|
||||
ExitMethod jmp = Scan(target, end, labels);
|
||||
return exit_method = ParallelExit(no_jmp, jmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +228,11 @@ public:
|
||||
return active_type == Type::Integer;
|
||||
}
|
||||
|
||||
/// Returns the current active type of the register
|
||||
Type GetActiveType() const {
|
||||
return active_type;
|
||||
}
|
||||
|
||||
/// Returns the index of the register
|
||||
size_t GetIndex() const {
|
||||
return index;
|
||||
@@ -299,7 +335,7 @@ public:
|
||||
* are stored as floats, so this may require conversion.
|
||||
* @param reg The destination register to use.
|
||||
* @param elem The element to use for the operation.
|
||||
* @param attribute The input attibute to use as the source value.
|
||||
* @param attribute The input attribute to use as the source value.
|
||||
*/
|
||||
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute) {
|
||||
std::string dest = GetRegisterAsFloat(reg);
|
||||
@@ -328,22 +364,28 @@ public:
|
||||
shader.AddLine(dest + " = " + src + ';');
|
||||
}
|
||||
|
||||
/// Generates code representing a uniform (C buffer) register.
|
||||
std::string GetUniform(const Uniform& uniform, const Register& dest_reg) {
|
||||
/// Generates code representing a uniform (C buffer) register, interpreted as the input type.
|
||||
std::string GetUniform(const Uniform& uniform, GLSLRegister::Type type) {
|
||||
declr_const_buffers[uniform.index].MarkAsUsed(static_cast<unsigned>(uniform.index),
|
||||
static_cast<unsigned>(uniform.offset), stage);
|
||||
std::string value =
|
||||
'c' + std::to_string(uniform.index) + '[' + std::to_string(uniform.offset) + ']';
|
||||
|
||||
if (regs[dest_reg].IsFloat()) {
|
||||
if (type == GLSLRegister::Type::Float) {
|
||||
return value;
|
||||
} else if (regs[dest_reg].IsInteger()) {
|
||||
} else if (type == GLSLRegister::Type::Integer) {
|
||||
return "floatBitsToInt(" + value + ')';
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates code representing a uniform (C buffer) register, interpreted as the type of the
|
||||
/// destination register.
|
||||
std::string GetUniform(const Uniform& uniform, const Register& dest_reg) {
|
||||
return GetUniform(uniform, regs[dest_reg].GetActiveType());
|
||||
}
|
||||
|
||||
/// Add declarations for registers
|
||||
void GenerateDeclarations() {
|
||||
for (const auto& reg : regs) {
|
||||
@@ -451,6 +493,12 @@ private:
|
||||
switch (attribute) {
|
||||
case Attribute::Index::Position:
|
||||
return "position";
|
||||
case Attribute::Index::TessCoordInstanceIDVertexID:
|
||||
// TODO(Subv): Find out what the values are for the first two elements when inside a
|
||||
// vertex shader, and what's the value of the fourth element when inside a Tess Eval
|
||||
// shader.
|
||||
ASSERT(stage == Maxwell3D::Regs::ShaderStage::Vertex);
|
||||
return "vec4(0, 0, gl_InstanceID, gl_VertexID)";
|
||||
default:
|
||||
const u32 index{static_cast<u32>(attribute) -
|
||||
static_cast<u32>(Attribute::Index::Attribute_0)};
|
||||
@@ -606,9 +654,9 @@ private:
|
||||
std::string GetPredicateComparison(Tegra::Shader::PredCondition condition) const {
|
||||
using Tegra::Shader::PredCondition;
|
||||
static const std::unordered_map<PredCondition, const char*> PredicateComparisonStrings = {
|
||||
{PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
|
||||
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
|
||||
{PredCondition::GreaterEqual, ">="},
|
||||
{PredCondition::LessThan, "<"}, {PredCondition::Equal, "=="},
|
||||
{PredCondition::LessEqual, "<="}, {PredCondition::GreaterThan, ">"},
|
||||
{PredCondition::NotEqual, "!="}, {PredCondition::GreaterEqual, ">="},
|
||||
};
|
||||
|
||||
auto comparison = PredicateComparisonStrings.find(condition);
|
||||
@@ -786,8 +834,13 @@ private:
|
||||
1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::RRO: {
|
||||
NGLOG_DEBUG(HW_GPU, "Skipping RRO instruction");
|
||||
case OpCode::Id::RRO_C:
|
||||
case OpCode::Id::RRO_R:
|
||||
case OpCode::Id::RRO_IMM: {
|
||||
// Currently RRO is only implemented as a register move.
|
||||
// Usage of `abs_b` and `negate_b` here should also be correct.
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_b, 1, 1);
|
||||
NGLOG_WARNING(HW_GPU, "RRO instruction is incomplete");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -797,6 +850,73 @@ private:
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::Logic: {
|
||||
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, false);
|
||||
|
||||
if (instr.alu.lop.invert_a)
|
||||
op_a = "~(" + op_a + ')';
|
||||
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::LOP32I: {
|
||||
u32 imm = static_cast<u32>(instr.alu.imm20_32.Value());
|
||||
|
||||
if (instr.alu.lop.invert_b)
|
||||
imm = ~imm;
|
||||
|
||||
switch (instr.alu.lop.operation) {
|
||||
case Tegra::Shader::LogicOperation::And: {
|
||||
regs.SetRegisterToInteger(instr.gpr0, false, 0,
|
||||
'(' + op_a + " & " + std::to_string(imm) + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::LogicOperation::Or: {
|
||||
regs.SetRegisterToInteger(instr.gpr0, false, 0,
|
||||
'(' + op_a + " | " + std::to_string(imm) + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case Tegra::Shader::LogicOperation::Xor: {
|
||||
regs.SetRegisterToInteger(instr.gpr0, false, 0,
|
||||
'(' + op_a + " ^ " + std::to_string(imm) + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
NGLOG_CRITICAL(HW_GPU, "Unimplemented lop32i operation: {}",
|
||||
static_cast<u32>(instr.alu.lop.operation.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled logic instruction: {}", opcode->GetName());
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::ScaledAdd: {
|
||||
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
|
||||
|
||||
if (instr.iscadd.negate_a)
|
||||
op_a = '-' + op_a;
|
||||
|
||||
std::string op_b = instr.iscadd.negate_b ? "-" : "";
|
||||
|
||||
if (instr.is_b_imm) {
|
||||
op_b += '(' + std::to_string(instr.iscadd.GetImmediate()) + ')';
|
||||
} else {
|
||||
if (instr.is_b_gpr) {
|
||||
op_b += regs.GetRegisterAsInteger(instr.gpr20);
|
||||
} else {
|
||||
op_b += regs.GetUniform(instr.uniform, instr.gpr0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string shift = std::to_string(instr.iscadd.shift_amount.Value());
|
||||
|
||||
regs.SetRegisterToInteger(instr.gpr0, true, 0,
|
||||
"((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::Ffma: {
|
||||
std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
std::string op_b = instr.ffma.negate_b ? "-" : "";
|
||||
@@ -834,13 +954,13 @@ private:
|
||||
}
|
||||
case OpCode::Type::Conversion: {
|
||||
ASSERT_MSG(instr.conversion.size == Register::Size::Word, "Unimplemented");
|
||||
ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
|
||||
ASSERT_MSG(!instr.conversion.negate_a, "Unimplemented");
|
||||
ASSERT_MSG(!instr.conversion.saturate_a, "Unimplemented");
|
||||
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::I2I_R:
|
||||
case OpCode::Id::I2F_R: {
|
||||
case OpCode::Id::I2I_R: {
|
||||
ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
|
||||
|
||||
std::string op_a =
|
||||
regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_signed);
|
||||
|
||||
@@ -851,6 +971,27 @@ private:
|
||||
regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_signed, 0, op_a, 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::I2F_R: {
|
||||
std::string op_a =
|
||||
regs.GetRegisterAsInteger(instr.gpr20, 0, instr.conversion.is_signed);
|
||||
|
||||
if (instr.conversion.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::F2F_R: {
|
||||
std::string op_a = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
|
||||
if (instr.conversion.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", opcode->GetName());
|
||||
UNREACHABLE();
|
||||
@@ -874,10 +1015,10 @@ private:
|
||||
instr.gpr0);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
case OpCode::Id::TEX: {
|
||||
ASSERT_MSG(instr.attribute.fmt20.size == 4, "untested");
|
||||
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
|
||||
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
|
||||
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
|
||||
@@ -886,8 +1027,52 @@ private:
|
||||
++shader.scope;
|
||||
shader.AddLine(coord);
|
||||
const std::string texture = "texture(" + sampler + ", coords)";
|
||||
for (unsigned elem = 0; elem < instr.attribute.fmt20.size; ++elem) {
|
||||
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, elem);
|
||||
|
||||
size_t dest_elem{};
|
||||
for (size_t elem = 0; elem < instr.attribute.fmt20.size; ++elem) {
|
||||
if (!instr.tex.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
}
|
||||
regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
|
||||
++dest_elem;
|
||||
}
|
||||
--shader.scope;
|
||||
shader.AddLine("}");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TEXS: {
|
||||
ASSERT_MSG(instr.attribute.fmt20.size == 4, "untested");
|
||||
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 + ");";
|
||||
// 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("{");
|
||||
++shader.scope;
|
||||
shader.AddLine(coord);
|
||||
const std::string texture = "texture(" + sampler + ", coords)";
|
||||
|
||||
// TEXS has two destination registers. RG goes into gpr0+0 and gpr0+1, and BA goes
|
||||
// into gpr28+0 and gpr28+1
|
||||
size_t offset{};
|
||||
|
||||
for (const auto& dest : {instr.gpr0.Value(), instr.gpr28.Value()}) {
|
||||
for (unsigned elem = 0; elem < 2; ++elem) {
|
||||
if (!instr.texs.IsComponentEnabled(elem)) {
|
||||
// Skip disabled components
|
||||
continue;
|
||||
}
|
||||
regs.SetRegisterToFloat(dest, elem + offset, texture, 1, 4, false, elem);
|
||||
}
|
||||
|
||||
if (!instr.texs.HasTwoDestinations()) {
|
||||
// Skip the second destination
|
||||
break;
|
||||
}
|
||||
|
||||
offset += 2;
|
||||
}
|
||||
--shader.scope;
|
||||
shader.AddLine("}");
|
||||
@@ -920,7 +1105,7 @@ private:
|
||||
if (instr.is_b_gpr) {
|
||||
op_b += regs.GetRegisterAsFloat(instr.gpr20);
|
||||
} else {
|
||||
op_b += regs.GetUniform(instr.uniform, instr.gpr0);
|
||||
op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Float);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,13 +1129,49 @@ private:
|
||||
'(' + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
|
||||
if (instr.fsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// Set the secondary predicate to the result of !Predicate OP SecondPredicate, if
|
||||
// enabled
|
||||
// Set the secondary predicate to the result of !Predicate OP SecondPredicate,
|
||||
// if enabled
|
||||
SetPredicate(instr.fsetp.pred0,
|
||||
"!(" + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::IntegerSetPredicate: {
|
||||
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed);
|
||||
|
||||
std::string op_b{};
|
||||
|
||||
ASSERT_MSG(!instr.is_b_imm, "ISETP_IMM not implemented");
|
||||
|
||||
if (instr.is_b_gpr) {
|
||||
op_b += regs.GetRegisterAsInteger(instr.gpr20, 0, instr.isetp.is_signed);
|
||||
} else {
|
||||
op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Integer);
|
||||
}
|
||||
|
||||
using Tegra::Shader::Pred;
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.isetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string second_pred =
|
||||
GetPredicateCondition(instr.isetp.pred39, instr.isetp.neg_pred != 0);
|
||||
|
||||
std::string comparator = GetPredicateComparison(instr.isetp.cond);
|
||||
std::string combiner = GetPredicateCombiner(instr.isetp.op);
|
||||
|
||||
std::string predicate = '(' + op_a + ") " + comparator + " (" + op_b + ')';
|
||||
// Set the primary predicate to the result of Predicate OP SecondPredicate
|
||||
SetPredicate(instr.isetp.pred3,
|
||||
'(' + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
|
||||
if (instr.isetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// Set the secondary predicate to the result of !Predicate OP SecondPredicate,
|
||||
// if enabled
|
||||
SetPredicate(instr.isetp.pred0,
|
||||
"!(" + predicate + ") " + combiner + " (" + second_pred + ')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::FloatSet: {
|
||||
std::string op_a = instr.fset.neg_a ? "-" : "";
|
||||
op_a += regs.GetRegisterAsFloat(instr.gpr8);
|
||||
@@ -971,7 +1192,7 @@ private:
|
||||
if (instr.is_b_gpr) {
|
||||
op_b += regs.GetRegisterAsFloat(instr.gpr20);
|
||||
} else {
|
||||
op_b += regs.GetUniform(instr.uniform, instr.gpr0);
|
||||
op_b += regs.GetUniform(instr.uniform, GLSLRegister::Type::Float);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -990,15 +1211,17 @@ private:
|
||||
std::string predicate = "(((" + op_a + ") " + comparator + " (" + op_b + ")) " +
|
||||
combiner + " (" + second_pred + "))";
|
||||
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1);
|
||||
if (instr.fset.bf) {
|
||||
regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1);
|
||||
} else {
|
||||
regs.SetRegisterToInteger(instr.gpr0, false, 0, predicate + " ? 0xFFFFFFFF : 0", 1,
|
||||
1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::EXIT: {
|
||||
ASSERT_MSG(instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex),
|
||||
"Predicated exits not implemented");
|
||||
|
||||
// Final color output is currently hardcoded to GPR0-3 for fragment shaders
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Fragment) {
|
||||
shader.AddLine("color.r = " + regs.GetRegisterAsFloat(0) + ';');
|
||||
@@ -1008,13 +1231,25 @@ private:
|
||||
}
|
||||
|
||||
shader.AddLine("return true;");
|
||||
offset = PROGRAM_END - 1;
|
||||
if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) {
|
||||
// If this is an unconditional exit then just end processing here, otherwise we
|
||||
// have to account for the possibility of the condition not being met, so
|
||||
// continue processing the next instruction.
|
||||
offset = PROGRAM_END - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::KIL: {
|
||||
shader.AddLine("discard;");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::BRA: {
|
||||
ASSERT_MSG(instr.bra.constant_buffer == 0,
|
||||
"BRA with constant buffers are not implemented");
|
||||
u32 target = offset + instr.bra.GetBranchTarget();
|
||||
shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::IPA: {
|
||||
const auto& attribute = instr.attribute.fmt28;
|
||||
regs.SetRegisterToInputAttibute(instr.gpr0, attribute.element, attribute.index);
|
||||
|
||||
@@ -46,6 +46,7 @@ void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_
|
||||
u32 BytesPerPixel(TextureFormat format) {
|
||||
switch (format) {
|
||||
case TextureFormat::DXT1:
|
||||
case TextureFormat::DXN1:
|
||||
// In this case a 'pixel' actually refers to a 4x4 tile.
|
||||
return 8;
|
||||
case TextureFormat::DXT23:
|
||||
@@ -60,6 +61,8 @@ u32 BytesPerPixel(TextureFormat format) {
|
||||
return 2;
|
||||
case TextureFormat::R8:
|
||||
return 1;
|
||||
case TextureFormat::R16_G16_B16_A16:
|
||||
return 8;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Format not implemented");
|
||||
break;
|
||||
@@ -77,7 +80,9 @@ std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width,
|
||||
case TextureFormat::DXT1:
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
// In the DXT formats, each 4x4 tile is swizzled instead of just individual pixel values.
|
||||
case TextureFormat::DXN1:
|
||||
// In the DXT and DXN formats, each 4x4 tile is swizzled instead of just individual pixel
|
||||
// values.
|
||||
CopySwizzledData(width / 4, height / 4, bytes_per_pixel, bytes_per_pixel, data,
|
||||
unswizzled_data.data(), true, block_height);
|
||||
break;
|
||||
@@ -86,6 +91,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, TextureFormat format, u32 width,
|
||||
case TextureFormat::A1B5G5R5:
|
||||
case TextureFormat::B5G6R5:
|
||||
case TextureFormat::R8:
|
||||
case TextureFormat::R16_G16_B16_A16:
|
||||
CopySwizzledData(width, height, bytes_per_pixel, bytes_per_pixel, data,
|
||||
unswizzled_data.data(), true, block_height);
|
||||
break;
|
||||
@@ -106,6 +112,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
|
||||
case TextureFormat::DXT1:
|
||||
case TextureFormat::DXT23:
|
||||
case TextureFormat::DXT45:
|
||||
case TextureFormat::DXN1:
|
||||
case TextureFormat::A8R8G8B8:
|
||||
case TextureFormat::A2B10G10R10:
|
||||
case TextureFormat::A1B5G5R5:
|
||||
|
||||
@@ -335,6 +335,24 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GMainWindow::SupportsRequiredGLExtensions() {
|
||||
QStringList unsupported_ext;
|
||||
|
||||
if (!GLAD_GL_ARB_program_interface_query)
|
||||
unsupported_ext.append("ARB_program_interface_query");
|
||||
if (!GLAD_GL_ARB_separate_shader_objects)
|
||||
unsupported_ext.append("ARB_separate_shader_objects");
|
||||
if (!GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
unsupported_ext.append("ARB_shader_storage_buffer_object");
|
||||
if (!GLAD_GL_ARB_vertex_attrib_binding)
|
||||
unsupported_ext.append("ARB_vertex_attrib_binding");
|
||||
|
||||
for (const QString& ext : unsupported_ext)
|
||||
NGLOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString());
|
||||
|
||||
return unsupported_ext.empty();
|
||||
}
|
||||
|
||||
bool GMainWindow::LoadROM(const QString& filename) {
|
||||
// Shutdown previous session if the emu thread is still active...
|
||||
if (emu_thread != nullptr)
|
||||
@@ -350,6 +368,14 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SupportsRequiredGLExtensions()) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error while initializing OpenGL Core!"),
|
||||
tr("Your GPU may not support one or more required OpenGL extensions. Please "
|
||||
"ensure you have the latest graphics driver. See the log for more details."));
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
system.SetGPUDebugContext(debug_context);
|
||||
|
||||
@@ -79,6 +79,7 @@ private:
|
||||
void ConnectWidgetEvents();
|
||||
void ConnectMenuEvents();
|
||||
|
||||
bool SupportsRequiredGLExtensions();
|
||||
bool LoadROM(const QString& filename);
|
||||
void BootGame(const QString& filename);
|
||||
void ShutdownGame();
|
||||
|
||||
@@ -78,6 +78,24 @@ void EmuWindow_SDL2::Fullscreen() {
|
||||
SDL_MaximizeWindow(render_window);
|
||||
}
|
||||
|
||||
bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
|
||||
std::vector<std::string> unsupported_ext;
|
||||
|
||||
if (!GLAD_GL_ARB_program_interface_query)
|
||||
unsupported_ext.push_back("ARB_program_interface_query");
|
||||
if (!GLAD_GL_ARB_separate_shader_objects)
|
||||
unsupported_ext.push_back("ARB_separate_shader_objects");
|
||||
if (!GLAD_GL_ARB_shader_storage_buffer_object)
|
||||
unsupported_ext.push_back("ARB_shader_storage_buffer_object");
|
||||
if (!GLAD_GL_ARB_vertex_attrib_binding)
|
||||
unsupported_ext.push_back("ARB_vertex_attrib_binding");
|
||||
|
||||
for (const std::string& ext : unsupported_ext)
|
||||
NGLOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext);
|
||||
|
||||
return unsupported_ext.empty();
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
InputCommon::Init();
|
||||
|
||||
@@ -128,6 +146,11 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!SupportsRequiredGLExtensions()) {
|
||||
NGLOG_CRITICAL(Frontend, "GPU does not support all required OpenGL extensions! Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
OnResize();
|
||||
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
|
||||
SDL_PumpEvents();
|
||||
|
||||
@@ -46,6 +46,9 @@ private:
|
||||
/// Called when user passes the fullscreen parameter flag
|
||||
void Fullscreen();
|
||||
|
||||
/// Whether the GPU and driver supports the OpenGL extension required
|
||||
bool SupportsRequiredGLExtensions();
|
||||
|
||||
/// Called when a configuration change affects the minimal size of the window
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
|
||||
Reference in New Issue
Block a user