Compare commits

..

73 Commits

Author SHA1 Message Date
mailwl
a776464a55 Remove unused header files 2018-06-06 09:10:48 +03:00
mailwl
62cd19e4ae Small fixes 2018-06-05 15:34:01 +03:00
mailwl
7e3d746b06 Service/MM: add service and stub some functions 2018-06-05 12:19:29 +03:00
bunnei
8c99dd055c Merge pull request #518 from Subv/incomplete_shaders
GPU: Implemented predicated exit instructions in the shader programs.
2018-06-04 22:43:46 -04:00
bunnei
00749f5ab3 Merge pull request #519 from bunnei/pred-not-equal
gl_shader_decompiler: Implement PredCondition::NotEqual.
2018-06-04 22:18:22 -04:00
bunnei
6ea1576513 gl_shader_decompiler: Implement PredCondition::NotEqual. 2018-06-04 22:00:47 -04:00
bunnei
81a16c073a Merge pull request #517 from Subv/iscadd
GPU: Implement the ISCADD shader instruction.
2018-06-04 21:59:55 -04:00
Subv
23b1e6eded GPU: Implement the ISCADD shader instructions. 2018-06-04 20:17:41 -05:00
Subv
438a9b70cc GPU: Added decodings for the ISCADD instructions. 2018-06-04 20:17:39 -05:00
bunnei
e8bfff7b4b Merge pull request #514 from Subv/lop32i
GPU: Implemented the LOP32I instruction.
2018-06-04 20:48:15 -04:00
bunnei
f564822e78 Merge pull request #510 from Subv/isetp
GPU: Implemented the ISETP_R and ISETP_C instructions
2018-06-04 20:47:11 -04:00
Subv
6cf6fa2842 GPU: Implement predicated exit instructions in the shader programs. 2018-06-04 19:18:11 -05:00
Subv
d27279092f GPU: Take into account predicated exits when performing shader control flow analysis. 2018-06-04 19:14:23 -05:00
bunnei
37fd4e6d9b Merge pull request #512 from Subv/fset
GPU: Corrected the FSET and I2F instructions.
2018-06-04 19:04:20 -04:00
bunnei
cdd92dc692 Merge pull request #501 from Subv/shader_bra
GPU: Partially implemented the bra shader instruction
2018-06-04 18:31:07 -04:00
bunnei
38d25a4cb2 Merge pull request #515 from Subv/viewport_fix
GPU: Calculate the correct viewport dimensions based on the scale and translate registers.
2018-06-04 18:11:36 -04:00
Subv
2933521a08 GPU: Use the bf bit in FSET to determine whether to write 0xFFFFFFFF or 1.0f. 2018-06-04 16:41:28 -05:00
Subv
f6679ce422 GPU: Corrected the I2F_R implementation. 2018-06-04 16:41:27 -05:00
Subv
5d55403f94 GPU: Calculate the correct viewport dimensions based on the scale and translate registers.
This is how nouveau calculates the viewport width and height. For some reason some games set 0xFFFF in the VIEWPORT_HORIZ and VIEWPORT_VERT registers, maybe those are a misnomer and actually refer to something else?
2018-06-04 16:36:54 -05:00
bunnei
0a0233f39f Merge pull request #490 from BreadFish64/extension-check
Add checks for OpenGL extension support
2018-06-04 16:13:55 -04:00
bunnei
9936d1b9e2 Merge pull request #513 from Subv/cache_alignment
GLCache: Corrected a mismatch between storing compressed sizes and verifying the uncompressed alignment in GetSurface.
2018-06-04 16:12:55 -04:00
greggameplayer
4fad069870 Nvdrv/devices/nvhost_gpu : Add some IoctlCommands with their params (#511)
* Add some IoctlCommand with their params to nvhost_gpu

* fix clang-format

* delete trailing whitespace

* fix some clang-format

* delete one other trailing whitespace

* last clang-format fix
2018-06-04 16:12:02 -04:00
Subv
0c688b421c GPU: Implemented the LOP32I instruction. 2018-06-04 13:56:31 -05:00
Subv
cb47abecc6 GLCache: Corrected a mismatch between storing compressed sizes and verifying the uncompressed alignment in GetSurface. 2018-06-04 13:01:53 -05:00
BreadFish64
fbef849c04 sdl: add check for GL extension support 2018-06-04 12:26:41 -05:00
BreadFish64
0641950f9a qt: add check for GL extension support 2018-06-04 12:26:30 -05:00
bunnei
b7c64f0ded Merge pull request #502 from bunnei/more-am-stuff
am: Implement PopOutData, and various fixes.
2018-06-04 13:23:19 -04:00
Subv
90cddf1996 GPU: Use explicit types when retrieving the uniform values for fsetp/fset and isetp instead of the type of an invalid output register. 2018-06-04 11:22:26 -05:00
Subv
7c181fd4f4 GPU: Implemented the ISETP_R and ISETP_C shader instructions. 2018-06-04 11:12:03 -05:00
James Rowe
d16f83fda3 Merge pull request #507 from valentinvanelslande/3616
Port citra #3616
2018-06-04 10:04:18 -06:00
Valentin Vanelslande
5c82400ef8 Port citra #3616 2018-06-04 10:57:18 -05:00
bunnei
afdd2f4cad am: Implement ILibraryAppletAccessor::PopOutData. 2018-06-03 23:44:23 -04:00
bunnei
df4336a85e am: ISelfController:LaunchableEvent should be sticky. 2018-06-03 23:44:22 -04:00
bunnei
51d8a2c322 am: Stub out ILibraryAppletAccessor Start and GetResult methods. 2018-06-03 23:44:22 -04:00
bunnei
049ce242a4 Merge pull request #499 from bunnei/am-stuff
am: Implement CreateStorage, PushInData, etc.
2018-06-03 23:43:52 -04:00
Subv
b481d8a00d GPU: Partially implemented the shader BRA instruction. 2018-06-03 22:26:36 -05:00
Subv
06c72b4fcf GPU: Added decoding for the BRA instruction. 2018-06-03 22:14:00 -05:00
bunnei
876b805e50 am: Implement ILibraryAppletAccessor::PushInData. 2018-06-03 22:10:06 -04:00
bunnei
2dcb98226b am: Implement IStorageAccessor::Write. 2018-06-03 22:10:06 -04:00
bunnei
9fedfbe141 am: Cleanup IStorageAccessor::Read. 2018-06-03 22:10:06 -04:00
bunnei
d73c22bf4d am: Implement ILibraryAppletCreator::CreateStorage. 2018-06-03 22:10:05 -04:00
bunnei
ba117854f9 Merge pull request #500 from Subv/long_queries
GPU: Partial implementation of long GPU queries.
2018-06-03 21:24:50 -04:00
bunnei
527c098ff6 Merge pull request #498 from bunnei/texs-mask
gl_shader_decompiler: Implement TEXS component mask.
2018-06-03 21:22:12 -04:00
Subv
d57333406d GPU: Partial implementation of long GPU queries.
Long queries write a 128-bit result value to memory, which consists of a 64 bit query value and a 64 bit timestamp.

In this implementation, only select=Zero of the Crop unit is implemented, this writes the query sequence as a 64 bit value, and a 0u64 value for the timestamp, since we emulate an infinitely fast GPU.

This specific type was hwtested, but more rigorous tests should be performed in the future for the other types.
2018-06-03 19:17:31 -05:00
bunnei
1efcba346a gl_shader_decompiler: Implement TEXS component mask. 2018-06-03 12:08:17 -04:00
bunnei
bb9d39b8fe Merge pull request #494 from bunnei/shader-tex
gl_shader_decompiler: Implement TEX, fixes for TEXS.
2018-06-03 12:05:38 -04:00
bunnei
27c0f9e02d Merge pull request #495 from bunnei/improve-rro
gl_shader_decompiler: Implement RRO as a register move.
2018-06-03 12:05:26 -04:00
bunnei
41faeeeb03 Merge pull request #484 from mailwl/nvhost-nvdec
Services/nvdrv: add '/dev/nvhost-nvdec' device
2018-06-03 11:18:00 -04:00
bunnei
63270e588b Merge pull request #496 from Subv/waitprocesswidekey_timeout
Kernel/Threads: A thread waking up by timeout from a WaitProcessWideKey may already have an assigned lock owner.
2018-06-03 11:15:49 -04:00
bunnei
e54ea773fc gl_shader_decompiler: Implement RRO as a register move. 2018-06-03 11:14:31 -04:00
bunnei
0d64ddc6dd Merge pull request #497 from Subv/dxn1
GPU: Implemented the DXN1 (BC4) texture format.
2018-06-03 11:05:04 -04:00
Subv
9cd87a6352 Kernel/Threads: A thread waking up by timeout from a WaitProcessWideKey may already have an assigned lock owner.
This situation may happen like so:
Thread 1 with low priority calls WaitProcessWideKey with timeout.
Thread 2 with high priority calls WaitProcessWideKey without timeout.
Thread 3 calls SignalProcessWideKey
- Thread 2 acquires the lock and awakens.
- Thread 1 can't acquire the lock and is put to sleep with the lock owner being Thread 2.
Thread 1's timeout expires, with the lock owner still being set to Thread 2.
2018-06-02 14:06:35 -05:00
Subv
99f9d47d16 GPU: Implemented the DXN1 (BC4) texture format. 2018-06-02 13:17:09 -05:00
bunnei
bbbe34429e Merge pull request #492 from mailwl/time
Service/time: implement posix time to calendar conversion
2018-06-01 10:14:20 -04:00
mailwl
11568c2ea3 Service/time: implement posix time to calendar conversion 2018-06-01 09:40:28 +03:00
bunnei
888eb345c0 gl_shader_decompiler: Implement TEX instruction. 2018-05-31 23:36:45 -04:00
bunnei
4c727d0ba8 gl_shader_decompiler: Support multi-destination for TEXS. 2018-05-31 22:57:32 -04:00
bunnei
bdd68fc210 Merge pull request #488 from Subv/thread_masks
Kernel/SVC: Corrected the behavior of svcSetThreadCoreMask for core values -2 and -3.
2018-05-31 18:22:18 -04:00
bunnei
f1bded1270 Merge pull request #491 from bunnei/rgba16f
gl_rasterizer_cache: Implement PixelFormat RGBA16F.
2018-05-31 18:16:32 -04:00
bunnei
49309b5848 gl_rasterizer_cache: Assert that component type is UNorm or format is RGBA16F. 2018-05-30 22:50:41 -04:00
Subv
c02d7c8ce7 Kernel/Thread: Corrected a typo that caused the affinity mask to never be changed. 2018-05-30 21:36:29 -05:00
Subv
3957b0c34e Kernel/SVC: Support special core values -2 and -3 in svcSetThreadCoreMask.
Also added some proper error handling.
2018-05-30 21:36:29 -05:00
bunnei
ca5a4a704b gl_rasterizer_cache: Implement PixelFormat RGBA16F. 2018-05-30 22:24:07 -04:00
bunnei
15086a22be Merge pull request #489 from Subv/vertexid
Shaders: Implemented reading the gl_InstanceID and gl_VertexID variables in the vertex shader.
2018-05-30 14:10:48 -04:00
greggameplayer
94fecef137 add IPC CommandType & Some HID FunctionInfo (#487)
* add some CommandType

* add some hid FunctionInfo

* add some other HID FunctionInfo

* delete non useful comments
2018-05-30 14:09:21 -04:00
Subv
d1f9c750a6 Kernel/Thread: Corrected a typo in an assert about the processor id. 2018-05-30 11:32:46 -05:00
Subv
99f12b05fa Shaders: Implemented reading the gl_InstanceID and gl_VertexID variables in the vertex shader. 2018-05-30 10:58:03 -05:00
Sebastian Valle
8df011a57f Merge pull request #483 from bunnei/sonic
Several GPU fixes to boot Sonic Mania
2018-05-30 07:31:46 -05:00
bunnei
6fcc7e9c36 gl_shader_decompiler: F2F_R instruction: Implement abs. 2018-05-29 23:52:54 -04:00
bunnei
68937a662d gl_shader_decompiler: Partially implement F2F_R instruction. 2018-05-29 23:10:44 -04:00
bunnei
6306655665 nvhost_ctrl: Stub out IocCtrlEventRegister. 2018-05-29 22:39:31 -04:00
bunnei
0658973a4e nvhost_ctrl: Stub out IocCtrlEventWaitAsyncCommand. 2018-05-29 22:35:41 -04:00
bunnei
0d843eaba6 gl_rasterize_cache: Invert order of tex format RGB565. 2018-05-29 22:16:18 -04:00
31 changed files with 945 additions and 166 deletions

View File

@@ -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:

View File

@@ -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) \

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -21,7 +21,9 @@ enum {
// Confirmed Switch OS error codes
MisalignedAddress = 102,
InvalidProcessorId = 113,
InvalidHandle = 114,
InvalidCombination = 116,
Timeout = 117,
SynchronizationCanceled = 118,
TooLarge = 119,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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[] = {

View File

@@ -121,6 +121,7 @@ public:
private:
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
void CreateStorage(Kernel::HLERequestContext& ctx);
};
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {

View File

@@ -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"},
};

View 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

View 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

View File

@@ -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(&params, 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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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> {

View File

@@ -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:

View File

@@ -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);

View File

@@ -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"),

View File

@@ -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;

View File

@@ -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 =

View File

@@ -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();

View File

@@ -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);

View File

@@ -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:

View File

@@ -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);

View File

@@ -79,6 +79,7 @@ private:
void ConnectWidgetEvents();
void ConnectMenuEvents();
bool SupportsRequiredGLExtensions();
bool LoadROM(const QString& filename);
void BootGame(const QString& filename);
void ShutdownGame();

View File

@@ -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();

View File

@@ -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;