Skip to content

Commit

Permalink
GLES: Avoid hanging while precompiling shaders.
Browse files Browse the repository at this point in the history
We want to continue updating the screen so it doesn't seem frozen.
  • Loading branch information
unknownbrackets committed Dec 3, 2017
1 parent 60c4ac5 commit 9f000dd
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 25 deletions.
7 changes: 6 additions & 1 deletion GPU/GLES/GPU_GLES.cpp
Expand Up @@ -171,7 +171,8 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
if (discID.size()) {
File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE));
shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) + "/" + discID + ".glshadercache";
shaderManagerGL_->LoadAndPrecompile(shaderCachePath_);
// Actually precompiled by IsReady() since we're single-threaded.
shaderManagerGL_->Load(shaderCachePath_);
}

if (g_Config.bHardwareTessellation) {
Expand Down Expand Up @@ -350,6 +351,10 @@ void GPU_GLES::CheckGPUFeatures() {
gstate_c.featureFlags = features;
}

bool GPU_GLES::IsReady() {
return shaderManagerGL_->ContinuePrecompile();
}

// Let's avoid passing nulls into snprintf().
static const char *GetGLStringAlways(GLenum name) {
const GLubyte *value = glGetString(name);
Expand Down
2 changes: 2 additions & 0 deletions GPU/GLES/GPU_GLES.h
Expand Up @@ -38,6 +38,8 @@ class GPU_GLES : public GPUCommon {
// This gets called on startup and when we get back from settings.
void CheckGPUFeatures();

bool IsReady() override;

void PreExecuteOp(u32 op, u32 diff) override;
void ExecuteOp(u32 op, u32 diff) override;

Expand Down
97 changes: 74 additions & 23 deletions GPU/GLES/ShaderManagerGLES.cpp
Expand Up @@ -955,7 +955,7 @@ struct CacheHeader {
int numLinkedPrograms;
};

void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
void ShaderManagerGLES::Load(const std::string &filename) {
File::IOFile f(filename, "rb");
u64 sz = f.GetSize();
if (!f.IsOpen()) {
Expand All @@ -969,7 +969,8 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
return;
}
time_update();
double start = time_now_d();
diskCachePending_.start = time_now_d();
diskCachePending_.Clear();

// Sanity check the file contents
if (header.numFragmentShaders > 1000 || header.numVertexShaders > 1000 || header.numLinkedPrograms > 1000) {
Expand All @@ -987,16 +988,59 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
return;
}

for (int i = 0; i < header.numVertexShaders; i++) {
VShaderID id;
if (!f.ReadArray(&id, 1)) {
diskCachePending_.vert.resize(header.numVertexShaders);
if (!f.ReadArray(&diskCachePending_.vert[0], header.numVertexShaders)) {
diskCachePending_.vert.clear();
return;
}

diskCachePending_.frag.resize(header.numFragmentShaders);
if (!f.ReadArray(&diskCachePending_.frag[0], header.numFragmentShaders)) {
diskCachePending_.vert.clear();
diskCachePending_.frag.clear();
return;
}

for (int i = 0; i < header.numLinkedPrograms; i++) {
VShaderID vsid;
FShaderID fsid;
if (!f.ReadArray(&vsid, 1)) {
return;
}
if (!f.ReadArray(&fsid, 1)) {
return;
}
diskCachePending_.link.push_back(std::make_pair(vsid, fsid));
}

// Actual compilation happens in ContinuePrecompile(), called by GPU_GLES's IsReady.
NOTICE_LOG(G3D, "Precompiling the shader cache from '%s'", filename.c_str());
diskCacheDirty_ = false;
}

bool ShaderManagerGLES::ContinuePrecompile(float sliceTime) {
auto &pending = diskCachePending_;
if (pending.Done()) {
return true;
}

double start = real_time_now();
// Let's try to keep it under sliceTime if possible.
double end = start + sliceTime;

for (size_t &i = pending.vertPos; i < pending.vert.size(); i++) {
if (real_time_now() >= end) {
// We'll finish later.
return false;
}

const VShaderID &id = pending.vert[i];
if (!vsCache_.Get(id)) {
if (id.Bit(VS_BIT_IS_THROUGH) && id.Bit(VS_BIT_USE_HW_TRANSFORM)) {
// Clearly corrupt, bailing.
ERROR_LOG_REPORT(G3D, "Corrupt shader cache: Both IS_THROUGH and USE_HW_TRANSFORM set.");
return;
pending.Clear();
return false;
}

Shader *vs = CompileVertexShader(id);
Expand All @@ -1005,33 +1049,37 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
// without trying to deduce the vertType from the VSID.
ERROR_LOG(G3D, "Failed to compile a vertex shader loading from cache. Skipping rest of shader cache.");
delete vs;
return;
pending.Clear();
return false;
}
vsCache_.Insert(id, vs);
} else {
WARN_LOG(G3D, "Duplicate vertex shader found in GL shader cache, ignoring");
}
}
for (int i = 0; i < header.numFragmentShaders; i++) {
FShaderID id;
if (!f.ReadArray(&id, 1)) {
return;

for (size_t &i = pending.fragPos; i < pending.frag.size(); i++) {
if (real_time_now() >= end) {
// We'll finish later.
return false;
}

const FShaderID &id = pending.frag[i];
if (!fsCache_.Get(id)) {
fsCache_.Insert(id, CompileFragmentShader(id));
} else {
WARN_LOG(G3D, "Duplicate fragment shader found in GL shader cache, ignoring");
}
}
for (int i = 0; i < header.numLinkedPrograms; i++) {
VShaderID vsid;
FShaderID fsid;
if (!f.ReadArray(&vsid, 1)) {
return;
}
if (!f.ReadArray(&fsid, 1)) {
return;

for (size_t &i = pending.linkPos; i < pending.link.size(); i++) {
if (real_time_now() >= end) {
// We'll finish later.
return false;
}

const VShaderID &vsid = pending.link[i].first;
const FShaderID &fsid = pending.link[i].second;
Shader *vs = vsCache_.Get(vsid);
Shader *fs = fsCache_.Get(fsid);
if (vs && fs) {
Expand All @@ -1040,12 +1088,15 @@ void ShaderManagerGLES::LoadAndPrecompile(const std::string &filename) {
linkedShaderCache_.push_back(entry);
}
}

// Okay, finally done. Time to report status.
time_update();
double end = time_now_d();
double finish = time_now_d();

NOTICE_LOG(G3D, "Compiled and linked %d programs (%d vertex, %d fragment) in %0.1f milliseconds", header.numLinkedPrograms, header.numVertexShaders, header.numFragmentShaders, 1000 * (end - start));
NOTICE_LOG(G3D, "Loaded the shader cache from '%s'", filename.c_str());
diskCacheDirty_ = false;
NOTICE_LOG(G3D, "Compiled and linked %d programs (%d vertex, %d fragment) in %0.1f milliseconds", (int)pending.link.size(), (int)pending.vert.size(), (int)pending.frag.size(), 1000 * (finish - pending.start));
pending.Clear();

return true;
}

void ShaderManagerGLES::Save(const std::string &filename) {
Expand Down
26 changes: 25 additions & 1 deletion GPU/GLES/ShaderManagerGLES.h
Expand Up @@ -167,7 +167,8 @@ class ShaderManagerGLES : public ShaderManagerCommon {
std::vector<std::string> DebugGetShaderIDs(DebugShaderType type);
std::string DebugGetShaderString(std::string id, DebugShaderType type, DebugShaderStringType stringType);

void LoadAndPrecompile(const std::string &filename);
void Load(const std::string &filename);
bool ContinuePrecompile(float sliceTime = 1.0f / 60.0f);
void Save(const std::string &filename);

private:
Expand Down Expand Up @@ -203,4 +204,27 @@ class ShaderManagerGLES : public ShaderManagerCommon {
VSCache vsCache_;

bool diskCacheDirty_;
struct {
std::vector<VShaderID> vert;
std::vector<FShaderID> frag;
std::vector<std::pair<VShaderID, FShaderID>> link;

size_t vertPos = 0;
size_t fragPos = 0;
size_t linkPos = 0;
double start;

void Clear() {
vert.clear();
frag.clear();
link.clear();
vertPos = 0;
fragPos = 0;
linkPos = 0;
}

bool Done() {
return vertPos >= vert.size() && fragPos >= frag.size() && linkPos >= link.size();
}
} diskCachePending_;
};

0 comments on commit 9f000dd

Please sign in to comment.