early-access version 1424
This commit is contained in:
parent
5ff3465447
commit
5c7b8e9d6c
6 changed files with 231 additions and 114 deletions
|
@ -1,7 +1,7 @@
|
||||||
yuzu emulator early access
|
yuzu emulator early access
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is the source code for early-access 1423.
|
This is the source code for early-access 1424.
|
||||||
|
|
||||||
## Legal Notice
|
## Legal Notice
|
||||||
|
|
||||||
|
|
|
@ -374,7 +374,10 @@ static ResultCode GetThreadId(Core::System& system, u64* out_thread_id, Handle t
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the thread's id.
|
// Get the thread's id.
|
||||||
*out_thread_id = thread->GetThreadID();
|
*out_thread_id = thread->GetThreadID();
|
||||||
|
@ -484,7 +487,10 @@ static ResultCode CancelSynchronization(Core::System& system, Handle thread_hand
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel the thread's wait.
|
// Cancel the thread's wait.
|
||||||
thread->WaitCancel();
|
thread->WaitCancel();
|
||||||
|
@ -502,8 +508,15 @@ static ResultCode ArbitrateLock(Core::System& system, Handle thread_handle, VAdd
|
||||||
thread_handle, address, tag);
|
thread_handle, address, tag);
|
||||||
|
|
||||||
// Validate the input address.
|
// Validate the input address.
|
||||||
R_UNLESS(!Memory::IsKernelAddress(address), Svc::ResultInvalidCurrentMemory);
|
if (Memory::IsKernelAddress(address)) {
|
||||||
R_UNLESS(Common::IsAligned(address, sizeof(u32)), Svc::ResultInvalidAddress);
|
LOG_ERROR(Kernel_SVC, "Attempting to arbitrate a lock on a kernel address (address={:08X})",
|
||||||
|
address);
|
||||||
|
return ResultInvalidCurrentMemory;
|
||||||
|
}
|
||||||
|
if (!Common::IsAligned(address, sizeof(u32))) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address);
|
||||||
|
return ResultInvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag);
|
return system.Kernel().CurrentProcess()->WaitForAddress(thread_handle, address, tag);
|
||||||
}
|
}
|
||||||
|
@ -518,8 +531,16 @@ static ResultCode ArbitrateUnlock(Core::System& system, VAddr address) {
|
||||||
LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address);
|
LOG_TRACE(Kernel_SVC, "called address=0x{:X}", address);
|
||||||
|
|
||||||
// Validate the input address.
|
// Validate the input address.
|
||||||
R_UNLESS(!Memory::IsKernelAddress(address), Svc::ResultInvalidCurrentMemory);
|
if (Memory::IsKernelAddress(address)) {
|
||||||
R_UNLESS(Common::IsAligned(address, sizeof(u32)), Svc::ResultInvalidAddress);
|
LOG_ERROR(Kernel_SVC,
|
||||||
|
"Attempting to arbitrate an unlock on a kernel address (address={:08X})",
|
||||||
|
address);
|
||||||
|
return ResultInvalidCurrentMemory;
|
||||||
|
}
|
||||||
|
if (!Common::IsAligned(address, sizeof(u32))) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Input address must be 4 byte aligned (address: {:08X})", address);
|
||||||
|
return ResultInvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
return system.Kernel().CurrentProcess()->SignalToAddress(address);
|
return system.Kernel().CurrentProcess()->SignalToAddress(address);
|
||||||
}
|
}
|
||||||
|
@ -1031,37 +1052,47 @@ static ResultCode UnmapPhysicalMemory32(Core::System& system, u32 addr, u32 size
|
||||||
return UnmapPhysicalMemory(system, addr, size);
|
return UnmapPhysicalMemory(system, addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool IsValidThreadActivity(Svc::ThreadActivity thread_activity) {
|
|
||||||
switch (thread_activity) {
|
|
||||||
case Svc::ThreadActivity::Runnable:
|
|
||||||
case Svc::ThreadActivity::Paused:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the thread activity
|
/// Sets the thread activity
|
||||||
static ResultCode SetThreadActivity(Core::System& system, Handle thread_handle,
|
static ResultCode SetThreadActivity(Core::System& system, Handle thread_handle,
|
||||||
Svc::ThreadActivity thread_activity) {
|
ThreadActivity thread_activity) {
|
||||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle,
|
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", thread_handle,
|
||||||
thread_activity);
|
thread_activity);
|
||||||
|
|
||||||
// Validate the activity.
|
// Validate the activity.
|
||||||
R_UNLESS(IsValidThreadActivity(thread_activity), Svc::ResultInvalidEnumValue);
|
constexpr auto IsValidThreadActivity = [](ThreadActivity activity) {
|
||||||
|
return activity == ThreadActivity::Runnable || activity == ThreadActivity::Paused;
|
||||||
|
};
|
||||||
|
if (!IsValidThreadActivity(thread_activity)) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread activity value provided (activity={})",
|
||||||
|
thread_activity);
|
||||||
|
return ResultInvalidEnumValue;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
auto& kernel = system.Kernel();
|
auto& kernel = system.Kernel();
|
||||||
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
|
const auto& handle_table = kernel.CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the activity is being set on a non-current thread for the current process.
|
// Check that the activity is being set on a non-current thread for the current process.
|
||||||
R_UNLESS(thread->GetOwnerProcess() == kernel.CurrentProcess(), Svc::ResultInvalidHandle);
|
if (thread->GetOwnerProcess() != kernel.CurrentProcess()) {
|
||||||
R_UNLESS(thread.get() != GetCurrentThreadPointer(kernel), Svc::ResultBusy);
|
LOG_ERROR(Kernel_SVC, "Invalid owning process for the created thread.");
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
if (thread.get() == GetCurrentThreadPointer(kernel)) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Thread is busy");
|
||||||
|
return ResultBusy;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the activity.
|
// Set the activity.
|
||||||
R_TRY(thread->SetActivity(thread_activity));
|
const auto set_result = thread->SetActivity(thread_activity);
|
||||||
|
if (set_result.IsError()) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Failed to set thread activity.");
|
||||||
|
return set_result;
|
||||||
|
}
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1080,16 +1111,29 @@ static ResultCode GetThreadContext(Core::System& system, VAddr out_context, Hand
|
||||||
const auto* current_process = system.Kernel().CurrentProcess();
|
const auto* current_process = system.Kernel().CurrentProcess();
|
||||||
const std::shared_ptr<KThread> thread =
|
const std::shared_ptr<KThread> thread =
|
||||||
current_process->GetHandleTable().Get<KThread>(thread_handle);
|
current_process->GetHandleTable().Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Require the handle be to a non-current thread in the current process.
|
// Require the handle be to a non-current thread in the current process.
|
||||||
R_UNLESS(thread->GetOwnerProcess() == current_process, Svc::ResultInvalidHandle);
|
if (thread->GetOwnerProcess() != current_process) {
|
||||||
R_UNLESS(thread.get() != system.Kernel().CurrentScheduler()->GetCurrentThread(),
|
LOG_ERROR(Kernel_SVC, "Thread owning process is not the current process.");
|
||||||
Svc::ResultBusy);
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
if (thread.get() == system.Kernel().CurrentScheduler()->GetCurrentThread()) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Current thread is busy.");
|
||||||
|
return ResultBusy;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the thread context.
|
// Get the thread context.
|
||||||
std::vector<u8> context;
|
std::vector<u8> context;
|
||||||
R_TRY(thread->GetThreadContext3(context));
|
const auto context_result = thread->GetThreadContext3(context);
|
||||||
|
if (context_result.IsError()) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Unable to successfully retrieve thread context (result: {})",
|
||||||
|
context_result.raw);
|
||||||
|
return context_result;
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the thread context to user space.
|
// Copy the thread context to user space.
|
||||||
system.Memory().WriteBlock(out_context, context.data(), context.size());
|
system.Memory().WriteBlock(out_context, context.data(), context.size());
|
||||||
|
@ -1108,7 +1152,10 @@ static ResultCode GetThreadPriority(Core::System& system, u32* out_priority, Han
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the thread's priority.
|
// Get the thread's priority.
|
||||||
*out_priority = thread->GetPriority();
|
*out_priority = thread->GetPriority();
|
||||||
|
@ -1124,13 +1171,18 @@ static ResultCode SetThreadPriority(Core::System& system, Handle handle, u32 pri
|
||||||
LOG_TRACE(Kernel_SVC, "called");
|
LOG_TRACE(Kernel_SVC, "called");
|
||||||
|
|
||||||
// Validate the priority.
|
// Validate the priority.
|
||||||
R_UNLESS(Svc::HighestThreadPriority <= priority && priority <= Svc::LowestThreadPriority,
|
if (HighestThreadPriority > priority || priority > LowestThreadPriority) {
|
||||||
Svc::ResultInvalidPriority);
|
LOG_ERROR(Kernel_SVC, "Invalid thread priority specified (priority={})", priority);
|
||||||
|
return ResultInvalidPriority;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid handle provided (handle={:08X})", handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the thread priority.
|
// Set the thread priority.
|
||||||
thread->SetBasePriority(priority);
|
thread->SetBasePriority(priority);
|
||||||
|
@ -1446,17 +1498,28 @@ static ResultCode CreateThread(Core::System& system, Handle* out_handle, VAddr e
|
||||||
// Adjust core id, if it's the default magic.
|
// Adjust core id, if it's the default magic.
|
||||||
auto& kernel = system.Kernel();
|
auto& kernel = system.Kernel();
|
||||||
auto& process = *kernel.CurrentProcess();
|
auto& process = *kernel.CurrentProcess();
|
||||||
if (core_id == Svc::IdealCoreUseProcessValue) {
|
if (core_id == IdealCoreUseProcessValue) {
|
||||||
core_id = process.GetIdealCoreId();
|
core_id = process.GetIdealCoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate arguments.
|
// Validate arguments.
|
||||||
R_UNLESS(IsValidCoreId(core_id), Svc::ResultInvalidCoreId);
|
if (!IsValidCoreId(core_id)) {
|
||||||
R_UNLESS(((1ULL << core_id) & process.GetCoreMask()) != 0, Svc::ResultInvalidCoreId);
|
LOG_ERROR(Kernel_SVC, "Invalid Core ID specified (id={})", core_id);
|
||||||
|
return ResultInvalidCoreId;
|
||||||
|
}
|
||||||
|
if (((1ULL << core_id) & process.GetCoreMask()) == 0) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Core ID doesn't fall within allowable cores (id={})", core_id);
|
||||||
|
return ResultInvalidCoreId;
|
||||||
|
}
|
||||||
|
|
||||||
R_UNLESS(Svc::HighestThreadPriority <= priority && priority <= Svc::LowestThreadPriority,
|
if (HighestThreadPriority > priority || priority > LowestThreadPriority) {
|
||||||
Svc::ResultInvalidPriority);
|
LOG_ERROR(Kernel_SVC, "Invalid priority specified (priority={})", priority);
|
||||||
R_UNLESS(process.CheckThreadPriority(priority), Svc::ResultInvalidPriority);
|
return ResultInvalidPriority;
|
||||||
|
}
|
||||||
|
if (!process.CheckThreadPriority(priority)) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid allowable thread priority (priority={})", priority);
|
||||||
|
return ResultInvalidPriority;
|
||||||
|
}
|
||||||
|
|
||||||
KScopedResourceReservation thread_reservation(
|
KScopedResourceReservation thread_reservation(
|
||||||
kernel.CurrentProcess(), LimitableResource::Threads, 1,
|
kernel.CurrentProcess(), LimitableResource::Threads, 1,
|
||||||
|
@ -1501,10 +1564,19 @@ static ResultCode StartThread(Core::System& system, Handle thread_handle) {
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle provided (handle={:08X})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Try to start the thread.
|
// Try to start the thread.
|
||||||
R_TRY(thread->Run());
|
const auto run_result = thread->Run();
|
||||||
|
if (run_result.IsError()) {
|
||||||
|
LOG_ERROR(Kernel_SVC,
|
||||||
|
"Unable to successfuly start thread (thread handle={:08X}, result={})",
|
||||||
|
thread_handle, run_result.raw);
|
||||||
|
return run_result;
|
||||||
|
}
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1565,8 +1637,14 @@ static ResultCode WaitProcessWideKeyAtomic(Core::System& system, VAddr address,
|
||||||
cv_key, tag, timeout_ns);
|
cv_key, tag, timeout_ns);
|
||||||
|
|
||||||
// Validate input.
|
// Validate input.
|
||||||
R_UNLESS(!Memory::IsKernelAddress(address), Svc::ResultInvalidCurrentMemory);
|
if (Memory::IsKernelAddress(address)) {
|
||||||
R_UNLESS(Common::IsAligned(address, sizeof(int32_t)), Svc::ResultInvalidAddress);
|
LOG_ERROR(Kernel_SVC, "Attempted to wait on kernel address (address={:08X})", address);
|
||||||
|
return ResultInvalidCurrentMemory;
|
||||||
|
}
|
||||||
|
if (!Common::IsAligned(address, sizeof(s32))) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Address must be 4 byte aligned (address={:08X})", address);
|
||||||
|
return ResultInvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert timeout from nanoseconds to ticks.
|
// Convert timeout from nanoseconds to ticks.
|
||||||
s64 timeout{};
|
s64 timeout{};
|
||||||
|
@ -1641,9 +1719,18 @@ static ResultCode WaitForAddress(Core::System& system, VAddr address, Svc::Arbit
|
||||||
address, arb_type, value, timeout_ns);
|
address, arb_type, value, timeout_ns);
|
||||||
|
|
||||||
// Validate input.
|
// Validate input.
|
||||||
R_UNLESS(!Memory::IsKernelAddress(address), Svc::ResultInvalidCurrentMemory);
|
if (Memory::IsKernelAddress(address)) {
|
||||||
R_UNLESS(Common::IsAligned(address, sizeof(int32_t)), Svc::ResultInvalidAddress);
|
LOG_ERROR(Kernel_SVC, "Attempting to wait on kernel address (address={:08X})", address);
|
||||||
R_UNLESS(IsValidArbitrationType(arb_type), Svc::ResultInvalidEnumValue);
|
return ResultInvalidCurrentMemory;
|
||||||
|
}
|
||||||
|
if (!Common::IsAligned(address, sizeof(s32))) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Wait address must be 4 byte aligned (address={:08X})", address);
|
||||||
|
return ResultInvalidAddress;
|
||||||
|
}
|
||||||
|
if (!IsValidArbitrationType(arb_type)) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid arbitration type specified (type={})", arb_type);
|
||||||
|
return ResultInvalidEnumValue;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert timeout from nanoseconds to ticks.
|
// Convert timeout from nanoseconds to ticks.
|
||||||
s64 timeout{};
|
s64 timeout{};
|
||||||
|
@ -1677,9 +1764,18 @@ static ResultCode SignalToAddress(Core::System& system, VAddr address, Svc::Sign
|
||||||
address, signal_type, value, count);
|
address, signal_type, value, count);
|
||||||
|
|
||||||
// Validate input.
|
// Validate input.
|
||||||
R_UNLESS(!Memory::IsKernelAddress(address), Svc::ResultInvalidCurrentMemory);
|
if (Memory::IsKernelAddress(address)) {
|
||||||
R_UNLESS(Common::IsAligned(address, sizeof(s32)), Svc::ResultInvalidAddress);
|
LOG_ERROR(Kernel_SVC, "Attempting to signal to a kernel address (address={:08X})", address);
|
||||||
R_UNLESS(IsValidSignalType(signal_type), Svc::ResultInvalidEnumValue);
|
return ResultInvalidCurrentMemory;
|
||||||
|
}
|
||||||
|
if (!Common::IsAligned(address, sizeof(s32))) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Signaled address must be 4 byte aligned (address={:08X})", address);
|
||||||
|
return ResultInvalidAddress;
|
||||||
|
}
|
||||||
|
if (!IsValidSignalType(signal_type)) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid signal type specified (type={})", signal_type);
|
||||||
|
return ResultInvalidEnumValue;
|
||||||
|
}
|
||||||
|
|
||||||
return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value,
|
return system.Kernel().CurrentProcess()->SignalAddressArbiter(address, signal_type, value,
|
||||||
count);
|
count);
|
||||||
|
@ -1835,10 +1931,17 @@ static ResultCode GetThreadCoreMask(Core::System& system, Handle thread_handle,
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle specified (handle={:08X})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the core mask.
|
// Get the core mask.
|
||||||
R_TRY(thread->GetCoreMask(out_core_id, out_affinity_mask));
|
const auto result = thread->GetCoreMask(out_core_id, out_affinity_mask);
|
||||||
|
if (result.IsError()) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Unable to successfully retrieve core mask (result={})", result.raw);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1866,26 +1969,46 @@ static ResultCode SetThreadCoreMask(Core::System& system, Handle thread_handle,
|
||||||
} else {
|
} else {
|
||||||
// Validate the affinity mask.
|
// Validate the affinity mask.
|
||||||
const u64 process_core_mask = current_process.GetCoreMask();
|
const u64 process_core_mask = current_process.GetCoreMask();
|
||||||
R_UNLESS((affinity_mask | process_core_mask) == process_core_mask,
|
if ((affinity_mask | process_core_mask) != process_core_mask) {
|
||||||
Svc::ResultInvalidCoreId);
|
LOG_ERROR(Kernel_SVC,
|
||||||
R_UNLESS(affinity_mask != 0, Svc::ResultInvalidCombination);
|
"Affinity mask does match the process core mask (affinity mask={:016X}, core "
|
||||||
|
"mask={:016X})",
|
||||||
|
affinity_mask, process_core_mask);
|
||||||
|
return ResultInvalidCoreId;
|
||||||
|
}
|
||||||
|
if (affinity_mask == 0) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Affinity mask is zero.");
|
||||||
|
return ResultInvalidCombination;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the core id.
|
// Validate the core id.
|
||||||
if (IsValidCoreId(core_id)) {
|
if (IsValidCoreId(core_id)) {
|
||||||
R_UNLESS(((1ULL << core_id) & affinity_mask) != 0, Svc::ResultInvalidCombination);
|
if (((1ULL << core_id) & affinity_mask) == 0) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid core ID (ID={})", core_id);
|
||||||
|
return ResultInvalidCombination;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
R_UNLESS(core_id == Svc::IdealCoreNoUpdate || core_id == Svc::IdealCoreDontCare,
|
if (core_id != IdealCoreNoUpdate && core_id != IdealCoreDontCare) {
|
||||||
Svc::ResultInvalidCoreId);
|
LOG_ERROR(Kernel_SVC, "Invalid core ID (ID={})", core_id);
|
||||||
|
return ResultInvalidCoreId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the thread from its handle.
|
// Get the thread from its handle.
|
||||||
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
|
||||||
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
const std::shared_ptr<KThread> thread = handle_table.Get<KThread>(thread_handle);
|
||||||
R_UNLESS(thread, Svc::ResultInvalidHandle);
|
if (!thread) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid thread handle (handle={:08X})", thread_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the core mask.
|
// Set the core mask.
|
||||||
R_TRY(thread->SetCoreMask(core_id, affinity_mask));
|
const auto set_result = thread->SetCoreMask(core_id, affinity_mask);
|
||||||
|
if (set_result.IsError()) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Unable to successfully set core mask (result={})", set_result.raw);
|
||||||
|
return set_result;
|
||||||
|
}
|
||||||
|
|
||||||
return RESULT_SUCCESS;
|
return RESULT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@ -1913,7 +2036,10 @@ static ResultCode SignalEvent(Core::System& system, Handle event_handle) {
|
||||||
|
|
||||||
// Get the writable event.
|
// Get the writable event.
|
||||||
auto writable_event = handle_table.Get<KWritableEvent>(event_handle);
|
auto writable_event = handle_table.Get<KWritableEvent>(event_handle);
|
||||||
R_UNLESS(writable_event, Svc::ResultInvalidHandle);
|
if (!writable_event) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Invalid event handle provided (handle={:08X})", event_handle);
|
||||||
|
return ResultInvalidHandle;
|
||||||
|
}
|
||||||
|
|
||||||
// Commit the successfuly reservation.
|
// Commit the successfuly reservation.
|
||||||
event_reservation.Commit();
|
event_reservation.Commit();
|
||||||
|
@ -1965,7 +2091,10 @@ static ResultCode CreateEvent(Core::System& system, Handle* out_write, Handle* o
|
||||||
|
|
||||||
// Create a new event.
|
// Create a new event.
|
||||||
const auto event = KEvent::Create(kernel, "CreateEvent");
|
const auto event = KEvent::Create(kernel, "CreateEvent");
|
||||||
R_UNLESS(event != nullptr, Svc::ResultOutOfResource);
|
if (!event) {
|
||||||
|
LOG_ERROR(Kernel_SVC, "Unable to create new events. Event creation limit reached.");
|
||||||
|
return ResultOutOfResource;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the event.
|
// Initialize the event.
|
||||||
event->Initialize();
|
event->Initialize();
|
||||||
|
|
|
@ -37,7 +37,7 @@ void Mouse::UpdateThread() {
|
||||||
if (configuring) {
|
if (configuring) {
|
||||||
UpdateYuzuSettings();
|
UpdateYuzuSettings();
|
||||||
}
|
}
|
||||||
if (mouse_panning_timout++ > 12) {
|
if (mouse_panning_timout++ > 8) {
|
||||||
StopPanning();
|
StopPanning();
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
|
std::this_thread::sleep_for(std::chrono::milliseconds(update_time));
|
||||||
|
@ -72,11 +72,9 @@ void Mouse::PressButton(int x, int y, int button_) {
|
||||||
void Mouse::StopPanning() {
|
void Mouse::StopPanning() {
|
||||||
for (MouseInfo& info : mouse_info) {
|
for (MouseInfo& info : mouse_info) {
|
||||||
if (Settings::values.mouse_panning) {
|
if (Settings::values.mouse_panning) {
|
||||||
if (info.data.pressed) {
|
info.data.axis = {};
|
||||||
continue;
|
|
||||||
}
|
|
||||||
info.data.axis = {0, 0};
|
|
||||||
info.tilt_speed = 0;
|
info.tilt_speed = 0;
|
||||||
|
info.last_mouse_change = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,17 +83,15 @@ void Mouse::MouseMove(int x, int y, int center_x, int center_y) {
|
||||||
for (MouseInfo& info : mouse_info) {
|
for (MouseInfo& info : mouse_info) {
|
||||||
if (Settings::values.mouse_panning) {
|
if (Settings::values.mouse_panning) {
|
||||||
const auto mouse_change = Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y);
|
const auto mouse_change = Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y);
|
||||||
const auto length =
|
|
||||||
(mouse_change.y * mouse_change.y) + (mouse_change.x * mouse_change.x);
|
|
||||||
mouse_panning_timout = 0;
|
mouse_panning_timout = 0;
|
||||||
|
|
||||||
if (info.data.pressed || length < 4) {
|
if (mouse_change.y == 0 && mouse_change.x == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
info.last_mouse_change = (info.last_mouse_change * 0.8f) + (mouse_change * 0.2f);
|
info.last_mouse_change = (info.last_mouse_change * 0.8f) + (mouse_change * 0.2f);
|
||||||
info.data.axis = {static_cast<int>(12 * info.last_mouse_change.x),
|
info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x),
|
||||||
static_cast<int>(12 * -info.last_mouse_change.y)};
|
static_cast<int>(16 * -info.last_mouse_change.y)};
|
||||||
info.tilt_direction = info.last_mouse_change;
|
info.tilt_direction = info.last_mouse_change;
|
||||||
info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
|
info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -96,7 +96,7 @@ constexpr std::array<FormatTuple, MaxPixelFormat> FORMAT_TABLE = {{
|
||||||
{GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UFLOAT
|
{GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UFLOAT
|
||||||
{GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SFLOAT
|
{GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SFLOAT
|
||||||
{GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4_UNORM
|
{GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4_UNORM
|
||||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
|
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
|
||||||
{GL_RGBA32F, GL_RGBA, GL_FLOAT}, // R32G32B32A32_FLOAT
|
{GL_RGBA32F, GL_RGBA, GL_FLOAT}, // R32G32B32A32_FLOAT
|
||||||
{GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, // R32G32B32A32_SINT
|
{GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, // R32G32B32A32_SINT
|
||||||
{GL_RG32F, GL_RG, GL_FLOAT}, // R32G32_FLOAT
|
{GL_RG32F, GL_RG, GL_FLOAT}, // R32G32_FLOAT
|
||||||
|
@ -125,7 +125,7 @@ constexpr std::array<FormatTuple, MaxPixelFormat> FORMAT_TABLE = {{
|
||||||
{GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8_UNORM
|
{GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8_UNORM
|
||||||
{GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM
|
{GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM
|
||||||
{GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM
|
{GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM
|
||||||
{GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
|
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE}, // B8G8R8A8_SRGB
|
||||||
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB
|
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB
|
||||||
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB
|
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB
|
||||||
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
|
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
|
||||||
|
@ -396,6 +396,18 @@ void AttachTexture(GLuint fbo, GLenum attachment, const ImageView* image_view) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPixelFormatBGRA(PixelFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case PixelFormat::B5G6R5_UNORM:
|
||||||
|
case PixelFormat::B10G11R11_FLOAT:
|
||||||
|
case PixelFormat::B8G8R8A8_UNORM:
|
||||||
|
case PixelFormat::B8G8R8A8_SRGB:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
ImageBufferMap::~ImageBufferMap() {
|
ImageBufferMap::~ImageBufferMap() {
|
||||||
|
@ -944,7 +956,12 @@ void ImageView::SetupView(const Device& device, Image& image, ImageViewType view
|
||||||
glTextureView(handle, target, parent, internal_format, view_range.base.level,
|
glTextureView(handle, target, parent, internal_format, view_range.base.level,
|
||||||
view_range.extent.levels, view_range.base.layer, view_range.extent.layers);
|
view_range.extent.levels, view_range.base.layer, view_range.extent.layers);
|
||||||
if (!info.IsRenderTarget()) {
|
if (!info.IsRenderTarget()) {
|
||||||
ApplySwizzle(handle, format, info.Swizzle());
|
auto swizzle = info.Swizzle();
|
||||||
|
if (IsPixelFormatBGRA(image.info.format)) {
|
||||||
|
// Swap the R and B channels of the swizzle.
|
||||||
|
std::swap(swizzle[0], swizzle[2]);
|
||||||
|
}
|
||||||
|
ApplySwizzle(handle, format, swizzle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (device.HasDebuggingToolAttached()) {
|
if (device.HasDebuggingToolAttached()) {
|
||||||
|
|
|
@ -126,7 +126,7 @@ public:
|
||||||
/// Create the original context that should be shared from
|
/// Create the original context that should be shared from
|
||||||
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
|
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
|
||||||
QSurfaceFormat format;
|
QSurfaceFormat format;
|
||||||
format.setVersion(4, 3);
|
format.setVersion(4, 6);
|
||||||
format.setProfile(QSurfaceFormat::CompatibilityProfile);
|
format.setProfile(QSurfaceFormat::CompatibilityProfile);
|
||||||
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
|
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
|
||||||
if (Settings::values.renderer_debug) {
|
if (Settings::values.renderer_debug) {
|
||||||
|
@ -656,10 +656,10 @@ bool GRenderWindow::LoadOpenGL() {
|
||||||
const QString renderer =
|
const QString renderer =
|
||||||
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||||
|
|
||||||
if (!GLAD_GL_VERSION_4_3) {
|
if (!GLAD_GL_VERSION_4_6) {
|
||||||
LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString());
|
LOG_ERROR(Frontend, "GPU does not support OpenGL 4.6: {}", renderer.toStdString());
|
||||||
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"),
|
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.6!"),
|
||||||
tr("Your GPU may not support OpenGL 4.3, or you do not have the "
|
tr("Your GPU may not support OpenGL 4.6, or you do not have the "
|
||||||
"latest graphics driver.<br><br>GL Renderer:<br>%1")
|
"latest graphics driver.<br><br>GL Renderer:<br>%1")
|
||||||
.arg(renderer));
|
.arg(renderer));
|
||||||
return false;
|
return false;
|
||||||
|
@ -682,26 +682,13 @@ bool GRenderWindow::LoadOpenGL() {
|
||||||
QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
|
QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
|
||||||
QStringList unsupported_ext;
|
QStringList unsupported_ext;
|
||||||
|
|
||||||
if (!GLAD_GL_ARB_buffer_storage)
|
|
||||||
unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
|
|
||||||
if (!GLAD_GL_ARB_direct_state_access)
|
|
||||||
unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
|
|
||||||
if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
|
|
||||||
unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
|
|
||||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
|
||||||
unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
|
|
||||||
if (!GLAD_GL_ARB_multi_bind)
|
|
||||||
unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
|
|
||||||
if (!GLAD_GL_ARB_clip_control)
|
|
||||||
unsupported_ext.append(QStringLiteral("ARB_clip_control"));
|
|
||||||
|
|
||||||
// Extensions required to support some texture formats.
|
// Extensions required to support some texture formats.
|
||||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
if (!GLAD_GL_EXT_texture_compression_s3tc) {
|
||||||
unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
|
unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
|
||||||
if (!GLAD_GL_ARB_texture_compression_rgtc)
|
}
|
||||||
|
if (!GLAD_GL_ARB_texture_compression_rgtc) {
|
||||||
unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
|
unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
|
||||||
if (!GLAD_GL_ARB_depth_buffer_float)
|
}
|
||||||
unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
|
|
||||||
|
|
||||||
if (!unsupported_ext.empty()) {
|
if (!unsupported_ext.empty()) {
|
||||||
LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
|
LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
|
||||||
|
|
|
@ -59,29 +59,17 @@ private:
|
||||||
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
|
bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
|
||||||
std::vector<std::string_view> unsupported_ext;
|
std::vector<std::string_view> unsupported_ext;
|
||||||
|
|
||||||
if (!GLAD_GL_ARB_buffer_storage)
|
|
||||||
unsupported_ext.push_back("ARB_buffer_storage");
|
|
||||||
if (!GLAD_GL_ARB_direct_state_access)
|
|
||||||
unsupported_ext.push_back("ARB_direct_state_access");
|
|
||||||
if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
|
|
||||||
unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev");
|
|
||||||
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
|
|
||||||
unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge");
|
|
||||||
if (!GLAD_GL_ARB_multi_bind)
|
|
||||||
unsupported_ext.push_back("ARB_multi_bind");
|
|
||||||
if (!GLAD_GL_ARB_clip_control)
|
|
||||||
unsupported_ext.push_back("ARB_clip_control");
|
|
||||||
|
|
||||||
// Extensions required to support some texture formats.
|
// Extensions required to support some texture formats.
|
||||||
if (!GLAD_GL_EXT_texture_compression_s3tc)
|
if (!GLAD_GL_EXT_texture_compression_s3tc) {
|
||||||
unsupported_ext.push_back("EXT_texture_compression_s3tc");
|
unsupported_ext.push_back("EXT_texture_compression_s3tc");
|
||||||
if (!GLAD_GL_ARB_texture_compression_rgtc)
|
}
|
||||||
|
if (!GLAD_GL_ARB_texture_compression_rgtc) {
|
||||||
unsupported_ext.push_back("ARB_texture_compression_rgtc");
|
unsupported_ext.push_back("ARB_texture_compression_rgtc");
|
||||||
if (!GLAD_GL_ARB_depth_buffer_float)
|
}
|
||||||
unsupported_ext.push_back("ARB_depth_buffer_float");
|
|
||||||
|
|
||||||
for (const auto& extension : unsupported_ext)
|
for (const auto& extension : unsupported_ext) {
|
||||||
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", extension);
|
LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", extension);
|
||||||
|
}
|
||||||
|
|
||||||
return unsupported_ext.empty();
|
return unsupported_ext.empty();
|
||||||
}
|
}
|
||||||
|
@ -89,7 +77,7 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
|
||||||
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen)
|
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsystem, bool fullscreen)
|
||||||
: EmuWindow_SDL2{input_subsystem} {
|
: EmuWindow_SDL2{input_subsystem} {
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6);
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||||
|
|
Loading…
Reference in a new issue