mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 02:39:05 +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.
329 lines
9.3 KiB
C++
329 lines
9.3 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
|
|
#include "Common/Thread/Promise.h"
|
|
#include "Common/Data/Collections/Hashmaps.h"
|
|
#include "Common/Data/Collections/FastVec.h"
|
|
#include "Common/GPU/Vulkan/VulkanContext.h"
|
|
#include "Common/GPU/Vulkan/VulkanBarrier.h"
|
|
#include "Common/GPU/Vulkan/VulkanFrameData.h"
|
|
#include "Common/GPU/Vulkan/VulkanFramebuffer.h"
|
|
#include "Common/Data/Convert/SmallDataConvert.h"
|
|
#include "Common/Data/Collections/TinySet.h"
|
|
#include "Common/GPU/DataFormat.h"
|
|
|
|
class VKRFramebuffer;
|
|
struct VKRGraphicsPipeline;
|
|
struct VKRComputePipeline;
|
|
struct VKRImage;
|
|
struct VKRPipelineLayout;
|
|
struct FrameData;
|
|
|
|
enum {
|
|
QUEUE_HACK_MGS2_ACID = 1,
|
|
QUEUE_HACK_SONIC = 2,
|
|
QUEUE_HACK_RENDERPASS_MERGE = 8,
|
|
};
|
|
|
|
enum class VKRRenderCommand : uint8_t {
|
|
REMOVED,
|
|
BIND_GRAPHICS_PIPELINE, // async
|
|
STENCIL,
|
|
BLEND,
|
|
VIEWPORT,
|
|
SCISSOR,
|
|
CLEAR,
|
|
DRAW,
|
|
DRAW_INDEXED,
|
|
PUSH_CONSTANTS,
|
|
DEBUG_ANNOTATION,
|
|
NUM_RENDER_COMMANDS,
|
|
};
|
|
|
|
enum class PipelineFlags : u8 {
|
|
NONE = 0,
|
|
USES_BLEND_CONSTANT = (1 << 1),
|
|
USES_DEPTH_STENCIL = (1 << 2), // Reads or writes the depth or stencil buffers.
|
|
// Note: (1 << 3) is free, it used to be USES_GEOMETRY_SHADER.
|
|
USES_MULTIVIEW = (1 << 4), // Inherited from the render pass it was created with.
|
|
USES_DISCARD = (1 << 5),
|
|
USES_FLAT_SHADING = (1 << 6),
|
|
};
|
|
ENUM_CLASS_BITOPS(PipelineFlags);
|
|
|
|
struct VkRenderData {
|
|
VKRRenderCommand cmd;
|
|
union {
|
|
struct {
|
|
VKRGraphicsPipeline *pipeline;
|
|
VKRPipelineLayout *pipelineLayout;
|
|
} graphics_pipeline;
|
|
struct {
|
|
uint32_t descSetIndex;
|
|
int numUboOffsets;
|
|
uint32_t uboOffsets[3];
|
|
VkBuffer vbuffer;
|
|
VkDeviceSize voffset;
|
|
uint32_t count;
|
|
uint32_t offset;
|
|
} draw;
|
|
struct {
|
|
uint32_t descSetIndex;
|
|
uint32_t uboOffsets[3];
|
|
uint16_t numUboOffsets;
|
|
uint16_t instances;
|
|
VkBuffer vbuffer;
|
|
VkBuffer ibuffer;
|
|
uint32_t voffset;
|
|
uint32_t ioffset;
|
|
uint32_t count;
|
|
} drawIndexed;
|
|
struct {
|
|
uint32_t clearColor;
|
|
float clearZ;
|
|
int clearStencil;
|
|
int clearMask; // VK_IMAGE_ASPECT_COLOR_BIT etc
|
|
} clear;
|
|
struct {
|
|
VkViewport vp;
|
|
} viewport;
|
|
struct {
|
|
VkRect2D scissor;
|
|
} scissor;
|
|
struct {
|
|
uint8_t stencilWriteMask;
|
|
uint8_t stencilCompareMask;
|
|
uint8_t stencilRef;
|
|
} stencil;
|
|
struct {
|
|
uint32_t color;
|
|
} blendColor;
|
|
struct {
|
|
VkShaderStageFlags stages;
|
|
uint8_t offset;
|
|
uint8_t size;
|
|
uint8_t data[40]; // Should be enough for now.
|
|
} push;
|
|
struct {
|
|
const char *annotation;
|
|
} debugAnnotation;
|
|
struct {
|
|
int setIndex;
|
|
} bindDescSet;
|
|
};
|
|
};
|
|
|
|
enum class VKRStepType : uint8_t {
|
|
RENDER,
|
|
RENDER_SKIP,
|
|
COPY,
|
|
BLIT,
|
|
READBACK,
|
|
READBACK_IMAGE,
|
|
};
|
|
|
|
struct TransitionRequest {
|
|
VKRFramebuffer *fb;
|
|
VkImageAspectFlags aspect; // COLOR or DEPTH
|
|
VkImageLayout targetLayout;
|
|
|
|
bool operator == (const TransitionRequest &other) const {
|
|
return fb == other.fb && aspect == other.aspect && targetLayout == other.targetLayout;
|
|
}
|
|
};
|
|
|
|
class VKRRenderPass;
|
|
|
|
struct VKRStep {
|
|
VKRStep(VKRStepType _type) : stepType(_type) {}
|
|
~VKRStep() {}
|
|
|
|
VKRStepType stepType;
|
|
FastVec<VkRenderData> commands;
|
|
TinySet<TransitionRequest, 4> preTransitions;
|
|
TinySet<VKRFramebuffer *, 8> dependencies;
|
|
const char *tag;
|
|
union {
|
|
struct {
|
|
VKRFramebuffer *framebuffer;
|
|
VKRRenderPassLoadAction colorLoad;
|
|
VKRRenderPassLoadAction depthLoad;
|
|
VKRRenderPassLoadAction stencilLoad;
|
|
VKRRenderPassStoreAction colorStore;
|
|
VKRRenderPassStoreAction depthStore;
|
|
VKRRenderPassStoreAction stencilStore;
|
|
uint32_t clearColor;
|
|
float clearDepth;
|
|
u8 clearStencil;
|
|
int numDraws;
|
|
// Downloads and textures from this pass.
|
|
int numReads;
|
|
VkImageLayout finalColorLayout;
|
|
VkImageLayout finalDepthStencilLayout;
|
|
PipelineFlags pipelineFlags; // contains the self dependency flag, in the form of USES_INPUT_ATTACHMENT
|
|
VkRect2D renderArea;
|
|
// Render pass type. Deduced after finishing recording the pass, from the used pipelines.
|
|
// NOTE: Storing the render pass here doesn't do much good, we change the compatible parameters (load/store ops) during step optimization.
|
|
RenderPassType renderPassType;
|
|
} render;
|
|
struct {
|
|
VKRFramebuffer *src;
|
|
VKRFramebuffer *dst;
|
|
VkRect2D srcRect;
|
|
VkOffset2D dstPos;
|
|
VkImageAspectFlags aspectMask;
|
|
} copy;
|
|
struct {
|
|
VKRFramebuffer *src;
|
|
VKRFramebuffer *dst;
|
|
VkRect2D srcRect;
|
|
VkRect2D dstRect;
|
|
VkImageAspectFlags aspectMask;
|
|
VkFilter filter;
|
|
} blit;
|
|
struct {
|
|
VKRFramebuffer *src;
|
|
VkRect2D srcRect;
|
|
VkImageAspectFlags aspectMask;
|
|
bool delayed;
|
|
} readback;
|
|
struct {
|
|
VkImage image;
|
|
VkRect2D srcRect;
|
|
int mipLevel;
|
|
} readback_image;
|
|
};
|
|
};
|
|
|
|
// These are enqueued from the main thread,
|
|
// and the render thread pops them off
|
|
struct VKRRenderThreadTask {
|
|
VKRRenderThreadTask(VKRRunType _runType) : runType(_runType) {}
|
|
std::vector<VKRStep *> steps;
|
|
int frame = -1;
|
|
VKRRunType runType;
|
|
|
|
// Avoid copying these by accident.
|
|
VKRRenderThreadTask(VKRRenderThreadTask &) = delete;
|
|
VKRRenderThreadTask &operator =(VKRRenderThreadTask &) = delete;
|
|
};
|
|
|
|
class VulkanQueueRunner {
|
|
public:
|
|
VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan) {}
|
|
|
|
void SetBackbuffer(VkFramebuffer fb, VkImage img) {
|
|
backbuffer_ = fb;
|
|
backbufferImage_ = img;
|
|
}
|
|
|
|
void PreprocessSteps(std::vector<VKRStep *> &steps);
|
|
void RunSteps(std::vector<VKRStep *> &steps, int curFrame, FrameData &frameData, FrameDataShared &frameDataShared, bool keepSteps = false);
|
|
void LogSteps(const std::vector<VKRStep *> &steps, bool verbose);
|
|
|
|
static std::string StepToString(VulkanContext *vulkan, const VKRStep &step);
|
|
|
|
void CreateDeviceObjects();
|
|
void DestroyDeviceObjects();
|
|
|
|
// Swapchain
|
|
void DestroyBackBuffers();
|
|
bool CreateSwapchain(VkCommandBuffer cmdInit, VulkanBarrierBatch *barriers);
|
|
|
|
bool HasBackbuffers() const {
|
|
return !framebuffers_.empty();
|
|
}
|
|
|
|
// Get a render pass that's compatible with all our framebuffers.
|
|
// Note that it's precached, cannot look up in the map as this might be on another thread.
|
|
VKRRenderPass *GetCompatibleRenderPass() const {
|
|
return compatibleRenderPass_;
|
|
}
|
|
|
|
inline int RPIndex(VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth) {
|
|
return (int)depth * 3 + (int)color;
|
|
}
|
|
|
|
// src == 0 means to copy from the sync readback buffer.
|
|
bool CopyReadbackBuffer(FrameData &frameData, VKRFramebuffer *src, int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels);
|
|
|
|
VKRRenderPass *GetRenderPass(const RPKey &key);
|
|
|
|
bool GetRenderPassKey(VKRRenderPass *passToFind, RPKey *outKey) const {
|
|
for (int i = 0; i < RPKey::INDEX_COUNT; i++) {
|
|
if (retro_atomic_load_acquire_ptr(const_cast<retro_atomic_ptr_t *>(&renderPasses_[i])) == passToFind) {
|
|
*outKey = passToFind->Key();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void EnableHacks(uint32_t hacks) {
|
|
hacksEnabled_ = hacks;
|
|
}
|
|
|
|
bool InitBackbufferFramebuffers(int width, int height, FrameDataShared &frameDataShared);
|
|
bool InitDepthStencilBuffer(VkCommandBuffer cmd, VulkanBarrierBatch *barriers); // Used for non-buffered rendering.
|
|
private:
|
|
|
|
VKRRenderPass *PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd);
|
|
void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd, int curFrame, QueueProfileContext &profile);
|
|
void PerformCopy(const VKRStep &pass, VkCommandBuffer cmd);
|
|
void PerformBlit(const VKRStep &pass, VkCommandBuffer cmd);
|
|
void PerformReadback(const VKRStep &pass, VkCommandBuffer cmd, FrameData &frameData);
|
|
void PerformReadbackImage(const VKRStep &pass, VkCommandBuffer cmd);
|
|
|
|
void LogRenderPass(const VKRStep &pass, bool verbose);
|
|
void LogCopy(const VKRStep &pass);
|
|
void LogBlit(const VKRStep &pass);
|
|
void LogReadback(const VKRStep &pass);
|
|
void LogReadbackImage(const VKRStep &pass);
|
|
|
|
void ResizeReadbackBuffer(CachedReadback *readback, VkDeviceSize requiredSize);
|
|
|
|
static void ApplyMGSHack(std::vector<VKRStep *> &steps);
|
|
static void ApplySonicHack(std::vector<VKRStep *> &steps);
|
|
static void ApplyRenderPassMerge(std::vector<VKRStep *> &steps);
|
|
|
|
static void SetupTransferDstWriteAfterWrite(VKRImage &img, VkImageAspectFlags aspect, VulkanBarrierBatch *recordBarrier);
|
|
|
|
VulkanContext *vulkan_;
|
|
|
|
VkFramebuffer backbuffer_ = VK_NULL_HANDLE;
|
|
VkImage backbufferImage_ = VK_NULL_HANDLE;
|
|
|
|
// The "Compatible" render pass. Should be able to get rid of this soon.
|
|
VKRRenderPass *compatibleRenderPass_ = nullptr;
|
|
|
|
// Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents.
|
|
// Each VKRRenderPass contains all compatibility classes (which attachments they have, etc).
|
|
// VKRRenderPass * by RPKey::Index(). Filled in with CAS from the main and render threads, see
|
|
// GetRenderPass, and only emptied in DestroyDeviceObjects.
|
|
retro_atomic_ptr_t renderPasses_[RPKey::INDEX_COUNT]{};
|
|
|
|
// Readback buffer. Currently we only support synchronous readback, so we only really need one.
|
|
// We size it generously.
|
|
CachedReadback syncReadback_{};
|
|
|
|
// TODO: Enable based on compat.ini.
|
|
uint32_t hacksEnabled_ = 0;
|
|
|
|
// Image barrier helper used during command buffer record (PerformRenderPass etc).
|
|
// Stored here to help reuse the allocation.
|
|
|
|
VulkanBarrierBatch recordBarrier_;
|
|
|
|
std::vector<VkFramebuffer> framebuffers_;
|
|
struct DepthBufferInfo {
|
|
VkFormat format = VK_FORMAT_UNDEFINED;
|
|
VkImage image = VK_NULL_HANDLE;
|
|
VmaAllocation alloc = VK_NULL_HANDLE;
|
|
VkImageView view = VK_NULL_HANDLE;
|
|
};
|
|
DepthBufferInfo depth_;
|
|
};
|
|
|
|
const char *VKRRenderCommandToString(VKRRenderCommand cmd);
|