Merge pull request #698 from mapbox/cache-shaders

Add caching of compiled shaders
This commit is contained in:
Dane Springmeyer
2014-12-05 23:37:35 -05:00
12 changed files with 254 additions and 47 deletions

View File

@@ -32,6 +32,7 @@
'hard_dependency': 1,
'sources': [
'../platform/ios/cache_database_library.mm',
'../platform/ios/shader_cache_library.mm',
'../platform/darwin/log_nslog.mm',
'../platform/darwin/string_nsstring.mm',
'../platform/darwin/http_request_baton_cocoa.mm',

View File

@@ -28,6 +28,7 @@
},
'sources': [
'../platform/default/cache_database_tmp.cpp',
'../platform/default/shader_cache_tmp.cpp',
'../platform/default/log_stderr.cpp',
'../platform/default/string_stdlib.cpp',
'../platform/default/http_request_baton_curl.cpp',

View File

@@ -7,6 +7,7 @@
'hard_dependency': 1,
'sources': [
'../platform/osx/cache_database_application_support.mm',
'../platform/osx/shader_cache_application_support.mm',
'../platform/darwin/log_nslog.mm',
'../platform/darwin/string_nsstring.mm',
'../platform/darwin/http_request_baton_cocoa.mm',

View File

@@ -118,6 +118,7 @@ typedef void (* PFNGLGETOBJECTLABELEXTPROC) (GLenum type, GLuint object, GLsizei
extern PFNGLLABELOBJECTEXTPROC LabelObjectEXT;
extern PFNGLGETOBJECTLABELEXTPROC GetObjectLabelEXT;
// GL_ARB_vertex_array_object / GL_OES_vertex_array_object
#define GL_VERTEX_ARRAY_BINDING 0x85B5
typedef void (* PFNGLBINDVERTEXARRAYPROC) (GLuint array);
typedef void (* PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint* arrays);
@@ -128,6 +129,17 @@ extern PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays;
extern PFNGLGENVERTEXARRAYSPROC GenVertexArrays;
extern PFNGLISVERTEXARRAYPROC IsVertexArray;
// GL_ARB_get_program_binary / GL_OES_get_program_binary
#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257
#define GL_PROGRAM_BINARY_LENGTH 0x8741
#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE
#define GL_PROGRAM_BINARY_FORMATS 0x87FF
typedef void (* PFNGLGETPROGRAMBINARYPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLenum *binaryFormat, void *binary);
typedef void (* PFNGLPROGRAMBINARYPROC) (GLuint program, GLenum binaryFormat, const void *binary, GLsizei length);
typedef void (* PFNGLPROGRAMPARAMETERIPROC) (GLuint program, GLenum pname, GLint value);
extern PFNGLGETPROGRAMBINARYPROC GetProgramBinary;
extern PFNGLPROGRAMBINARYPROC ProgramBinary;
extern PFNGLPROGRAMPARAMETERIPROC ProgramParameteri;
// Debug group markers, useful for debugging on iOS
#if defined(DEBUG)

View File

@@ -20,6 +20,9 @@ std::string lowercase(const std::string &string);
// Returns the path to the default cache database on this system.
std::string defaultCacheDatabase();
// Returns the path to the default shader cache on this system.
std::string defaultShaderCache();
// Shows an alpha image with the specified dimensions in a named window.
void show_debug_image(std::string name, const char *data, size_t width, size_t height);

View File

@@ -82,6 +82,17 @@ void GLFWView::initialize(mbgl::Map *map_) {
gl::GetObjectLabel = (gl::PFNGLGETOBJECTLABELPROC)glfwGetProcAddress("glGetObjectLabel");
gl::ObjectPtrLabel = (gl::PFNGLOBJECTPTRLABELPROC)glfwGetProcAddress("glObjectPtrLabel");
gl::GetObjectPtrLabel = (gl::PFNGLGETOBJECTPTRLABELPROC)glfwGetProcAddress("glGetObjectPtrLabel");
assert(gl::DebugMessageControl != nullptr);
assert(gl::DebugMessageInsert != nullptr);
assert(gl::DebugMessageCallback != nullptr);
assert(gl::GetDebugMessageLog != nullptr);
assert(gl::GetPointerv != nullptr);
assert(gl::PushDebugGroup != nullptr);
assert(gl::PopDebugGroup != nullptr);
assert(gl::ObjectLabel != nullptr);
assert(gl::GetObjectLabel != nullptr);
assert(gl::ObjectPtrLabel != nullptr);
assert(gl::GetObjectPtrLabel != nullptr);
} else {
if (extensions.find("GL_ARB_debug_output") != std::string::npos) {
gl::DebugMessageControl = (gl::PFNGLDEBUGMESSAGECONTROLPROC)glfwGetProcAddress("glDebugMessageControlARB");
@@ -89,17 +100,27 @@ void GLFWView::initialize(mbgl::Map *map_) {
gl::DebugMessageCallback = (gl::PFNGLDEBUGMESSAGECALLBACKPROC)glfwGetProcAddress("glDebugMessageCallbackARB");
gl::GetDebugMessageLog = (gl::PFNGLGETDEBUGMESSAGELOGPROC)glfwGetProcAddress("glGetDebugMessageLogARB");
gl::GetPointerv = (gl::PFNGLGETPOINTERVPROC)glfwGetProcAddress("glGetPointerv");
assert(gl::DebugMessageControl != nullptr);
assert(gl::DebugMessageInsert != nullptr);
assert(gl::DebugMessageCallback != nullptr);
assert(gl::GetDebugMessageLog != nullptr);
assert(gl::GetPointerv != nullptr);
}
if (extensions.find("GL_EXT_debug_marker") != std::string::npos) {
gl::InsertEventMarkerEXT = (gl::PFNGLINSERTEVENTMARKEREXTPROC)glfwGetProcAddress("glInsertEventMarkerEXT");
gl::PushGroupMarkerEXT = (gl::PFNGLPUSHGROUPMARKEREXTPROC)glfwGetProcAddress("glPushGroupMarkerEXT");
gl::PopGroupMarkerEXT = (gl::PFNGLPOPGROUPMARKEREXTPROC)glfwGetProcAddress("glPopGroupMarkerEXT");
assert(gl::InsertEventMarkerEXT != nullptr);
assert(gl::PushGroupMarkerEXT != nullptr);
assert(gl::PopGroupMarkerEXT != nullptr);
}
if (extensions.find("GL_EXT_debug_label") != std::string::npos) {
gl::LabelObjectEXT = (gl::PFNGLLABELOBJECTEXTPROC)glfwGetProcAddress("glLabelObjectEXT");
gl::GetObjectLabelEXT = (gl::PFNGLGETOBJECTLABELEXTPROC)glfwGetProcAddress("glGetObjectLabelEXT");
assert(gl::LabelObjectEXT != nullptr);
assert(gl::GetObjectLabelEXT != nullptr);
}
}
@@ -108,11 +129,28 @@ void GLFWView::initialize(mbgl::Map *map_) {
gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)glfwGetProcAddress("glDeleteVertexArrays");
gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)glfwGetProcAddress("glGenVertexArrays");
gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)glfwGetProcAddress("glIsVertexArray");
assert(gl::BindVertexArray != nullptr);
assert(gl::DeleteVertexArrays != nullptr);
assert(gl::GenVertexArrays != nullptr);
assert(gl::IsVertexArray != nullptr);
} else if (extensions.find("GL_APPLE_vertex_array_object") != std::string::npos) {
gl::BindVertexArray = (gl::PFNGLBINDVERTEXARRAYPROC)glfwGetProcAddress("glBindVertexArrayAPPLE");
gl::DeleteVertexArrays = (gl::PFNGLDELETEVERTEXARRAYSPROC)glfwGetProcAddress("glDeleteVertexArraysAPPLE");
gl::GenVertexArrays = (gl::PFNGLGENVERTEXARRAYSPROC)glfwGetProcAddress("glGenVertexArraysAPPLE");
gl::IsVertexArray = (gl::PFNGLISVERTEXARRAYPROC)glfwGetProcAddress("glIsVertexArrayAPPLE");
assert(gl::BindVertexArray != nullptr);
assert(gl::DeleteVertexArrays != nullptr);
assert(gl::GenVertexArrays != nullptr);
assert(gl::IsVertexArray != nullptr);
}
if (extensions.find("GL_ARB_get_program_binary") != std::string::npos) {
gl::GetProgramBinary = (gl::PFNGLGETPROGRAMBINARYPROC)glfwGetProcAddress("glGetProgramBinary");
gl::ProgramBinary = (gl::PFNGLPROGRAMBINARYPROC)glfwGetProcAddress("glProgramBinary");
gl::ProgramParameteri = (gl::PFNGLPROGRAMPARAMETERIPROC)glfwGetProcAddress("glProgramParameteri");
assert(gl::GetProgramBinary != nullptr);
assert(gl::ProgramBinary != nullptr);
assert(gl::ProgramParameteri != nullptr);
}
}

View File

@@ -0,0 +1,12 @@
#include <mbgl/platform/platform.hpp>
namespace mbgl {
namespace platform {
// Returns the path to the default shader cache on this system.
std::string defaultShaderCache() {
return "/tmp/mbgl-shader-cache-";
}
}
}

View File

@@ -0,0 +1,21 @@
#import <Foundation/Foundation.h>
#include <mbgl/platform/platform.hpp>
namespace mbgl {
namespace platform {
// Returns the path to the default shader cache on this system.
std::string defaultShaderCache() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
if ([paths count] == 0) {
// Disable the cache if we don't have a location to write.
return "";
}
NSString *libraryDirectory = [paths objectAtIndex:0];
return [[libraryDirectory stringByAppendingPathComponent:@"shader-cache-"] UTF8String];
}
}
}

View File

@@ -0,0 +1,31 @@
#import <Foundation/Foundation.h>
#include <mbgl/platform/platform.hpp>
namespace mbgl {
namespace platform {
// Returns the path to the default shader cache on this system.
std::string defaultShaderCache() {
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
if ([paths count] == 0) {
// Disable the cache if we don't have a location to write.
return "";
}
NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"];
if (![[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:nil]) {
// Disable the cache if we couldn't create the directory.
return "";
}
return [[path stringByAppendingPathComponent:@"shader-cache-"] UTF8String];
}
}
}

View File

@@ -70,6 +70,10 @@ PFNGLDELETEVERTEXARRAYSPROC DeleteVertexArrays = nullptr;
PFNGLGENVERTEXARRAYSPROC GenVertexArrays = nullptr;
PFNGLISVERTEXARRAYPROC IsVertexArray = nullptr;
PFNGLGETPROGRAMBINARYPROC GetProgramBinary = nullptr;
PFNGLPROGRAMBINARYPROC ProgramBinary = nullptr;
PFNGLPROGRAMPARAMETERIPROC ProgramParameteri = nullptr;
}
}

View File

@@ -2,9 +2,11 @@
#include <mbgl/platform/gl.hpp>
#include <mbgl/util/stopwatch.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/platform/platform.hpp>
#include <cstring>
#include <cstdlib>
#include <cassert>
using namespace mbgl;
@@ -14,49 +16,97 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo
program(0) {
util::stopwatch stopwatch("shader compilation", Event::Shader);
GLuint vertShader;
if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertSource)) {
Log::Error(Event::Shader, "Vertex shader failed to compile: %s", vertSource);
return;
}
GLuint fragShader;
if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragSource)) {
Log::Error(Event::Shader, "Fragment shader failed to compile: %s", fragSource);
return;
}
program = glCreateProgram();
// Attach shaders
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
if (!mbgl::platform::defaultShaderCache().empty()) {
binaryFileName = mbgl::platform::defaultShaderCache() + name + ".bin";
}
// Load binary shader if it exists
bool skipCompile = false;
if (!binaryFileName.empty() && (gl::ProgramBinary != nullptr)) {
FILE *binaryFile = fopen(binaryFileName.c_str(), "rb");
if (binaryFile != nullptr) {
GLsizei binaryLength;
GLenum binaryFormat;
bool lengthOk = fread(&binaryLength, sizeof(binaryLength), 1, binaryFile) == 1;
bool formatOk = fread(&binaryFormat, sizeof(binaryFormat), 1, binaryFile) == 1;
{
// Link program
GLint status;
glLinkProgram(program);
if (lengthOk && formatOk && binaryLength > 0) {
std::unique_ptr<char[]> binary = mbgl::util::make_unique<char[]>(binaryLength);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == 0) {
GLint logLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(program, logLength, &logLength, log);
Log::Error(Event::Shader, "Program failed to link: %s", log);
free(log);
if (binary != nullptr) {
bool binaryOk = fread(binary.get(), binaryLength, 1, binaryFile) == 1;
if (binaryOk) {
gl::ProgramBinary(program, binaryFormat, binary.get(), binaryLength);
// Check if the binary was valid
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_TRUE) {
skipCompile = true;
}
}
}
}
glDeleteShader(vertShader);
vertShader = 0;
glDeleteShader(fragShader);
fragShader = 0;
fclose(binaryFile);
binaryFile = nullptr;
}
}
GLuint vertShader = 0;
GLuint fragShader = 0;
if (!skipCompile) {
if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertSource)) {
Log::Error(Event::Shader, "Vertex shader %s failed to compile: %s", name, vertSource);
glDeleteProgram(program);
program = 0;
return;
}
if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragSource)) {
Log::Error(Event::Shader, "Fragment shader %s failed to compile: %s", name, fragSource);
glDeleteShader(vertShader);
vertShader = 0;
glDeleteProgram(program);
program = 0;
return;
}
// Attach shaders
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
{
if (!binaryFileName.empty() && (gl::ProgramParameteri != nullptr)) {
gl::ProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
}
// Link program
GLint status;
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == 0) {
GLint logLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
std::unique_ptr<GLchar[]> log = mbgl::util::make_unique<GLchar[]>(logLength);
glGetProgramInfoLog(program, logLength, &logLength, log.get());
Log::Error(Event::Shader, "Program failed to link: %s", log.get());
}
glDeleteShader(vertShader);
vertShader = 0;
glDeleteShader(fragShader);
fragShader = 0;
glDeleteProgram(program);
program = 0;
return;
}
}
}
{
@@ -69,10 +119,9 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo
GLint logLength;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(program, logLength, &logLength, log);
Log::Error(Event::Shader, "Program failed to validate: %s", log);
free(log);
std::unique_ptr<GLchar[]> log = mbgl::util::make_unique<GLchar[]>(logLength);
glGetProgramInfoLog(program, logLength, &logLength, log.get());
Log::Error(Event::Shader, "Program failed to validate: %s", log.get());
}
glDeleteShader(vertShader);
@@ -82,14 +131,15 @@ Shader::Shader(const char *name_, const GLchar *vertSource, const GLchar *fragSo
glDeleteProgram(program);
program = 0;
}
}
// Remove the compiled shaders; they are now part of the program.
glDetachShader(program, vertShader);
glDeleteShader(vertShader);
glDetachShader(program, fragShader);
glDeleteShader(fragShader);
if (!skipCompile) {
// Remove the compiled shaders; they are now part of the program.
glDetachShader(program, vertShader);
glDeleteShader(vertShader);
glDetachShader(program, fragShader);
glDeleteShader(fragShader);
}
valid = true;
}
@@ -100,7 +150,7 @@ bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) {
*shader = glCreateShader(type);
const GLchar *strings[] = { source };
const GLint lengths[] = { (GLint)strlen(source) };
const GLsizei lengths[] = { (GLsizei)strlen(source) };
glShaderSource(*shader, 1, strings, lengths);
glCompileShader(*shader);
@@ -110,10 +160,9 @@ bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) {
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
Log::Error(Event::Shader, "Shader failed to compile: %s", log);
free(log);
std::unique_ptr<GLchar[]> log = mbgl::util::make_unique<GLchar[]>(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log.get());
Log::Error(Event::Shader, "Shader failed to compile: %s", log.get());
}
glDeleteShader(*shader);
@@ -121,10 +170,41 @@ bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) {
return false;
}
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
Log::Error(Event::Shader, "Shader %s failed to compile.", name, type);
glDeleteShader(*shader);
*shader = 0;
return false;
}
return true;
}
Shader::~Shader() {
if (!binaryFileName.empty() && (gl::GetProgramBinary != nullptr)) {
// Retrieve the program binary
GLsizei binaryLength;
GLenum binaryFormat;
glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
if (binaryLength > 0) {
std::unique_ptr<char[]> binary = mbgl::util::make_unique<char[]>(binaryLength);
if (binary != nullptr) {
gl::GetProgramBinary(program, binaryLength, NULL, &binaryFormat, binary.get());
// Write the binary to a file
FILE *binaryFile = fopen(binaryFileName.c_str(), "wb");
if (binaryFile != nullptr) {
fwrite(&binaryLength, sizeof(binaryLength), 1, binaryFile);
fwrite(&binaryFormat, sizeof(binaryFormat), 1, binaryFile);
fwrite(binary.get(), binaryLength, 1, binaryFile);
fclose(binaryFile);
binaryFile = nullptr;
}
}
}
}
if (program) {
glDeleteProgram(program);
program = 0;

View File

@@ -3,6 +3,7 @@
#include <cstdint>
#include <array>
#include <string>
#include <mbgl/util/noncopyable.hpp>
namespace mbgl {
@@ -21,6 +22,8 @@ public:
private:
bool compileShader(uint32_t *shader, uint32_t type, const char *source);
std::string binaryFileName;
};
}