mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 02:39:05 +02:00
The depth draws were queued and rasterized on the emu thread at each render target change, in Wipeout about 40 small batches a frame, and around 40% of that thread's time. Now a render target change hands the batch to a worker thread and carries on. Waiting for it is left to whatever reads the depth buffers: DrawSync and ListSync, block transfers, framebuffer copies, showing a frame, savestates and shutdown, and the texture cache when the texture or CLUT it reads overlaps a depth buffer still being written. Batches grow as needed instead of being preallocated at full size. A full batch is now handed over and a new one started, where the draw used to be dropped. Wipeout headless, output unchanged: 16% less wall time on a Galaxy S8 (8.2 s to 6.9 s for 10 emulated seconds). On an M1, where headless waits on the GPU, the emu thread uses 38% less CPU time. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
135 lines
4.4 KiB
C++
135 lines
4.4 KiB
C++
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <mutex>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "Common/CommonTypes.h"
|
|
#include "GPU/ge_constants.h"
|
|
|
|
struct DepthScreenVertex {
|
|
int x;
|
|
int y;
|
|
int z;
|
|
};
|
|
|
|
// X11.h, sigh.
|
|
#ifdef Always
|
|
#undef Always
|
|
#endif
|
|
|
|
// We only need to support these three modes.
|
|
enum class ZCompareMode : u8 {
|
|
Greater, // Most common
|
|
Less, // Less common
|
|
Always, // Mostly used for clears
|
|
};
|
|
|
|
struct DepthScissor {
|
|
u16 x1;
|
|
u16 y1;
|
|
u16 x2;
|
|
u16 y2;
|
|
|
|
DepthScissor Tile(int tile, int numTiles) const;
|
|
};
|
|
|
|
struct DepthDraw {
|
|
u32 depthAddr;
|
|
u16 depthStride;
|
|
u8 cullMode;
|
|
GEPrimitiveType prim;
|
|
ZCompareMode compareMode;
|
|
bool cullEnabled;
|
|
DepthScissor scissor;
|
|
int vertexOffset;
|
|
int indexOffset;
|
|
int vertexCount;
|
|
};
|
|
|
|
// Specialized, very limited depth-only rasterizer.
|
|
// Meant to run in parallel with hardware rendering, in games that read back the depth buffer
|
|
// for effects like lens flare.
|
|
// So, we can be quite inaccurate without any issues, and skip a lot of functionality.
|
|
|
|
class VertexDecoder;
|
|
struct TransformedVertex;
|
|
|
|
// maxOutCount is the capacity of each of tx/ty/tz, in elements.
|
|
int DepthRasterClipIndexedTriangles(int *tx, int *ty, float *tz, const float *transformed, const uint16_t *indexBuffer, const DepthDraw &draw, const DepthScissor scissor, int maxOutCount);
|
|
int DepthRasterClipIndexedRectangles(int *tx, int *ty, float *tz, const float *transformed, const uint16_t *indexBuffer, const DepthDraw &draw, const DepthScissor scissor);
|
|
void DecodeAndTransformForDepthRaster(float *dest, const float *worldviewproj, const void *vertexData, int indexLowerBound, int indexUpperBound, const VertexDecoder *dec, u32 vertTypeID);
|
|
void TransformPredecodedForDepthRaster(float *dest, const float *worldviewproj, const void *decodedVertexData, const VertexDecoder *dec, int count);
|
|
void ConvertPredecodedThroughForDepthRaster(float *dest, const void *decodedVertexData, const VertexDecoder *dec, int count);
|
|
void DepthRasterScreenVerts(uint16_t *depth, int depthStride, const int *tx, const int *ty, const float *tz, int count, const DepthDraw &draw, const DepthScissor scissor, bool lowQ);
|
|
|
|
// A batch of queued depth draws, with their transformed vertices and indices.
|
|
struct DepthBatch {
|
|
float *transformed = nullptr;
|
|
uint16_t *indices = nullptr;
|
|
std::vector<DepthDraw> draws;
|
|
int vertexCount = 0;
|
|
int indexCount = 0;
|
|
int vertexCapacity = 0;
|
|
int indexCapacity = 0;
|
|
bool lowQ = false;
|
|
};
|
|
|
|
// Rasterizes batches of depth draws on a thread of its own, in the order they're submitted, so
|
|
// that it overlaps with emulating the CPU and the rest of the GE. The PSP memory it writes is only
|
|
// the depth buffers of the draws, so anything reading those has to Wait() first (or WaitIfWriting).
|
|
class DepthRasterWorker {
|
|
public:
|
|
DepthRasterWorker(int maxVerts, int maxIndices);
|
|
~DepthRasterWorker();
|
|
|
|
// The batch to add draws to.
|
|
DepthBatch &Current() { return *current_; }
|
|
// Makes room in the current batch for this many more vertices and indices.
|
|
void Reserve(int vertices, int indices);
|
|
// Starts rasterizing the current batch (if it has draws) and makes an empty one current.
|
|
void Submit();
|
|
// Submits the current batch and waits until all are rasterized.
|
|
void Wait();
|
|
// Waits only if a batch being rasterized may write to [addr, addr + size).
|
|
void WaitIfWriting(u32 addr, u32 size);
|
|
|
|
// Time spent culling and rasterizing since the last call, for the stats.
|
|
void TakeTimes(double *cull, double *raster);
|
|
|
|
private:
|
|
DepthBatch *NewBatch();
|
|
static void FreeBatch(DepthBatch *batch);
|
|
void Run();
|
|
void Rasterize(const DepthBatch &batch, double *cull, double *raster);
|
|
|
|
const int maxVerts_;
|
|
const int maxIndices_;
|
|
int *screenVerts_ = nullptr;
|
|
|
|
std::vector<DepthBatch *> all_;
|
|
std::vector<DepthBatch *> free_;
|
|
std::vector<DepthBatch *> queue_; // Oldest first.
|
|
DepthBatch *current_ = nullptr;
|
|
int running_ = 0; // Batches taken off the queue and not yet done.
|
|
|
|
// The depth buffer bytes the submitted, unfinished batches can write.
|
|
// The depth buffers being written (VRAM offsets, end exclusive), and their span, which only grows
|
|
// while busy_, so it can be checked without the lock.
|
|
std::vector<std::pair<u32, u32>> busyRanges_;
|
|
std::atomic<u32> busyLow_{};
|
|
std::atomic<u32> busyHigh_{};
|
|
std::atomic<bool> busy_{};
|
|
|
|
double cullTime_ = 0.0;
|
|
double rasterTime_ = 0.0;
|
|
|
|
bool quit_ = false;
|
|
std::mutex mutex_;
|
|
std::condition_variable work_;
|
|
std::condition_variable done_;
|
|
std::thread thread_;
|
|
};
|