mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 07:50:28 +02:00
The core and the headless tools no longer use mutexes, condition variables, spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through libretro-common's retro_atomic and rthreads plus a few small pieces on top of them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and ReaderGate. Every wait blocks until it's woken; nothing polls on a timer. State that only one thread touches just loses its lock. The PSP filesystem, sceIo and the savedata dialog now belong to the emulation thread: async reads and writes run when they're queued and complete later in emulated time, and the IO and savedata threads are gone. The friend finder only talks to the adhoc server and hands what it gets to the emulation thread. GPU init always runs on the thread that starts the game. The Vulkan render and compile queues, pipeline and render pass caches, delete list and SPIR-V cache, the GL render queue, the software renderer's jit caches, the file loader caches, the web debugger, UPnP, input, savestates, logging and the GE debugger all hand off through queues and atomics. The IR jit's spinning profiler thread goes. A CPU thread with nothing to run now sleeps until work is queued instead of spinning. Headless with --debugger waits for a client while stopped, and ends the run once every client that connected has left. Web server shutdown keeps servicing the CPU queue until the server has finished.
250 lines
6.5 KiB
C++
250 lines
6.5 KiB
C++
// Copyright (c) 2016- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#pragma once
|
|
|
|
#include <retro_atomic.h>
|
|
#include <string>
|
|
|
|
#include "Common/File/VFS/VFS.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Core/ConfigValues.h"
|
|
#include "GPU/Common/ImageCommon.h"
|
|
|
|
class TextureReplacer;
|
|
class LimitedWaitable;
|
|
|
|
// For forward compatibility, we specify the hash.
|
|
enum class ReplacedTextureHash {
|
|
QUICK,
|
|
XXH32,
|
|
XXH64,
|
|
};
|
|
|
|
enum class ReplacedImageType {
|
|
PNG,
|
|
ZIM,
|
|
DDS,
|
|
BASIS, // TODO: Might not even do this, KTX2 is a better container.
|
|
KTX2,
|
|
INVALID,
|
|
};
|
|
|
|
static const int MAX_REPLACEMENT_MIP_LEVELS = 12; // 12 should be plenty, 8 is the max mip levels supported by the PSP.
|
|
|
|
enum class ReplacementState : uint32_t {
|
|
UNLOADED,
|
|
PENDING,
|
|
NOT_FOUND, // Also used on error loading the images.
|
|
ACTIVE,
|
|
CANCEL_INIT,
|
|
COUNT, // Not a valid state
|
|
};
|
|
|
|
const char *StateString(ReplacementState state);
|
|
|
|
struct GPUFormatSupport {
|
|
bool bc123;
|
|
bool astc;
|
|
bool bc7;
|
|
bool etc2;
|
|
};
|
|
|
|
struct ReplacementCacheKey {
|
|
u64 cachekey; // Split into two u32?
|
|
u32 hash;
|
|
|
|
ReplacementCacheKey() : cachekey(0), hash(0) {}
|
|
ReplacementCacheKey(u64 ckey, u32 h) : cachekey(ckey), hash(h) {}
|
|
|
|
bool operator ==(const ReplacementCacheKey &k) const {
|
|
return k.cachekey == cachekey && k.hash == hash;
|
|
}
|
|
|
|
bool operator <(const ReplacementCacheKey &k) const {
|
|
if (k.cachekey == cachekey) {
|
|
return k.hash < hash;
|
|
}
|
|
return k.cachekey < cachekey;
|
|
}
|
|
|
|
// Access the parts.
|
|
u64 CacheKey() const { return cachekey; }
|
|
u32 ClutHash() const { return (u32)(cachekey & 0xFFFFFFFFULL); }
|
|
u32 Address() const { return (u32)(cachekey >> 32); }
|
|
u32 ContentsHash() const { return hash; }
|
|
|
|
void ZeroAddress() {
|
|
cachekey &= 0xFFFFFFFFULL;
|
|
}
|
|
void ZeroClutHash() {
|
|
cachekey &= 0xFFFFFFFF00000000ULL;
|
|
}
|
|
void ZeroContentsHash() {
|
|
hash = 0;
|
|
}
|
|
};
|
|
|
|
struct ReplacementDesc {
|
|
int newW;
|
|
int newH;
|
|
ReplacementCacheKey cacheKey;
|
|
int w;
|
|
int h;
|
|
TextureFiltering forceFiltering;
|
|
std::string hashfiles;
|
|
Path basePath;
|
|
std::vector<std::string> filenames;
|
|
std::string logId;
|
|
GPUFormatSupport formatSupport;
|
|
};
|
|
|
|
class ReplacedTexture;
|
|
|
|
// These aren't actually all replaced, they can also represent a placeholder for a not-found
|
|
// replacement (texture == nullptr).
|
|
struct ReplacedTextureRef {
|
|
ReplacedTexture *texture; // shortcut
|
|
std::string hashfiles; // key into levelCache_
|
|
};
|
|
|
|
// Metadata about a given texture level.
|
|
struct ReplacedTextureLevel {
|
|
// Data dimensions
|
|
int w = 0;
|
|
int h = 0;
|
|
// PSP texture dimensions
|
|
int fullW = 0;
|
|
int fullH = 0;
|
|
|
|
int fullDataSize = 0;
|
|
|
|
// To be able to reload, we need to be able to reopen, unfortunate we can't use zip_file_t.
|
|
// TODO: This really belongs on the level in the cache, not in the individual ReplacedTextureLevel objects.
|
|
VFSFileReference *fileRef = nullptr;
|
|
};
|
|
|
|
class ReplacedTexture {
|
|
public:
|
|
ReplacedTexture(const ReplacedTexture &) = delete;
|
|
ReplacedTexture &operator=(const ReplacedTexture &) = delete;
|
|
ReplacedTexture(VFSBackend *vfs, const ReplacementDesc &desc);
|
|
~ReplacedTexture();
|
|
|
|
// The loader task publishes the levels with its final state (release), and the
|
|
// emu thread only reads them once it sees that state (acquire).
|
|
inline ReplacementState State() const {
|
|
return (ReplacementState)retro_atomic_load_acquire_int(const_cast<retro_atomic_int_t *>(&state_));
|
|
}
|
|
|
|
void SetState(ReplacementState state) {
|
|
_dbg_assert_(state != State());
|
|
retro_atomic_store_release_int(&state_, (int)state);
|
|
}
|
|
|
|
void GetSize(int level, int *w, int *h) const {
|
|
_dbg_assert_(State() == ReplacementState::ACTIVE);
|
|
_dbg_assert_((size_t)level < levels_.size());
|
|
*w = levels_[level].fullW;
|
|
*h = levels_[level].fullH;
|
|
}
|
|
|
|
int GetLevelDataSizeAfterCopy(int level) const {
|
|
// Includes padding etc.
|
|
return levels_[level].fullDataSize;
|
|
}
|
|
|
|
size_t GetTotalDataSize() const {
|
|
if (State() != ReplacementState::ACTIVE) {
|
|
return 0;
|
|
}
|
|
size_t sz = 0;
|
|
for (auto &data : data_) {
|
|
sz += data.size();
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
bool ForceFiltering(TextureFiltering *forceFiltering) const {
|
|
if (desc_.forceFiltering != (TextureFiltering)0) {
|
|
*forceFiltering = desc_.forceFiltering;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int NumLevels() const {
|
|
_dbg_assert_(State() == ReplacementState::ACTIVE);
|
|
return (int)levels_.size();
|
|
}
|
|
|
|
Draw::DataFormat Format() const {
|
|
_dbg_assert_(State() == ReplacementState::ACTIVE);
|
|
return fmt;
|
|
}
|
|
|
|
const ReplacementDesc &Desc() const {
|
|
return desc_;
|
|
}
|
|
|
|
TextureAlpha AlphaStatus() const {
|
|
return alphaStatus_;
|
|
}
|
|
|
|
// wait: block until a pending load is done, rather than checking on it.
|
|
bool Poll(bool wait);
|
|
bool CopyLevelTo(int level, uint8_t *out, size_t outDataSize, int rowPitch);
|
|
|
|
std::string logId_;
|
|
|
|
private:
|
|
enum class LoadLevelResult {
|
|
LOAD_ERROR = 0,
|
|
CONTINUE = 1,
|
|
DONE = 2,
|
|
};
|
|
|
|
void Prepare(VFSBackend *vfs);
|
|
// Waits for any load task, then drops all data and file references, and detaches from the VFS.
|
|
// The texture reloads from scratch once it gets a VFS again. Must run before the VFS is deleted.
|
|
void Unload();
|
|
void ReleaseLevels();
|
|
LoadLevelResult LoadLevelData(VFSFileReference *fileRef, const std::string &filename, int level, Draw::DataFormat *pixelFormat);
|
|
void PurgeIfNotUsedSinceTime(double t);
|
|
|
|
std::vector<std::vector<uint8_t>> data_;
|
|
std::vector<ReplacedTextureLevel> levels_;
|
|
// Image format of mip level 0; mixing formats across levels is not
|
|
// allowed (container formats like KTX2/DDS manage their own mip chain).
|
|
ReplacedImageType firstImageType_ = ReplacedImageType::INVALID;
|
|
|
|
double lastUsed_ = 0.0;
|
|
LimitedWaitable *threadWaitable_ = nullptr;
|
|
Draw::DataFormat fmt = Draw::DataFormat::UNDEFINED;
|
|
TextureAlpha alphaStatus_ = TextureAlpha::Any;
|
|
double lastUsed = 0.0;
|
|
|
|
retro_atomic_int_t state_{ (int)ReplacementState::UNLOADED };
|
|
|
|
VFSBackend *vfs_ = nullptr;
|
|
ReplacementDesc desc_;
|
|
|
|
friend class TextureReplacer;
|
|
friend class ReplacedTextureTask;
|
|
};
|