mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 15:45:42 +00:00
CPU/Recompiler: Disable memory access exceptions by default
This means it'll no longer pass amidog's CPU test in the default config. But no games rely on this. You can enable it in advanced options if you want to pass the CPU test.
This commit is contained in:
parent
b1377fe0d9
commit
1d5f810a4b
|
@ -811,6 +811,9 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
|
||||||
case 0x03: // KUSEG 1536M-2048M
|
case 0x03: // KUSEG 1536M-2048M
|
||||||
{
|
{
|
||||||
// Above 512mb raises an exception.
|
// Above 512mb raises an exception.
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
value = UINT32_C(0xFFFFFFFF);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -834,6 +837,9 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32&
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
value = UINT32_C(0xFFFFFFFF);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1106,7 +1112,7 @@ namespace Recompiler::Thunks {
|
||||||
|
|
||||||
u64 ReadMemoryByte(u32 address)
|
u64 ReadMemoryByte(u32 address)
|
||||||
{
|
{
|
||||||
u32 temp = 0;
|
u32 temp;
|
||||||
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
|
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
|
||||||
if (cycles < 0)
|
if (cycles < 0)
|
||||||
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
||||||
|
@ -1123,7 +1129,7 @@ u64 ReadMemoryHalfWord(u32 address)
|
||||||
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
|
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 temp = 0;
|
u32 temp;
|
||||||
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
|
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
|
||||||
if (cycles < 0)
|
if (cycles < 0)
|
||||||
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
||||||
|
@ -1140,7 +1146,7 @@ u64 ReadMemoryWord(u32 address)
|
||||||
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
|
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 temp = 0;
|
u32 temp;
|
||||||
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, temp);
|
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, temp);
|
||||||
if (cycles < 0)
|
if (cycles < 0)
|
||||||
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
||||||
|
@ -1193,6 +1199,44 @@ u32 WriteMemoryWord(u32 address, u32 value)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 UncheckedReadMemoryByte(u32 address)
|
||||||
|
{
|
||||||
|
u32 temp;
|
||||||
|
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 UncheckedReadMemoryHalfWord(u32 address)
|
||||||
|
{
|
||||||
|
u32 temp;
|
||||||
|
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 UncheckedReadMemoryWord(u32 address)
|
||||||
|
{
|
||||||
|
u32 temp;
|
||||||
|
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, temp);
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UncheckedWriteMemoryByte(u32 address, u8 value)
|
||||||
|
{
|
||||||
|
u32 temp = ZeroExtend32(value);
|
||||||
|
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte>(address, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UncheckedWriteMemoryHalfWord(u32 address, u16 value)
|
||||||
|
{
|
||||||
|
u32 temp = ZeroExtend32(value);
|
||||||
|
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord>(address, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UncheckedWriteMemoryWord(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Recompiler::Thunks
|
} // namespace Recompiler::Thunks
|
||||||
|
|
||||||
} // namespace CPU
|
} // namespace CPU
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "cpu_core.h"
|
#include "cpu_core.h"
|
||||||
|
#include "cpu_core_private.h"
|
||||||
#include "cpu_disasm.h"
|
#include "cpu_disasm.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include "timing_event.h"
|
#include "timing_event.h"
|
||||||
|
|
|
@ -240,33 +240,6 @@ void ClearExternalInterrupt(u8 bit)
|
||||||
g_state.cop0_regs.cause.Ip &= static_cast<u8>(~(1u << bit));
|
g_state.cop0_regs.cause.Ip &= static_cast<u8>(~(1u << bit));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasPendingInterrupt()
|
|
||||||
{
|
|
||||||
// const bool do_interrupt = g_state.m_cop0_regs.sr.IEc && ((g_state.m_cop0_regs.cause.Ip & g_state.m_cop0_regs.sr.Im)
|
|
||||||
// != 0);
|
|
||||||
const bool do_interrupt = g_state.cop0_regs.sr.IEc &&
|
|
||||||
(((g_state.cop0_regs.cause.bits & g_state.cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0);
|
|
||||||
|
|
||||||
const bool interrupt_delay = g_state.interrupt_delay;
|
|
||||||
g_state.interrupt_delay = false;
|
|
||||||
|
|
||||||
return do_interrupt && !interrupt_delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DispatchInterrupt()
|
|
||||||
{
|
|
||||||
// If the instruction we're about to execute is a GTE instruction, delay dispatching the interrupt until the next
|
|
||||||
// instruction. For some reason, if we don't do this, we end up with incorrectly sorted polygons and flickering..
|
|
||||||
if (g_state.next_instruction.IsCop2Instruction())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Interrupt raising occurs before the start of the instruction.
|
|
||||||
RaiseException(
|
|
||||||
Cop0Registers::CAUSE::MakeValueForException(Exception::INT, g_state.next_instruction_is_branch_delay_slot,
|
|
||||||
g_state.branch_was_taken, g_state.next_instruction.cop.cop_n),
|
|
||||||
g_state.regs.pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateLoadDelay()
|
void UpdateLoadDelay()
|
||||||
{
|
{
|
||||||
// the old value is needed in case the delay slot instruction overwrites the same register
|
// the old value is needed in case the delay slot instruction overwrites the same register
|
||||||
|
|
|
@ -89,8 +89,6 @@ bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
||||||
// External IRQs
|
// External IRQs
|
||||||
void SetExternalInterrupt(u8 bit);
|
void SetExternalInterrupt(u8 bit);
|
||||||
void ClearExternalInterrupt(u8 bit);
|
void ClearExternalInterrupt(u8 bit);
|
||||||
bool HasPendingInterrupt();
|
|
||||||
void DispatchInterrupt();
|
|
||||||
|
|
||||||
void DisassembleAndPrint(u32 addr);
|
void DisassembleAndPrint(u32 addr);
|
||||||
void DisassembleAndLog(u32 addr);
|
void DisassembleAndLog(u32 addr);
|
||||||
|
|
|
@ -7,6 +7,33 @@ namespace CPU {
|
||||||
void RaiseException(Exception excode);
|
void RaiseException(Exception excode);
|
||||||
void RaiseException(u32 CAUSE_bits, u32 EPC);
|
void RaiseException(u32 CAUSE_bits, u32 EPC);
|
||||||
|
|
||||||
|
ALWAYS_INLINE static bool HasPendingInterrupt()
|
||||||
|
{
|
||||||
|
// const bool do_interrupt = g_state.m_cop0_regs.sr.IEc && ((g_state.m_cop0_regs.cause.Ip & g_state.m_cop0_regs.sr.Im)
|
||||||
|
// != 0);
|
||||||
|
const bool do_interrupt = g_state.cop0_regs.sr.IEc &&
|
||||||
|
(((g_state.cop0_regs.cause.bits & g_state.cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0);
|
||||||
|
|
||||||
|
const bool interrupt_delay = g_state.interrupt_delay;
|
||||||
|
g_state.interrupt_delay = false;
|
||||||
|
|
||||||
|
return do_interrupt && !interrupt_delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE static void DispatchInterrupt()
|
||||||
|
{
|
||||||
|
// If the instruction we're about to execute is a GTE instruction, delay dispatching the interrupt until the next
|
||||||
|
// instruction. For some reason, if we don't do this, we end up with incorrectly sorted polygons and flickering..
|
||||||
|
if (g_state.next_instruction.IsCop2Instruction())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Interrupt raising occurs before the start of the instruction.
|
||||||
|
RaiseException(
|
||||||
|
Cop0Registers::CAUSE::MakeValueForException(Exception::INT, g_state.next_instruction_is_branch_delay_slot,
|
||||||
|
g_state.branch_was_taken, g_state.next_instruction.cop.cop_n),
|
||||||
|
g_state.regs.pc);
|
||||||
|
}
|
||||||
|
|
||||||
// defined in cpu_memory.cpp - memory access functions which return false if an exception was thrown.
|
// defined in cpu_memory.cpp - memory access functions which return false if an exception was thrown.
|
||||||
bool FetchInstruction();
|
bool FetchInstruction();
|
||||||
bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value);
|
bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value);
|
||||||
|
|
|
@ -1626,7 +1626,8 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
||||||
|
|
||||||
// we don't need to test the address of constant branches unless they're definitely misaligned, which would be
|
// we don't need to test the address of constant branches unless they're definitely misaligned, which would be
|
||||||
// strange.
|
// strange.
|
||||||
if (!branch_target.IsConstant() || (branch_target.constant_value & 0x3) != 0)
|
if (g_settings.cpu_recompiler_memory_exceptions &&
|
||||||
|
(!branch_target.IsConstant() || (branch_target.constant_value & 0x3) != 0))
|
||||||
{
|
{
|
||||||
LabelType branch_okay;
|
LabelType branch_okay;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "cpu_core_private.h"
|
#include "cpu_core_private.h"
|
||||||
#include "cpu_recompiler_code_generator.h"
|
#include "cpu_recompiler_code_generator.h"
|
||||||
#include "cpu_recompiler_thunks.h"
|
#include "cpu_recompiler_thunks.h"
|
||||||
|
#include "settings.h"
|
||||||
Log_SetChannel(CPU::Recompiler);
|
Log_SetChannel(CPU::Recompiler);
|
||||||
|
|
||||||
namespace a64 = vixl::aarch64;
|
namespace a64 = vixl::aarch64;
|
||||||
|
@ -1268,121 +1269,190 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const
|
||||||
{
|
{
|
||||||
AddPendingCycles(true);
|
AddPendingCycles(true);
|
||||||
|
|
||||||
// We need to use the full 64 bits here since we test the sign bit result.
|
if (g_settings.cpu_recompiler_memory_exceptions)
|
||||||
Value result = m_register_cache.AllocateScratch(RegSize_64);
|
|
||||||
|
|
||||||
// NOTE: This can leave junk in the upper bits
|
|
||||||
switch (size)
|
|
||||||
{
|
{
|
||||||
case RegSize_8:
|
// We need to use the full 64 bits here since we test the sign bit result.
|
||||||
EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address);
|
Value result = m_register_cache.AllocateScratch(RegSize_64);
|
||||||
break;
|
|
||||||
|
|
||||||
case RegSize_16:
|
// NOTE: This can leave junk in the upper bits
|
||||||
EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address);
|
switch (size)
|
||||||
break;
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address);
|
||||||
|
break;
|
||||||
|
|
||||||
case RegSize_32:
|
case RegSize_16:
|
||||||
EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address);
|
EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case RegSize_32:
|
||||||
UnreachableCode();
|
EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_register_cache.PushState();
|
||||||
|
|
||||||
|
a64::Label load_okay;
|
||||||
|
m_emit->Tbz(GetHostReg64(result.host_reg), 63, &load_okay);
|
||||||
|
EmitBranch(GetCurrentFarCodePointer());
|
||||||
|
m_emit->Bind(&load_okay);
|
||||||
|
|
||||||
|
// load exception path
|
||||||
|
SwitchToFarCode();
|
||||||
|
|
||||||
|
// cause_bits = (-result << 2) | BD | cop_n
|
||||||
|
m_emit->neg(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg));
|
||||||
|
m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2);
|
||||||
|
EmitOr(result.host_reg, result.host_reg,
|
||||||
|
Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException(
|
||||||
|
static_cast<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
|
||||||
|
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
||||||
|
|
||||||
|
EmitExceptionExit();
|
||||||
|
SwitchToNearCode();
|
||||||
|
|
||||||
|
m_register_cache.PopState();
|
||||||
|
|
||||||
|
// Downcast to ignore upper 56/48/32 bits. This should be a noop.
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_8, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_16:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_16, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_32:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_32, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
m_register_cache.PushState();
|
|
||||||
|
|
||||||
a64::Label load_okay;
|
|
||||||
m_emit->Tbz(GetHostReg64(result.host_reg), 63, &load_okay);
|
|
||||||
EmitBranch(GetCurrentFarCodePointer());
|
|
||||||
m_emit->Bind(&load_okay);
|
|
||||||
|
|
||||||
// load exception path
|
|
||||||
SwitchToFarCode();
|
|
||||||
|
|
||||||
// cause_bits = (-result << 2) | BD | cop_n
|
|
||||||
m_emit->neg(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg));
|
|
||||||
m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2);
|
|
||||||
EmitOr(result.host_reg, result.host_reg,
|
|
||||||
Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException(
|
|
||||||
static_cast<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
|
|
||||||
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
|
||||||
|
|
||||||
EmitExceptionExit();
|
|
||||||
SwitchToNearCode();
|
|
||||||
|
|
||||||
m_register_cache.PopState();
|
|
||||||
|
|
||||||
// Downcast to ignore upper 56/48/32 bits. This should be a noop.
|
|
||||||
switch (size)
|
|
||||||
{
|
{
|
||||||
case RegSize_8:
|
Value result = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
ConvertValueSizeInPlace(&result, RegSize_8, false);
|
switch (size)
|
||||||
break;
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryByte, address);
|
||||||
|
break;
|
||||||
|
|
||||||
case RegSize_16:
|
case RegSize_16:
|
||||||
ConvertValueSizeInPlace(&result, RegSize_16, false);
|
EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryHalfWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RegSize_32:
|
case RegSize_32:
|
||||||
ConvertValueSizeInPlace(&result, RegSize_32, false);
|
EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
UnreachableCode();
|
UnreachableCode();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downcast to ignore upper 56/48/32 bits. This should be a noop.
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_8, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_16:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_16, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_32:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const Value& address, const Value& value)
|
void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const Value& address, const Value& value)
|
||||||
{
|
{
|
||||||
AddPendingCycles(true);
|
AddPendingCycles(true);
|
||||||
|
|
||||||
Value result = m_register_cache.AllocateScratch(RegSize_32);
|
if (g_settings.cpu_recompiler_memory_exceptions)
|
||||||
|
|
||||||
switch (value.size)
|
|
||||||
{
|
{
|
||||||
case RegSize_8:
|
Value result = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value);
|
switch (value.size)
|
||||||
break;
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
case RegSize_16:
|
case RegSize_16:
|
||||||
EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value);
|
EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RegSize_32:
|
case RegSize_32:
|
||||||
EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value);
|
EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
UnreachableCode();
|
UnreachableCode();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_register_cache.PushState();
|
||||||
|
|
||||||
|
a64::Label store_okay;
|
||||||
|
m_emit->Cbz(GetHostReg64(result.host_reg), &store_okay);
|
||||||
|
EmitBranch(GetCurrentFarCodePointer());
|
||||||
|
m_emit->Bind(&store_okay);
|
||||||
|
|
||||||
|
// store exception path
|
||||||
|
SwitchToFarCode();
|
||||||
|
|
||||||
|
// cause_bits = (result << 2) | BD | cop_n
|
||||||
|
m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2);
|
||||||
|
EmitOr(result.host_reg, result.host_reg,
|
||||||
|
Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException(
|
||||||
|
static_cast<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
|
||||||
|
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
||||||
|
|
||||||
|
EmitExceptionExit();
|
||||||
|
SwitchToNearCode();
|
||||||
|
|
||||||
|
m_register_cache.PopState();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (value.size)
|
||||||
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryByte, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
m_register_cache.PushState();
|
case RegSize_16:
|
||||||
|
EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryHalfWord, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
a64::Label store_okay;
|
case RegSize_32:
|
||||||
m_emit->Cbz(GetHostReg64(result.host_reg), &store_okay);
|
EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryWord, address, value);
|
||||||
EmitBranch(GetCurrentFarCodePointer());
|
break;
|
||||||
m_emit->Bind(&store_okay);
|
|
||||||
|
|
||||||
// store exception path
|
default:
|
||||||
SwitchToFarCode();
|
UnreachableCode();
|
||||||
|
break;
|
||||||
// cause_bits = (result << 2) | BD | cop_n
|
}
|
||||||
m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2);
|
}
|
||||||
EmitOr(result.host_reg, result.host_reg,
|
|
||||||
Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException(
|
|
||||||
static_cast<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
|
|
||||||
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
|
||||||
|
|
||||||
EmitExceptionExit();
|
|
||||||
SwitchToNearCode();
|
|
||||||
|
|
||||||
m_register_cache.PopState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)
|
void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "cpu_core_private.h"
|
#include "cpu_core_private.h"
|
||||||
#include "cpu_recompiler_code_generator.h"
|
#include "cpu_recompiler_code_generator.h"
|
||||||
#include "cpu_recompiler_thunks.h"
|
#include "cpu_recompiler_thunks.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
namespace CPU::Recompiler {
|
namespace CPU::Recompiler {
|
||||||
|
|
||||||
|
@ -1739,117 +1740,186 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const
|
||||||
{
|
{
|
||||||
AddPendingCycles(true);
|
AddPendingCycles(true);
|
||||||
|
|
||||||
// We need to use the full 64 bits here since we test the sign bit result.
|
if (g_settings.cpu_recompiler_memory_exceptions)
|
||||||
Value result = m_register_cache.AllocateScratch(RegSize_64);
|
|
||||||
|
|
||||||
// NOTE: This can leave junk in the upper bits
|
|
||||||
switch (size)
|
|
||||||
{
|
{
|
||||||
case RegSize_8:
|
// We need to use the full 64 bits here since we test the sign bit result.
|
||||||
EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address);
|
Value result = m_register_cache.AllocateScratch(RegSize_64);
|
||||||
break;
|
|
||||||
|
|
||||||
case RegSize_16:
|
// NOTE: This can leave junk in the upper bits
|
||||||
EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address);
|
switch (size)
|
||||||
break;
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address);
|
||||||
|
break;
|
||||||
|
|
||||||
case RegSize_32:
|
case RegSize_16:
|
||||||
EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address);
|
EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case RegSize_32:
|
||||||
UnreachableCode();
|
EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_emit->test(GetHostReg64(result.host_reg), GetHostReg64(result.host_reg));
|
||||||
|
m_emit->js(GetCurrentFarCodePointer());
|
||||||
|
|
||||||
|
m_register_cache.PushState();
|
||||||
|
|
||||||
|
// load exception path
|
||||||
|
SwitchToFarCode();
|
||||||
|
|
||||||
|
// cause_bits = (-result << 2) | BD | cop_n
|
||||||
|
m_emit->neg(GetHostReg32(result.host_reg));
|
||||||
|
m_emit->shl(GetHostReg32(result.host_reg), 2);
|
||||||
|
m_emit->or_(GetHostReg32(result.host_reg),
|
||||||
|
Cop0Registers::CAUSE::MakeValueForException(static_cast<Exception>(0), cbi.is_branch_delay_slot, false,
|
||||||
|
cbi.instruction.cop.cop_n));
|
||||||
|
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
||||||
|
|
||||||
|
EmitExceptionExit();
|
||||||
|
SwitchToNearCode();
|
||||||
|
|
||||||
|
m_register_cache.PopState();
|
||||||
|
|
||||||
|
// Downcast to ignore upper 56/48/32 bits. This should be a noop.
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_8, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_16:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_16, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_32:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_32, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
m_emit->test(GetHostReg64(result.host_reg), GetHostReg64(result.host_reg));
|
|
||||||
m_emit->js(GetCurrentFarCodePointer());
|
|
||||||
|
|
||||||
m_register_cache.PushState();
|
|
||||||
|
|
||||||
// load exception path
|
|
||||||
SwitchToFarCode();
|
|
||||||
|
|
||||||
// cause_bits = (-result << 2) | BD | cop_n
|
|
||||||
m_emit->neg(GetHostReg32(result.host_reg));
|
|
||||||
m_emit->shl(GetHostReg32(result.host_reg), 2);
|
|
||||||
m_emit->or_(GetHostReg32(result.host_reg),
|
|
||||||
Cop0Registers::CAUSE::MakeValueForException(static_cast<Exception>(0), cbi.is_branch_delay_slot, false,
|
|
||||||
cbi.instruction.cop.cop_n));
|
|
||||||
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
|
||||||
|
|
||||||
EmitExceptionExit();
|
|
||||||
SwitchToNearCode();
|
|
||||||
|
|
||||||
m_register_cache.PopState();
|
|
||||||
|
|
||||||
// Downcast to ignore upper 56/48/32 bits. This should be a noop.
|
|
||||||
switch (size)
|
|
||||||
{
|
{
|
||||||
case RegSize_8:
|
Value result = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
ConvertValueSizeInPlace(&result, RegSize_8, false);
|
switch (size)
|
||||||
break;
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryByte, address);
|
||||||
|
break;
|
||||||
|
|
||||||
case RegSize_16:
|
case RegSize_16:
|
||||||
ConvertValueSizeInPlace(&result, RegSize_16, false);
|
EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryHalfWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RegSize_32:
|
case RegSize_32:
|
||||||
ConvertValueSizeInPlace(&result, RegSize_32, false);
|
EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryWord, address);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
UnreachableCode();
|
UnreachableCode();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downcast to ignore upper 56/48/32 bits. This should be a noop.
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_8, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_16:
|
||||||
|
ConvertValueSizeInPlace(&result, RegSize_16, false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RegSize_32:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
UnreachableCode();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const Value& address, const Value& value)
|
void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const Value& address, const Value& value)
|
||||||
{
|
{
|
||||||
AddPendingCycles(true);
|
AddPendingCycles(true);
|
||||||
|
|
||||||
Value result = m_register_cache.AllocateScratch(RegSize_32);
|
if (g_settings.cpu_recompiler_memory_exceptions)
|
||||||
|
|
||||||
switch (value.size)
|
|
||||||
{
|
{
|
||||||
case RegSize_8:
|
Value result = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value);
|
switch (value.size)
|
||||||
break;
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
case RegSize_16:
|
case RegSize_16:
|
||||||
EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value);
|
EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RegSize_32:
|
case RegSize_32:
|
||||||
EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value);
|
EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
UnreachableCode();
|
UnreachableCode();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_register_cache.PushState();
|
||||||
|
|
||||||
|
m_emit->test(GetHostReg32(result), GetHostReg32(result));
|
||||||
|
m_emit->jnz(GetCurrentFarCodePointer());
|
||||||
|
|
||||||
|
// store exception path
|
||||||
|
SwitchToFarCode();
|
||||||
|
|
||||||
|
// cause_bits = (result << 2) | BD | cop_n
|
||||||
|
m_emit->shl(GetHostReg32(result.host_reg), 2);
|
||||||
|
m_emit->or_(GetHostReg32(result.host_reg),
|
||||||
|
Cop0Registers::CAUSE::MakeValueForException(static_cast<Exception>(0), cbi.is_branch_delay_slot, false,
|
||||||
|
cbi.instruction.cop.cop_n));
|
||||||
|
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
||||||
|
|
||||||
|
EmitExceptionExit();
|
||||||
|
SwitchToNearCode();
|
||||||
|
|
||||||
|
m_register_cache.PopState();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (value.size)
|
||||||
|
{
|
||||||
|
case RegSize_8:
|
||||||
|
EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryByte, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
m_register_cache.PushState();
|
case RegSize_16:
|
||||||
|
EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryHalfWord, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
m_emit->test(GetHostReg32(result), GetHostReg32(result));
|
case RegSize_32:
|
||||||
m_emit->jnz(GetCurrentFarCodePointer());
|
EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryWord, address, value);
|
||||||
|
break;
|
||||||
|
|
||||||
// store exception path
|
default:
|
||||||
SwitchToFarCode();
|
UnreachableCode();
|
||||||
|
break;
|
||||||
// cause_bits = (result << 2) | BD | cop_n
|
}
|
||||||
m_emit->shl(GetHostReg32(result.host_reg), 2);
|
}
|
||||||
m_emit->or_(GetHostReg32(result.host_reg),
|
|
||||||
Cop0Registers::CAUSE::MakeValueForException(static_cast<Exception>(0), cbi.is_branch_delay_slot, false,
|
|
||||||
cbi.instruction.cop.cop_n));
|
|
||||||
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
|
|
||||||
|
|
||||||
EmitExceptionExit();
|
|
||||||
SwitchToNearCode();
|
|
||||||
|
|
||||||
m_register_cache.PopState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)
|
void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)
|
||||||
|
|
|
@ -22,6 +22,15 @@ u32 WriteMemoryByte(u32 address, u8 value);
|
||||||
u32 WriteMemoryHalfWord(u32 address, u16 value);
|
u32 WriteMemoryHalfWord(u32 address, u16 value);
|
||||||
u32 WriteMemoryWord(u32 address, u32 value);
|
u32 WriteMemoryWord(u32 address, u32 value);
|
||||||
|
|
||||||
|
// Unchecked memory access variants. No alignment or bus exceptions.
|
||||||
|
u32 UncheckedReadMemoryByte(u32 address);
|
||||||
|
u32 UncheckedReadMemoryHalfWord(u32 address);
|
||||||
|
u32 UncheckedReadMemoryWord(u32 address);
|
||||||
|
void UncheckedWriteMemoryByte(u32 address, u8 value);
|
||||||
|
void UncheckedWriteMemoryHalfWord(u32 address, u16 value);
|
||||||
|
void UncheckedWriteMemoryWord(u32 address, u32 value);
|
||||||
|
|
||||||
|
|
||||||
} // namespace Recompiler::Thunks
|
} // namespace Recompiler::Thunks
|
||||||
|
|
||||||
} // namespace CPU
|
} // namespace CPU
|
|
@ -358,6 +358,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
||||||
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false);
|
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false);
|
||||||
|
|
||||||
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
|
si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE));
|
||||||
|
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false);
|
||||||
|
|
||||||
si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER));
|
si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER));
|
||||||
si.SetIntValue("GPU", "ResolutionScale", 1);
|
si.SetIntValue("GPU", "ResolutionScale", 1);
|
||||||
|
@ -474,6 +475,14 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler);
|
CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler &&
|
||||||
|
g_settings.cpu_recompiler_memory_exceptions != old_settings.cpu_recompiler_memory_exceptions)
|
||||||
|
{
|
||||||
|
ReportFormattedMessage("CPU memory exceptions %s, flushing all blocks.",
|
||||||
|
g_settings.cpu_recompiler_memory_exceptions ? "enabled" : "disabled");
|
||||||
|
CPU::CodeCache::Flush();
|
||||||
|
}
|
||||||
|
|
||||||
m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume);
|
m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume);
|
||||||
|
|
||||||
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale ||
|
if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale ||
|
||||||
|
|
|
@ -89,6 +89,7 @@ void Settings::Load(SettingsInterface& si)
|
||||||
ParseCPUExecutionMode(
|
ParseCPUExecutionMode(
|
||||||
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
|
si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str())
|
||||||
.value_or(DEFAULT_CPU_EXECUTION_MODE);
|
.value_or(DEFAULT_CPU_EXECUTION_MODE);
|
||||||
|
cpu_recompiler_memory_exceptions = si.GetBoolValue("CPU", "RecompilerMemoryExceptions", false);
|
||||||
|
|
||||||
gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str())
|
gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str())
|
||||||
.value_or(DEFAULT_GPU_RENDERER);
|
.value_or(DEFAULT_GPU_RENDERER);
|
||||||
|
@ -196,6 +197,7 @@ void Settings::Save(SettingsInterface& si) const
|
||||||
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states);
|
si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states);
|
||||||
|
|
||||||
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
|
si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode));
|
||||||
|
si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions);
|
||||||
|
|
||||||
si.SetStringValue("GPU", "Renderer", GetRendererName(gpu_renderer));
|
si.SetStringValue("GPU", "Renderer", GetRendererName(gpu_renderer));
|
||||||
si.SetStringValue("GPU", "Adapter", gpu_adapter.c_str());
|
si.SetStringValue("GPU", "Adapter", gpu_adapter.c_str());
|
||||||
|
|
|
@ -68,6 +68,7 @@ struct Settings
|
||||||
ConsoleRegion region = ConsoleRegion::Auto;
|
ConsoleRegion region = ConsoleRegion::Auto;
|
||||||
|
|
||||||
CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter;
|
CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter;
|
||||||
|
bool cpu_recompiler_memory_exceptions = false;
|
||||||
|
|
||||||
float emulation_speed = 1.0f;
|
float emulation_speed = 1.0f;
|
||||||
bool speed_limiter_enabled = true;
|
bool speed_limiter_enabled = true;
|
||||||
|
|
|
@ -23,6 +23,8 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface,
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.dmaHaltTicks, "Hacks", "DMAHaltTicks");
|
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.dmaHaltTicks, "Hacks", "DMAHaltTicks");
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuFIFOSize, "Hacks", "GPUFIFOSize");
|
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuFIFOSize, "Hacks", "GPUFIFOSize");
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuMaxRunAhead, "Hacks", "GPUMaxRunAhead");
|
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuMaxRunAhead, "Hacks", "GPUMaxRunAhead");
|
||||||
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU",
|
||||||
|
"RecompilerMemoryExceptions", false);
|
||||||
|
|
||||||
connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked);
|
connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked);
|
||||||
}
|
}
|
||||||
|
@ -35,4 +37,5 @@ void AdvancedSettingsWidget::onResetToDefaultClicked()
|
||||||
m_ui.dmaHaltTicks->setValue(static_cast<int>(Settings::DEFAULT_DMA_HALT_TICKS));
|
m_ui.dmaHaltTicks->setValue(static_cast<int>(Settings::DEFAULT_DMA_HALT_TICKS));
|
||||||
m_ui.gpuFIFOSize->setValue(static_cast<int>(Settings::DEFAULT_GPU_FIFO_SIZE));
|
m_ui.gpuFIFOSize->setValue(static_cast<int>(Settings::DEFAULT_GPU_FIFO_SIZE));
|
||||||
m_ui.gpuMaxRunAhead->setValue(static_cast<int>(Settings::DEFAULT_GPU_MAX_RUN_AHEAD));
|
m_ui.gpuMaxRunAhead->setValue(static_cast<int>(Settings::DEFAULT_GPU_MAX_RUN_AHEAD));
|
||||||
|
m_ui.cpuRecompilerMemoryExceptions->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,13 +184,20 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="2">
|
<item row="6" column="0" colspan="2">
|
||||||
<widget class="QPushButton" name="resetToDefaultButton">
|
<widget class="QPushButton" name="resetToDefaultButton">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Reset To Default</string>
|
<string>Reset To Default</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0" colspan="2">
|
||||||
|
<widget class="QCheckBox" name="cpuRecompilerMemoryExceptions">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable Recompiler Memory Exceptions</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -1231,6 +1231,9 @@ void SDLHostInterface::DrawSettingsWindow()
|
||||||
settings_changed = true;
|
settings_changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
settings_changed |=
|
||||||
|
ImGui::Checkbox("Enable Recompiler Memory Exceptions", &m_settings_copy.cpu_recompiler_memory_exceptions);
|
||||||
|
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue