diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 17771d19cc..b3ef8540db 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -733,9 +733,11 @@ void ARM64XEmitter::EncodeLoadStoreIndexedInst(u32 op, ARM64Reg Rt, ARM64Reg Rn, else if (size == 16) shift = 1; - _assert_msg_(DYNA_REC, ((imm >> shift) << shift) == imm, "%s(INDEX_UNSIGNED): offset must be aligned %d", __FUNCTION__, imm); + if (shift) { + _assert_msg_(DYNA_REC, ((imm >> shift) << shift) == imm, "%s(INDEX_UNSIGNED): offset must be aligned %d", __FUNCTION__, imm); + imm >>= shift; + } - imm >>= shift; _assert_msg_(DYNA_REC, imm >= 0, "%s(INDEX_UNSIGNED): offset must be positive %d", __FUNCTION__, imm); _assert_msg_(DYNA_REC, !(imm & ~0xFFF), "%s(INDEX_UNSIGNED): offset too large %d", __FUNCTION__, imm); diff --git a/Common/Vulkan/VulkanMemory.cpp b/Common/Vulkan/VulkanMemory.cpp index 67d300dc44..c46195db30 100644 --- a/Common/Vulkan/VulkanMemory.cpp +++ b/Common/Vulkan/VulkanMemory.cpp @@ -134,6 +134,27 @@ size_t VulkanPushBuffer::GetTotalSize() const { return sum; } +void VulkanPushBuffer::Map() { + assert(!writePtr_); + VkResult res = vkMapMemory(device_, buffers_[buf_].deviceMemory, 0, size_, 0, (void **)(&writePtr_)); + assert(writePtr_); + assert(VK_SUCCESS == res); +} + +void VulkanPushBuffer::Unmap() { + assert(writePtr_); + /* + // Should not need this since we use coherent memory. + VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; + range.offset = 0; + range.size = offset_; + range.memory = buffers_[buf_].deviceMemory; + vkFlushMappedMemoryRanges(device_, 1, &range); + */ + vkUnmapMemory(device_, buffers_[buf_].deviceMemory); + writePtr_ = nullptr; +} + VulkanDeviceAllocator::VulkanDeviceAllocator(VulkanContext *vulkan, size_t minSlabSize, size_t maxSlabSize) : vulkan_(vulkan), lastSlab_(0), minSlabSize_(minSlabSize), maxSlabSize_(maxSlabSize), memoryTypeIndex_(UNDEFINED_MEMORY_TYPE), destroyed_(false) { assert((minSlabSize_ & (SLAB_GRAIN_SIZE - 1)) == 0); diff --git a/Common/Vulkan/VulkanMemory.h b/Common/Vulkan/VulkanMemory.h index 96f5833340..15f09d1265 100644 --- a/Common/Vulkan/VulkanMemory.h +++ b/Common/Vulkan/VulkanMemory.h @@ -46,26 +46,9 @@ public: Unmap(); } - void Map() { - assert(!writePtr_); - VkResult res = vkMapMemory(device_, buffers_[buf_].deviceMemory, 0, size_, 0, (void **)(&writePtr_)); - assert(writePtr_); - assert(VK_SUCCESS == res); - } + void Map(); - void Unmap() { - assert(writePtr_); - /* - // Should not need this since we use coherent memory. - VkMappedMemoryRange range = { VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE }; - range.offset = 0; - range.size = offset_; - range.memory = buffers_[buf_].deviceMemory; - vkFlushMappedMemoryRanges(device_, 1, &range); - */ - vkUnmapMemory(device_, buffers_[buf_].deviceMemory); - writePtr_ = nullptr; - } + void Unmap(); // When using the returned memory, make sure to bind the returned vkbuf. // This will later allow for handling overflow correctly. diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index b277414422..0b14aa764a 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -87,7 +87,12 @@ bool CheatFileParser::Parse() { getline(file_, line, '\n'); line = TrimString(line); - if (line.length() > 2 && line[0] == '_') { + // Minimum length is set to 5 just to match GetCodesList() function + // which discards anything shorter when called anyway. + // It's decided from shortest possible _ lines name of the game "_G N+" + // and a minimum of 1 displayable character in cheat name string "_C0 1" + // which both equal to 5 characters. + if (line.length() >= 5 && line[0] == '_') { ParseLine(line); } else if (line.length() >= 2 && line[0] == '/' && line[1] == '/') { // Comment, ignore. @@ -340,7 +345,21 @@ std::vector CWCheatEngine::GetCodesList() { std::string line; getline(list, line, '\n'); - if (line.length() > 3 && (line.substr(0, 1) == "_" || line.substr(0, 2) == "//")) { + bool validCheatLine = false; + // This function is called by cheat menu(UI) which doesn't support empty names + // minimum 1 non space character is required starting from 5 position. + // It also goes through other "_" lines, but they all have to meet this requirement anyway + // so we don't have to specify any syntax checks here that are made by cheat engine. + if (line.length() >= 5 && line[0] == '_') { + for (int i = 4; i < line.length(); i++) { + if (line[i] != ' ') { + validCheatLine = true; + break; + } + } + } + // Any lines not passing this check are discarded when we save changes to the cheat ini file + if (validCheatLine || (line.length() >= 2 && line[0] == '/' && line[1] == '/') || (line.length() >= 1 && line[0] == '#')) { codesList.push_back(TrimString(line)); } } diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index e36a8c4109..8b63cfc401 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -635,7 +635,7 @@ size_t hleFormatLogArgs(char *message, size_t sz, const char *argmask) { // TODO: Double? Does it ever happen? default: - _assert_msg_(HLE, false, "Invalid argmask character: %c", argmask[i]); + _dbg_assert_msg_(HLE, false, "Invalid argmask character: %c", argmask[i]); APPEND_FMT(" -- invalid arg format: %c -- %08x", argmask[i], regval); break; } @@ -673,7 +673,7 @@ void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res, // TODO: For now, floats are just shown as bits. fmt = "%s%08x=%s(%s)%s"; } else { - _assert_msg_(HLE, false, "Invalid return format: %c", retmask); + _dbg_assert_msg_(HLE, false, "Invalid return format: %c", retmask); fmt = "%s%08llx=%s(%s)%s"; } diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 507238f459..0a7c3ad538 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -106,21 +106,6 @@ void ArmJit::DoState(PointerWrap &p) } } -// This is here so the savestate matches between jit and non-jit. -void ArmJit::DoDummyState(PointerWrap &p) -{ - auto s = p.Section("Jit", 1, 2); - if (!s) - return; - - bool dummy = false; - p.Do(dummy); - if (s >= 2) { - dummy = true; - p.Do(dummy); - } -} - void ArmJit::FlushAll() { gpr.FlushAll(); diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index 511ce5fce1..77b4c9ea51 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -39,7 +39,6 @@ public: virtual ~ArmJit(); void DoState(PointerWrap &p) override; - void DoDummyState(PointerWrap &p) override; const JitOptions &GetJitOptions() { return jo; } diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index fab227f303..ae9b6b8f4e 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -98,20 +98,6 @@ void Arm64Jit::DoState(PointerWrap &p) { } } -// This is here so the savestate matches between jit and non-jit. -void Arm64Jit::DoDummyState(PointerWrap &p) { - auto s = p.Section("Jit", 1, 2); - if (!s) - return; - - bool dummy = false; - p.Do(dummy); - if (s >= 2) { - dummy = true; - p.Do(dummy); - } -} - void Arm64Jit::FlushAll() { gpr.FlushAll(); diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index b2d853c3b2..e8874fa0b5 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -39,7 +39,6 @@ public: virtual ~Arm64Jit(); void DoState(PointerWrap &p) override; - void DoDummyState(PointerWrap &p) override; const JitOptions &GetJitOptions() { return jo; } diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 26f66bc6ad..b36c40f110 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -50,20 +50,6 @@ void IRJit::DoState(PointerWrap &p) { frontend_.DoState(p); } -// This is here so the savestate matches between jit and non-jit. -void IRJit::DoDummyState(PointerWrap &p) { - auto s = p.Section("Jit", 1, 2); - if (!s) - return; - - bool dummy = false; - p.Do(dummy); - if (s >= 2) { - dummy = true; - p.Do(dummy); - } -} - void IRJit::ClearCache() { ILOG("IRJit: Clearing the cache!"); blocks_.Clear(); diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 84851df7f3..67fa3a993c 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -127,7 +127,6 @@ public: virtual ~IRJit(); void DoState(PointerWrap &p) override; - void DoDummyState(PointerWrap &p) override; const JitOptions &GetJitOptions() { return jo; } diff --git a/Core/MIPS/JitCommon/JitCommon.cpp b/Core/MIPS/JitCommon/JitCommon.cpp index 55409c77c6..f7792a110c 100644 --- a/Core/MIPS/JitCommon/JitCommon.cpp +++ b/Core/MIPS/JitCommon/JitCommon.cpp @@ -21,6 +21,8 @@ #include "ext/udis86/udis86.h" #include "Common/StringUtils.h" +#include "Common/ChunkFile.h" + #include "Core/Util/DisArm64.h" #include "Core/Config.h" @@ -46,6 +48,20 @@ namespace MIPSComp { jit->Compile(currentMIPS->pc); } + void DoDummyJitState(PointerWrap &p) { + // This is here so the savestate matches between jit and non-jit. + auto s = p.Section("Jit", 1, 2); + if (!s) + return; + + bool dummy = false; + p.Do(dummy); + if (s >= 2) { + dummy = true; + p.Do(dummy); + } + } + JitInterface *CreateNativeJit(MIPSState *mips) { #if PPSSPP_ARCH(ARM) return new MIPSComp::ArmJit(mips); diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index 9e91664713..6296506a01 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -125,7 +125,6 @@ namespace MIPSComp { virtual JitBlockCache *GetBlockCache() = 0; virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0; virtual void DoState(PointerWrap &p) = 0; - virtual void DoDummyState(PointerWrap &p) = 0; virtual void RunLoopUntil(u64 globalticks) = 0; virtual void Compile(u32 em_address) = 0; virtual void ClearCache() = 0; @@ -147,5 +146,7 @@ namespace MIPSComp { extern JitInterface *jit; + void DoDummyJitState(PointerWrap &p); + JitInterface *CreateNativeJit(MIPSState *mips); } diff --git a/Core/MIPS/MIPS.cpp b/Core/MIPS/MIPS.cpp index f046d05271..2302928968 100644 --- a/Core/MIPS/MIPS.cpp +++ b/Core/MIPS/MIPS.cpp @@ -262,7 +262,7 @@ void MIPSState::DoState(PointerWrap &p) { if (MIPSComp::jit) MIPSComp::jit->DoState(p); else - MIPSComp::jit->DoDummyState(p); + MIPSComp::DoDummyJitState(p); p.DoArray(r, sizeof(r) / sizeof(r[0])); p.DoArray(f, sizeof(f) / sizeof(f[0])); diff --git a/Core/MIPS/MIPS/MipsJit.cpp b/Core/MIPS/MIPS/MipsJit.cpp index b669109899..48637e8f23 100644 --- a/Core/MIPS/MIPS/MipsJit.cpp +++ b/Core/MIPS/MIPS/MipsJit.cpp @@ -66,21 +66,6 @@ void MipsJit::DoState(PointerWrap &p) } } -// This is here so the savestate matches between jit and non-jit. -void MipsJit::DoDummyState(PointerWrap &p) -{ - auto s = p.Section("Jit", 1, 2); - if (!s) - return; - - bool dummy = false; - p.Do(dummy); - if (s >= 2) { - dummy = true; - p.Do(dummy); - } -} - void MipsJit::FlushAll() { //gpr.FlushAll(); diff --git a/Core/MIPS/MIPS/MipsJit.h b/Core/MIPS/MIPS/MipsJit.h index ad158f5de0..c0ff28582d 100644 --- a/Core/MIPS/MIPS/MipsJit.h +++ b/Core/MIPS/MIPS/MipsJit.h @@ -38,7 +38,6 @@ public: MipsJit(MIPSState *mips); void DoState(PointerWrap &p) override; - void DoDummyState(PointerWrap &p) override; // Compiled ops should ignore delay slots // the compiler will take care of them by itself diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 6a6b196aec..6e2ceae75e 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -142,21 +142,6 @@ void Jit::DoState(PointerWrap &p) { CBreakPoints::SetSkipFirst(0); } -// This is here so the savestate matches between jit and non-jit. -void Jit::DoDummyState(PointerWrap &p) { - auto s = p.Section("Jit", 1, 2); - if (!s) - return; - - bool dummy = false; - p.Do(dummy); - if (s >= 2) { - dummy = true; - p.Do(dummy); - } -} - - void Jit::GetStateAndFlushAll(RegCacheState &state) { gpr.GetState(state.gpr); fpr.GetState(state.fpr); diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index de8ef4d8f1..2a35212140 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -52,7 +52,6 @@ public: const JitOptions &GetJitOptions() { return jo; } void DoState(PointerWrap &p) override; - void DoDummyState(PointerWrap &p) override; // Compiled ops should ignore delay slots // the compiler will take care of them by itself diff --git a/Core/Util/AudioFormat.cpp b/Core/Util/AudioFormat.cpp index f588a2b80c..2424b405d5 100644 --- a/Core/Util/AudioFormat.cpp +++ b/Core/Util/AudioFormat.cpp @@ -26,7 +26,7 @@ void AdjustVolumeBlockStandard(s16 *out, s16 *in, size_t size, int leftVol, int rightVol) { #ifdef _M_SSE - if (leftVol <= 0x7fff && rightVol <= 0x7fff) { + if (leftVol <= 0x7fff && -leftVol <= 0x8000 && rightVol <= 0x7fff && -rightVol <= 0x8000) { __m128i volume = _mm_set_epi16(leftVol, rightVol, leftVol, rightVol, leftVol, rightVol, leftVol, rightVol); while (size >= 16) { __m128i indata1 = _mm_loadu_si128((__m128i *)in); @@ -37,25 +37,46 @@ void AdjustVolumeBlockStandard(s16 *out, s16 *in, size_t size, int leftVol, int out += 16; size -= 16; } - } else { - // We have to shift inside the loop to avoid the signed multiply issue. - leftVol >>= 1; - rightVol >>= 1; - __m128i volume = _mm_set_epi16(leftVol, rightVol, leftVol, rightVol, leftVol, rightVol, leftVol, rightVol); + } +#if 0 + // This path wraps instead of clamps in _mm_slli_epi16. Needs fixing. + else { + // We have to shift inside the loop to avoid the signed 16-bit multiply issue. + int leftShift = 0; + int leftVol16 = leftVol; + while (leftVol16 > 0x7fff || -leftVol16 > 0x8000) { + ++leftShift; + leftVol16 >>= 1; + } + int rightShift = 0; + int rightVol16 = rightVol; + while (rightVol16 > 0x7fff || -rightVol16 > 0x8000) { + ++rightShift; + rightVol16 >>= 1; + } + __m128i volume = _mm_set_epi16(leftVol16, rightVol16, leftVol16, rightVol16, leftVol16, rightVol16, leftVol16, rightVol16); while (size >= 16) { __m128i indata1 = _mm_loadu_si128((__m128i *)in); __m128i indata2 = _mm_loadu_si128((__m128i *)(in + 8)); - _mm_storeu_si128((__m128i *)out, _mm_slli_epi16(_mm_mulhi_epi16(indata1, volume), 1)); - _mm_storeu_si128((__m128i *)(out + 8), _mm_slli_epi16(_mm_mulhi_epi16(indata2, volume), 1)); + _mm_storeu_si128((__m128i *)out, _mm_slli_epi16(_mm_mulhi_epi16(indata1, volume), leftShift)); + _mm_storeu_si128((__m128i *)(out + 8), _mm_slli_epi16(_mm_mulhi_epi16(indata2, volume), rightShift)); in += 16; out += 16; size -= 16; } } #endif - for (size_t i = 0; i < size; i += 2) { - out[i] = ApplySampleVolume(in[i], leftVol); - out[i + 1] = ApplySampleVolume(in[i + 1], rightVol); +#endif + if (leftVol <= 0x7fff && -leftVol <= 0x8000 && rightVol <= 0x7fff && -rightVol <= 0x8000) { + for (size_t i = 0; i < size; i += 2) { + out[i] = ApplySampleVolume(in[i], leftVol); + out[i + 1] = ApplySampleVolume(in[i + 1], rightVol); + } + } else { + for (size_t i = 0; i < size; i += 2) { + out[i] = ApplySampleVolume20Bit(in[i], leftVol); + out[i + 1] = ApplySampleVolume20Bit(in[i + 1], rightVol); + } } } diff --git a/Core/Util/AudioFormat.h b/Core/Util/AudioFormat.h index 841b8e634a..7fc20eaf83 100644 --- a/Core/Util/AudioFormat.h +++ b/Core/Util/AudioFormat.h @@ -59,6 +59,12 @@ static inline s16 ApplySampleVolume(s16 sample, int vol) { #endif } +// We sacrifice a little volume precision to fit in 32 bits, for speed. +// Probably not worth it to make a special path for 64-bit CPUs. +static inline s16 ApplySampleVolume20Bit(s16 sample, int vol20) { + return clamp_s16((sample * (vol20 >> 4)) >> 12); +} + void SetupAudioFormats(); void AdjustVolumeBlockStandard(s16 *out, s16 *in, size_t size, int leftVol, int rightVol); void ConvertS16ToF32(float *ou, const s16 *in, size_t size); diff --git a/Core/Util/AudioFormatNEON.cpp b/Core/Util/AudioFormatNEON.cpp index 4cb3dd0d95..ec73e93d2b 100644 --- a/Core/Util/AudioFormatNEON.cpp +++ b/Core/Util/AudioFormatNEON.cpp @@ -55,9 +55,16 @@ void AdjustVolumeBlockNEON(s16 *out, s16 *in, size_t size, int leftVol, int righ size -= 16; } - for (size_t i = 0; i < size; i += 2) { - out[i] = ApplySampleVolume(in[i], leftVol); - out[i + 1] = ApplySampleVolume(in[i + 1], rightVol); + if (leftVol <= 0x7fff && -leftVol <= 0x8000 && rightVol <= 0x7fff && -rightVol <= 0x8000) { + for (size_t i = 0; i < size; i += 2) { + out[i] = ApplySampleVolume(in[i], leftVol); + out[i + 1] = ApplySampleVolume(in[i + 1], rightVol); + } + } else { + for (size_t i = 0; i < size; i += 2) { + out[i] = ApplySampleVolume20Bit(in[i], leftVol); + out[i + 1] = ApplySampleVolume20Bit(in[i + 1], rightVol); + } } } diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index bac844ec90..9c5b9343bc 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -136,6 +136,8 @@ FramebufferManagerCommon::~FramebufferManagerCommon() { DestroyFramebuf(vfb); } bvfbs_.clear(); + + SetNumExtraFBOs(0); } void FramebufferManagerCommon::Init() { @@ -201,7 +203,7 @@ bool FramebufferManagerCommon::ShouldDownloadFramebuffer(const VirtualFramebuffe void FramebufferManagerCommon::SetNumExtraFBOs(int num) { for (size_t i = 0; i < extraFBOs_.size(); i++) { - extraFBOs_[i]->Release(); + extraFBOs_[i]->ReleaseAssertLast(); } extraFBOs_.clear(); for (int i = 0; i < num; i++) { @@ -210,7 +212,8 @@ void FramebufferManagerCommon::SetNumExtraFBOs(int num) { extraFBOs_.push_back(fbo); } currentRenderVfb_ = 0; - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + if (num != 0) + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } // Heuristics to figure out the size of FBO to create. diff --git a/GPU/Common/VertexDecoderArm64.cpp b/GPU/Common/VertexDecoderArm64.cpp index 37e3cb9bdd..0e31dd80c2 100644 --- a/GPU/Common/VertexDecoderArm64.cpp +++ b/GPU/Common/VertexDecoderArm64.cpp @@ -698,7 +698,12 @@ void VertexDecoderJitCache::Jit_PosS16Through() { } void VertexDecoderJitCache::Jit_NormalS8() { - LDRH(INDEX_UNSIGNED, tempReg1, srcReg, dec_->nrmoff); + // nrmoff can be odd in case of byte-only vertices! odd unsigned offsets are not allowed for LDRH. + // Switching to LDRB. + // Only seen this in a crash log. + LDRB(INDEX_UNSIGNED, tempReg1, srcReg, dec_->nrmoff); + LDRB(INDEX_UNSIGNED, tempReg3, srcReg, dec_->nrmoff + 1); + ORR(tempReg1, tempReg1, tempReg3, ArithOption(tempReg3, ST_LSL, 8)); LDRB(INDEX_UNSIGNED, tempReg3, srcReg, dec_->nrmoff + 2); ORR(tempReg1, tempReg1, tempReg3, ArithOption(tempReg3, ST_LSL, 16)); STR(INDEX_UNSIGNED, tempReg1, dstReg, dec_->decFmt.nrmoff); diff --git a/GPU/Directx9/ShaderManagerDX9.cpp b/GPU/Directx9/ShaderManagerDX9.cpp index 3671ce4536..a22d17b2a3 100644 --- a/GPU/Directx9/ShaderManagerDX9.cpp +++ b/GPU/Directx9/ShaderManagerDX9.cpp @@ -148,9 +148,13 @@ void ShaderManagerDX9::PSSetColorUniform3(int creg, u32 color) { } void ShaderManagerDX9::PSSetColorUniform3Alpha255(int creg, u32 color, u8 alpha) { - float f[4]; - Uint8x3ToFloat4_AlphaUint8(f, color, alpha); - device_->SetPixelShaderConstantF(creg, f, 1); + const float col[4] = { + (float)((color & 0xFF)), + (float)((color & 0xFF00) >> 8), + (float)((color & 0xFF0000) >> 16), + (float)alpha, + }; + device_->SetPixelShaderConstantF(creg, col, 1); } void ShaderManagerDX9::PSSetFloat(int creg, float value) { diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 50d7e7f3cf..d6aa6a508a 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -250,7 +250,6 @@ FramebufferManagerGLES::~FramebufferManagerGLES() { if (stencilUploadProgram_) { glsl_destroy(stencilUploadProgram_); } - SetNumExtraFBOs(0); for (auto it = tempFBOs_.begin(), end = tempFBOs_.end(); it != end; ++it) { it->second.fbo->Release(); diff --git a/GPU/Vulkan/DepalettizeShaderVulkan.cpp b/GPU/Vulkan/DepalettizeShaderVulkan.cpp index a080b27416..b7dec11221 100644 --- a/GPU/Vulkan/DepalettizeShaderVulkan.cpp +++ b/GPU/Vulkan/DepalettizeShaderVulkan.cpp @@ -69,7 +69,8 @@ DepalShaderCacheVulkan::~DepalShaderCacheVulkan() { void DepalShaderCacheVulkan::DeviceLost() { Clear(); - vulkan_->Delete().QueueDeleteShaderModule(vshader_); + if (vshader_) + vulkan_->Delete().QueueDeleteShaderModule(vshader_); draw_ = nullptr; vulkan_ = nullptr; } diff --git a/GPU/Vulkan/DrawEngineVulkan.cpp b/GPU/Vulkan/DrawEngineVulkan.cpp index 82908e0048..73fb6afe4e 100644 --- a/GPU/Vulkan/DrawEngineVulkan.cpp +++ b/GPU/Vulkan/DrawEngineVulkan.cpp @@ -247,9 +247,11 @@ void DrawEngineVulkan::DestroyDeviceObjects() { delete nullTexture_; nullTexture_ = nullptr; } - vertexCache_->Destroy(vulkan_); - delete vertexCache_; - vertexCache_ = nullptr; + if (vertexCache_) { + vertexCache_->Destroy(vulkan_); + delete vertexCache_; + vertexCache_ = nullptr; + } // Need to clear this to get rid of all remaining references to the dead buffers. vai_.Iterate([](uint32_t hash, VertexArrayInfoVulkan *vai) { delete vai; diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 1b27f135b4..dbf3e1e130 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -301,8 +301,7 @@ static VulkanPipeline *CreateVulkanPipeline(VkDevice device, VkPipelineCache pip VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) { VulkanPipelineKey key{}; - if (!renderPass) - Crash(); + _assert_msg_(G3D, renderPass, "Can't create a pipeline with a null renderpass"); key.raster = rasterKey; key.renderPass = renderPass; diff --git a/README.md b/README.md index a4586435c7..6fa85a6208 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,23 @@ For the latest source code, see [our github page](https://github.com/hrydgard/pp For build instructions and other development tutorials, see the [wiki](https://github.com/hrydgard/ppsspp/wiki). + +What's new in 1.5.0 +------------------- +* Full Vulkan support, also for Android now. Very fast on supported devices. (#10033, #10049) +* Smarter graphics state management, reduced CPU consumption on all backends (#9899) +* Android: Support for Arabic and other scripts we couldn't support before +* Fix Android widgets, screen scaling (#10145) +* Fixes to video dumping +* Geometry problems fixed in Medal of Honor +* Implement immediate draws, fixing Thrillville (#7459) +* Software rendering improvements, speed and accuracy +* Hardware tesselation of PSP Beziers and Splines (used by a few games) +* Partial sceUsbGps and sceUsbCam support (Android) +* Android "Sustained performance mode" to avoid thermal throttling (#9901) +* Linux controller mapping fixes (#9997) +* Assorted bugfixes and compatibility improvements + What's new in 1.4.2 ------------------- * Fixed longstanding bug causing games to crash on ARM64 diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 70b8a35145..de30cc728f 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -65,9 +65,9 @@ void CwCheatScreen::CreateCodeList() { bEnableCheat.clear(); formattedList_.clear(); for (size_t i = 0; i < cheatList.size(); i++) { - if (cheatList[i].substr(0, 2) == "_C") { + if (cheatList[i][0] == '_' && cheatList[i][1] == 'C') { formattedList_.push_back(cheatList[i].substr(4)); - if (cheatList[i].substr(2, 1) == "0") { + if (cheatList[i][2] == '0') { bEnableCheat.push_back(false); } else { bEnableCheat.push_back(true); @@ -136,11 +136,11 @@ UI::EventReturn CwCheatScreen::OnEnableAll(UI::EventParams ¶ms) { enableAll = !enableAll; File::OpenCPPFile(fs, activeCheatFile, std::ios::out); for (int j = 0; j < (int)cheatList.size(); j++) { - if (cheatList[j].substr(0, 2) == "_C") { - if (cheatList[j].substr(2, 1) == "0" && enableAll) { - cheatList[j].replace(2, 1, "1"); - } else if (cheatList[j].substr(2, 1) != "0" && !enableAll) { - cheatList[j].replace(2, 1, "0"); + if (cheatList[j][0] == '_' && cheatList[j][1] == 'C') { + if (cheatList[j][2] == '0' && enableAll) { + cheatList[j][2] = '1'; + } else if (cheatList[j][2] != '0' && !enableAll) { + cheatList[j][2] = '0'; } } } @@ -181,6 +181,8 @@ UI::EventReturn CwCheatScreen::OnEditCheatFile(UI::EventParams ¶ms) { void CwCheatScreen::ParseCheatDB(std::string line, std::vector title, std::vector newList, std::string cheatFile) { bool finished = false, skip = false; + std::string gameID = StringFromFormat("_S %s-%s", gameTitle.substr(0, 4).c_str(), gameTitle.substr(4).c_str()); + std::fstream fs; File::OpenCPPFile(fs, cheatFile, std::ios::in); @@ -190,7 +192,7 @@ void CwCheatScreen::ParseCheatDB(std::string line, std::vector titl while (fs.good()) { getline(fs, line); // get line from file - if (line == "_S " + gameTitle.substr(0, 4) + "-" + gameTitle.substr(4)) { + if (line == gameID) { title.push_back(line); getline(fs, line); title.push_back(line); @@ -198,7 +200,7 @@ void CwCheatScreen::ParseCheatDB(std::string line, std::vector titl if (finished == false) { getline(fs, line); } - if (line.substr(0, 2) == "_C") { + if (line[0] == '_' && line[1] == 'C') { //Test if cheat already exists in cheatList for (size_t j = 0; j < formattedList_.size(); j++) { if (line.substr(4) == formattedList_[j]) { @@ -212,14 +214,14 @@ void CwCheatScreen::ParseCheatDB(std::string line, std::vector titl do { newList.push_back(line); getline(fs, line); - } while (line.substr(0, 2) == "_L"); + } while (line[0] == '_' && line[1] == 'L'); finished = true; } else { continue; } loop:; - } while (line.substr(0, 2) != "_S"); + } while (fs.good() && line[0] != '_' && line[1] != 'S'); finished = true; } if (finished == true) @@ -234,7 +236,7 @@ void CwCheatScreen::ParseCheatDB(std::string line, std::vector titl File::OpenCPPFile(fs, activeCheatFile, std::ios::out | std::ios::app); auto it = title.begin(); - if (title2.substr(0, 2) != "_S" && it != title.end() && (++it) != title.end()) { + if (title2[0] != '_' && title2[1] != 'S' && it != title.end() && (++it) != title.end()) { fs << title[0] << "\n" << title[1]; } diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 272201c143..b3b6c56151 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -534,6 +534,7 @@ void CreditsScreen::CreateViews() { } root_->Add(new Button(cr->T("PPSSPP Forums"), new AnchorLayoutParams(260, 64, 10, NONE, NONE, 84, false)))->OnClick.Handle(this, &CreditsScreen::OnForums); root_->Add(new Button("www.ppsspp.org", new AnchorLayoutParams(260, 64, 10, NONE, NONE, 158, false)))->OnClick.Handle(this, &CreditsScreen::OnPPSSPPOrg); + root_->Add(new Button(cr->T("Privacy Policy"), new AnchorLayoutParams(260, 64, 10, NONE, NONE, 232, false)))->OnClick.Handle(this, &CreditsScreen::OnPrivacy); #ifdef __ANDROID__ root_->Add(new Button(cr->T("Share PPSSPP"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, 84, false)))->OnClick.Handle(this, &CreditsScreen::OnShare); root_->Add(new Button(cr->T("Twitter @PPSSPP_emu"), new AnchorLayoutParams(260, 64, NONE, NONE, 10, 154, false)))->OnClick.Handle(this, &CreditsScreen::OnTwitter); @@ -568,6 +569,11 @@ UI::EventReturn CreditsScreen::OnPPSSPPOrg(UI::EventParams &e) { return UI::EVENT_DONE; } +UI::EventReturn CreditsScreen::OnPrivacy(UI::EventParams &e) { + LaunchBrowser("https://www.ppsspp.org/privacy.html"); + return UI::EVENT_DONE; +} + UI::EventReturn CreditsScreen::OnForums(UI::EventParams &e) { LaunchBrowser("https://forums.ppsspp.org"); return UI::EVENT_DONE; diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index d189121d7d..9e412d932b 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -142,8 +142,8 @@ private: UI::EventReturn OnSupport(UI::EventParams &e); UI::EventReturn OnPPSSPPOrg(UI::EventParams &e); + UI::EventReturn OnPrivacy(UI::EventParams &e); UI::EventReturn OnForums(UI::EventParams &e); - UI::EventReturn OnChineseForum(UI::EventParams &e); UI::EventReturn OnShare(UI::EventParams &e); UI::EventReturn OnTwitter(UI::EventParams &e); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 7667bcab39..a14699de59 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -613,12 +613,14 @@ static void UIThemeInit() { void RenderOverlays(UIContext *dc, void *userdata); -void NativeInitGraphics(GraphicsContext *graphicsContext) { +bool NativeInitGraphics(GraphicsContext *graphicsContext) { ILOG("NativeInitGraphics"); + _assert_msg_(G3D, graphicsContext, "No graphics context!"); using namespace Draw; Core_SetGraphicsContext(graphicsContext); g_draw = graphicsContext->GetDrawContext(); + _assert_msg_(G3D, g_draw, "No draw context available!"); ui_draw2d.SetAtlas(&ui_atlas); ui_draw2d_front.SetAtlas(&ui_atlas); @@ -694,6 +696,7 @@ void NativeInitGraphics(GraphicsContext *graphicsContext) { g_graphicsInited = true; ILOG("NativeInitGraphics completed"); + return true; } void NativeShutdownGraphics() { diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 8ef8b5c6a5..e145a623e2 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="15000000" + android:versionName="1.5.0.0"> color.layout; @@ -490,6 +491,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; break; + case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + break; default: Crash(); } @@ -517,8 +521,8 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step // Now, if the image needs transitioning, let's transition. // The backbuffer does not, that's handled by VulkanContext. if (step.render.framebuffer->color.layout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { - VkAccessFlags srcAccessMask; - VkPipelineStageFlags srcStage; + VkAccessFlags srcAccessMask = 0; + VkPipelineStageFlags srcStage = 0; switch (fb->color.layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: srcAccessMask = VK_ACCESS_SHADER_READ_BIT; @@ -533,7 +537,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; break; default: - Crash(); + _dbg_assert_msg_(G3D, false, "PerformBindRT: Unexpected color layout %d", (int)fb->color.layout); break; } @@ -545,8 +549,8 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step fb->color.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } if (fb->depth.layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { - VkAccessFlags srcAccessMask; - VkPipelineStageFlags srcStage; + VkAccessFlags srcAccessMask = 0; + VkPipelineStageFlags srcStage = 0; switch (fb->depth.layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: @@ -562,7 +566,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; break; default: - Crash(); + _dbg_assert_msg_(G3D, false, "PerformBindRT: Unexpected depth layout %d", (int)fb->color.layout); break; } @@ -791,7 +795,8 @@ void VulkanQueueRunner::SetupTransitionToTransferSrc(VKRImage &img, VkImageMemor stage |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; default: - Crash(); + _dbg_assert_msg_(G3D, false, "Transition from this layout to transfer src not supported (%d)", (int)img.layout); + break; } barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; @@ -826,7 +831,8 @@ void VulkanQueueRunner::SetupTransitionToTransferDst(VKRImage &img, VkImageMemor stage |= VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; break; default: - Crash(); + _dbg_assert_msg_(G3D, false, "Transition from this layout to transfer dst not supported (%d)", (int)img.layout); + break; } barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; @@ -869,7 +875,8 @@ void VulkanQueueRunner::PerformReadback(const VKRStep &step, VkCommandBuffer cmd srcImage = &step.readback.src->depth; } else { - assert(false); + _dbg_assert_msg_(G3D, false, "No image aspect to readback?"); + return; } VkImageMemoryBarrier barrier{ VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 4ff7156656..ea5e98367a 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -690,6 +690,12 @@ VkImageView VulkanRenderManager::BindFramebufferAsTexture(VKRFramebuffer *fb, in } } + if (!curRenderStep_->preTransitions.empty() && + curRenderStep_->preTransitions.back().fb == fb && + curRenderStep_->preTransitions.back().targetLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + // We're done. + return fb->color.imageView; + } curRenderStep_->preTransitions.push_back({ fb, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }); return fb->color.imageView; } diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index b8257cac86..c5e13130af 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -51,13 +51,20 @@ public: } ~VKRFramebuffer() { - vulkan_->Delete().QueueDeleteImage(color.image); - vulkan_->Delete().QueueDeleteImage(depth.image); - vulkan_->Delete().QueueDeleteImageView(color.imageView); - vulkan_->Delete().QueueDeleteImageView(depth.imageView); - vulkan_->Delete().QueueDeleteDeviceMemory(color.memory); - vulkan_->Delete().QueueDeleteDeviceMemory(depth.memory); - vulkan_->Delete().QueueDeleteFramebuffer(framebuf); + if (color.image) + vulkan_->Delete().QueueDeleteImage(color.image); + if (depth.image) + vulkan_->Delete().QueueDeleteImage(depth.image); + if (color.imageView) + vulkan_->Delete().QueueDeleteImageView(color.imageView); + if (depth.imageView) + vulkan_->Delete().QueueDeleteImageView(depth.imageView); + if (color.memory) + vulkan_->Delete().QueueDeleteDeviceMemory(color.memory); + if (depth.memory) + vulkan_->Delete().QueueDeleteDeviceMemory(depth.memory); + if (framebuf) + vulkan_->Delete().QueueDeleteFramebuffer(framebuf); } int numShadows = 1; // TODO: Support this. diff --git a/ext/native/thin3d/thin3d.cpp b/ext/native/thin3d/thin3d.cpp index add987548d..0cde248021 100644 --- a/ext/native/thin3d/thin3d.cpp +++ b/ext/native/thin3d/thin3d.cpp @@ -4,6 +4,7 @@ #include "base/logging.h" #include "thin3d/thin3d.h" +#include "Common/Log.h" #include "Common/ColorConv.h" namespace Draw { @@ -75,11 +76,26 @@ bool RefCountedObject::Release() { } } else { + _dbg_assert_msg_(G3D, false, "Refcount (%d) invalid for object %p - corrupt?", refcount_, this); + } + return false; +} + +bool RefCountedObject::ReleaseAssertLast() { + _dbg_assert_msg_(G3D, refcount_ == 1, "RefCountedObject: Expected to be the last reference, but isn't!"); + if (refcount_ > 0 && refcount_ < 10000) { + refcount_--; + if (refcount_ == 0) { + delete this; + return true; + } + } else { ELOG("Refcount (%d) invalid for object %p - corrupt?", refcount_, this); } return false; } + // ================================== PIXEL/FRAGMENT SHADERS // The Vulkan ones can be re-used with modern GL later if desired, as they're just GLSL. diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 16f5493801..916cc97f7e 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -346,6 +346,7 @@ public: void AddRef() { refcount_++; } bool Release(); + bool ReleaseAssertLast(); private: int refcount_; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index 0044b3f234..8e38776b6d 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -1298,7 +1298,6 @@ uint32_t VKContext::GetDataFormatSupport(DataFormat fmt) const { // use this frame's init command buffer. class VKFramebuffer : public Framebuffer { public: - // Inherits ownership so no AddRef. VKFramebuffer(VKRFramebuffer *fb) : buf_(fb) {} ~VKFramebuffer() { buf_->vulkan_->Delete().QueueCallback([](void *fb) { diff --git a/ext/native/ui/ui_screen.h b/ext/native/ui/ui_screen.h index cb894999f5..fee88ce255 100644 --- a/ext/native/ui/ui_screen.h +++ b/ext/native/ui/ui_screen.h @@ -264,7 +264,7 @@ private: const char *category_; ScreenManager *screenManager_; std::string valueText_; - bool restoreFocus_; + bool restoreFocus_ = false; std::set hidden_; }; diff --git a/ext/native/ui/viewgroup.cpp b/ext/native/ui/viewgroup.cpp index 50dabcead2..5c93a4c4c3 100644 --- a/ext/native/ui/viewgroup.cpp +++ b/ext/native/ui/viewgroup.cpp @@ -1314,8 +1314,10 @@ EventReturn ListView::OnItemCallback(int num, EventParams &e) { View *focused = GetFocusedView(); OnChoice.Trigger(ev); CreateAllItems(); - if (focused) - SetFocusedView(e.v); + // TODO: At this point, focused may no longer exist, depending on what OnChoice.Trigger does. + // Disable the refocus feature for now. + // if (focused) + // SetFocusedView(e.v); return EVENT_DONE; }