Files
ppsspp/Common/GPU/OpenGL/GLQueueRunner.h
T
Henrik RydgårdandClaude Opus 5.5 1467ef24a7 GL: Report readback failure, and convert from the format actually read
CopyFramebufferToMemory always returned true, even when glReadPixels
failed, and then copied out whatever the readback buffer held from before.
Depth was labeled D24_S8 while GL reads it as float, and the conversion
ignored the source format, so only a D32F destination came out right.

Record the format and the result of each read, convert from that, and
return it. That also fixes the image readback reusing the aspect of an
earlier framebuffer readback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 12:15:56 +02:00

448 lines
9.7 KiB
C++

#pragma once
#include <cstdint>
#include <vector>
#include <set>
#include <unordered_map>
#include "Common/GPU/OpenGL/GLCommon.h"
#include "Common/GPU/OpenGL/GLFrameData.h"
#include "Common/GPU/OpenGL/GLProfiler.h"
#include "Common/GPU/DataFormat.h"
#include "Common/GPU/Shader.h"
#include "Common/GPU/thin3d.h"
#include "Common/Data/Collections/TinySet.h"
#include "Common/Data/Collections/FastVec.h"
struct GLRViewport {
float x, y, w, h, minZ, maxZ;
};
struct GLRect2D {
int x, y, w, h;
};
struct GLOffset2D {
int x, y;
};
enum class GLRAllocType : uint8_t {
NONE,
NEW,
ALIGNED,
};
class GLRShader;
class GLRTexture;
class GLRBuffer;
class GLRFramebuffer;
class GLRProgram;
class GLRInputLayout;
enum class GLRRenderCommand : uint8_t {
DEPTH,
STENCIL,
BLEND,
BLENDCOLOR,
LOGICOP,
UNIFORM4I,
UNIFORM4UI,
UNIFORM4F,
UNIFORMMATRIX,
UNIFORMSTEREOMATRIX,
TEXTURESAMPLER,
TEXTURELOD,
VIEWPORT,
SCISSOR,
RASTER,
CLEAR,
INVALIDATE,
BINDPROGRAM,
BINDTEXTURE,
BIND_FB_TEXTURE,
BIND_VERTEX_BUFFER,
GENMIPS,
DRAW,
TEXTURE_SUBIMAGE,
};
// TODO: Bloated since the biggest struct decides the size. Will need something more efficient (separate structs with shared
// type field, smashed right after each other?)
// Also, all GLenums are really only 16 bits.
struct GLRRenderData {
GLRRenderData(GLRRenderCommand _cmd) : cmd(_cmd) {}
GLRRenderCommand cmd;
union {
struct {
GLboolean enabled;
GLenum srcColor;
GLenum dstColor;
GLenum srcAlpha;
GLenum dstAlpha;
GLenum funcColor;
GLenum funcAlpha;
int mask;
} blend;
struct {
float color[4];
} blendColor;
struct {
GLboolean enabled;
GLenum logicOp;
} logic;
struct {
GLboolean enabled;
GLboolean write;
GLenum func;
} depth;
struct {
GLenum func;
GLenum sFail;
GLenum zFail;
GLenum pass;
GLboolean enabled;
uint8_t ref;
uint8_t compareMask;
uint8_t writeMask;
} stencil;
struct {
GLRInputLayout *inputLayout;
GLRBuffer *vertexBuffer;
GLRBuffer *indexBuffer;
uint32_t vertexOffset;
uint32_t indexOffset;
GLenum mode; // primitive
GLint first;
GLint count;
GLint indexType;
GLint instances;
GLint maxIndex; // -1 if unknown. Otherwise, all indices are known to be <= maxIndex.
} draw;
struct {
const char *name; // if null, use loc
const GLint *loc; // NOTE: This is a pointer so we can immediately use things that are "queried" during program creation.
GLint count;
float v[4];
} uniform4;
struct {
const char *name; // if null, use loc
const GLint *loc;
float m[16];
} uniformMatrix4;
struct {
const char *name; // if null, use loc
const GLint *loc;
float *mData; // new'd, 32 entries
} uniformStereoMatrix4;
struct {
uint32_t clearColor;
float clearZ;
uint8_t clearStencil;
uint8_t colorMask; // Like blend, but for the clear.
GLuint clearMask; // GL_COLOR_BUFFER_BIT etc
int16_t scissorX;
int16_t scissorY;
int16_t scissorW;
int16_t scissorH;
} clear; // also used for invalidate
struct {
int slot;
GLRTexture *texture;
} texture;
struct {
GLRTexture *texture;
Draw::DataFormat format;
uint8_t slot;
uint8_t level;
uint16_t width;
uint16_t height;
uint16_t x;
uint16_t y;
GLRAllocType allocType;
uint8_t *data; // owned, delete[]-d
} texture_subimage;
struct {
GLRFramebuffer* framebuffer;
int slot;
int aspect;
} bind_fb_texture;
struct {
GLRBuffer *buffer;
GLuint target;
} bind_buffer;
struct {
GLRProgram *program;
} program;
struct {
int slot;
GLenum wrapS;
GLenum wrapT;
GLenum magFilter;
GLenum minFilter; // also includes mip. GL...
float anisotropy;
} textureSampler;
struct {
int slot;
float minLod;
float maxLod;
float lodBias;
} textureLod;
struct {
GLRViewport vp;
} viewport;
struct {
GLRect2D rc;
} scissor;
struct {
GLenum frontFace;
GLenum cullFace;
GLboolean cullEnable;
GLboolean ditherEnable;
GLboolean depthClampEnable;
} raster;
};
};
// Unlike in Vulkan, we can't create stuff on the main thread, but need to
// defer this too. A big benefit will be that we'll be able to do all creation
// at the start of the frame.
enum class GLRInitStepType : uint8_t {
CREATE_TEXTURE,
CREATE_SHADER,
CREATE_PROGRAM,
CREATE_BUFFER,
CREATE_INPUT_LAYOUT,
CREATE_FRAMEBUFFER,
TEXTURE_IMAGE,
TEXTURE_SUBIMAGE,
TEXTURE_FINALIZE,
BUFFER_SUBDATA,
};
struct GLRInitStep {
GLRInitStep(GLRInitStepType _type) : stepType(_type) {}
GLRInitStepType stepType;
union {
struct {
GLRTexture *texture;
} create_texture;
struct {
GLRShader *shader;
// This char arrays needs to be allocated with new[].
char *code;
GLuint stage;
} create_shader;
struct {
GLRProgram *program;
GLRShader *shaders[3];
int num_shaders;
bool support_dual_source;
} create_program;
struct {
GLRBuffer *buffer;
int size;
GLuint usage;
} create_buffer;
struct {
GLRInputLayout *inputLayout;
} create_input_layout;
struct {
GLRFramebuffer *framebuffer;
} create_framebuffer;
struct {
GLRBuffer *buffer;
int offset;
int size;
uint8_t *data; // owned, delete[]-d
bool deleteData;
} buffer_subdata;
struct {
GLRTexture *texture;
Draw::DataFormat format;
int level;
uint16_t width;
uint16_t height;
uint16_t depth;
GLRAllocType allocType;
bool linearFilter;
uint8_t *data; // owned, delete[]-d
} texture_image;
struct {
GLRTexture *texture;
Draw::DataFormat format;
int level;
uint16_t x;
uint16_t y;
uint16_t width;
uint16_t height;
GLRAllocType allocType;
uint8_t *data; // owned, delete[]-d
} texture_subimage;
struct {
GLRTexture *texture;
int loadedLevels;
bool genMips;
} texture_finalize;
};
};
enum class GLRStepType : uint8_t {
RENDER,
COPY,
BLIT,
READBACK,
READBACK_IMAGE,
RENDER_SKIP,
};
enum class GLRRenderPassAction {
DONT_CARE,
CLEAR,
KEEP,
};
class GLRFramebuffer;
enum GLRAspect {
GLR_ASPECT_COLOR = 1,
GLR_ASPECT_DEPTH = 2,
GLR_ASPECT_STENCIL = 3,
};
const char *GLRAspectToString(GLRAspect aspect);
struct GLRStep {
GLRStep(GLRStepType _type) : stepType(_type), tag() {}
GLRStepType stepType;
FastVec<GLRRenderData> commands;
TinySet<const GLRFramebuffer *, 8> dependencies;
const char *tag;
union {
struct {
GLRFramebuffer *framebuffer;
GLRRenderPassAction color;
GLRRenderPassAction depth;
GLRRenderPassAction stencil;
} render;
struct {
GLRFramebuffer *src;
GLRFramebuffer *dst;
GLRect2D srcRect;
GLOffset2D dstPos;
int aspectMask;
} copy;
struct {
GLRFramebuffer *src;
GLRFramebuffer *dst;
GLRect2D srcRect;
GLRect2D dstRect;
int aspectMask;
GLboolean filter;
} blit;
struct {
GLRFramebuffer* src;
GLRect2D srcRect;
int aspectMask;
Draw::DataFormat dstFormat;
} readback;
struct {
GLRTexture *texture;
GLRect2D srcRect;
int mipLevel;
} readback_image;
};
};
class GLQueueRunner {
public:
GLQueueRunner() {}
void SetErrorCallback(ErrorCallbackFn callback, void *userdata) {
errorCallback_ = callback;
errorCallbackUserData_ = userdata;
}
void SetDeviceCaps(const Draw::DeviceCaps &caps) {
caps_ = caps;
}
void RunInitSteps(const FastVec<GLRInitStep> &steps, bool skipGLCalls);
void RunSteps(const std::vector<GLRStep *> &steps, GLFrameData &frameData, bool skipGLCalls, bool keepSteps, bool useVR);
void CreateDeviceObjects();
void DestroyDeviceObjects();
// Converts the last readback to destFormat. Returns false if that read failed or can't be converted.
bool CopyFromReadbackBuffer(GLRFramebuffer *framebuffer, int width, int height, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels);
void Resize(int width, int height) {
targetWidth_ = width;
targetHeight_ = height;
}
// Deleting a framebuffer leaves the default one bound, behind fbo_bind_fb_target's cache.
void ForgetBoundFramebuffers() {
currentDrawHandle_ = 0;
currentReadHandle_ = 0;
}
std::string GetGLString(int name) const;
private:
void InitCreateFramebuffer(const GLRInitStep &step);
void PerformBindFramebufferAsRenderTarget(const GLRStep &pass);
void PerformRenderPass(const GLRStep &pass, bool first, bool last, GLQueueProfileContext &profile);
void PerformCopy(const GLRStep &pass);
void PerformBlit(const GLRStep &pass);
void PerformReadback(const GLRStep &pass);
void PerformReadbackImage(const GLRStep &pass);
void fbo_ext_create(const GLRInitStep &step); // Unused on some platforms
void fbo_bind_fb_target(bool read, GLuint name);
GLenum fbo_get_fb_target(bool read, GLuint **cached);
void fbo_unbind();
std::string StepToString(const GLRStep &step) const;
GLRFramebuffer *curFB_ = nullptr;
GLuint globalVAO_ = 0;
Draw::DeviceCaps caps_{}; // For sanity checks.
int curFBWidth_ = 0;
int curFBHeight_ = 0;
int targetWidth_ = 0;
int targetHeight_ = 0;
// Readback buffer. Currently we only support synchronous readback, so we only really need one.
// We size it generously.
uint8_t *readbackBuffer_ = nullptr;
int readbackBufferSize_ = 0;
// What the last readback actually read, and whether it succeeded.
Draw::DataFormat readbackFormat_ = Draw::DataFormat::UNDEFINED;
bool readbackOk_ = false;
float maxAnisotropyLevel_ = 0.0f;
// Framebuffer state?
GLuint currentDrawHandle_ = 0;
GLuint currentReadHandle_ = 0;
std::unordered_map<int, std::string> glStrings_;
bool sawOutOfMemory_ = false;
// On from the first render pass of a frame to the last (a clear turns it off and on again within a pass).
bool scissorTestOn_ = false;
bool useDebugGroups_ = false;
GLProfiler profiler_;
ErrorCallbackFn errorCallback_ = nullptr;
void *errorCallbackUserData_ = nullptr;
};
const char *RenderCommandToString(GLRRenderCommand cmd);