From f86333961eeacb9f2dd83a4c3680d30e06f947a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Wed, 15 Mar 2017 18:43:58 +0100 Subject: [core] cache binary shaders on Android --- cmake/core-files.cmake | 5 + cmake/test-files.cmake | 3 + include/mbgl/map/map.hpp | 3 +- .../com/mapbox/mapboxsdk/maps/NativeMapView.java | 11 +- platform/android/src/native_map_view.cpp | 26 +++-- platform/android/src/native_map_view.hpp | 8 +- proto/binary_program.proto | 18 ++++ src/mbgl/gl/attribute.hpp | 13 ++- src/mbgl/gl/context.cpp | 49 +++++++++ src/mbgl/gl/context.hpp | 10 ++ src/mbgl/gl/features.hpp | 7 ++ src/mbgl/gl/program.hpp | 30 ++++-- src/mbgl/gl/program_binary.cpp | 24 +++++ src/mbgl/gl/program_binary.hpp | 27 +++++ src/mbgl/gl/types.hpp | 2 + src/mbgl/gl/uniform.hpp | 13 ++- src/mbgl/map/map.cpp | 16 ++- src/mbgl/programs/binary_program.cpp | 117 +++++++++++++++++++++ src/mbgl/programs/binary_program.hpp | 43 ++++++++ src/mbgl/programs/program.hpp | 58 +++++++++- src/mbgl/programs/program_parameters.hpp | 15 ++- src/mbgl/programs/programs.hpp | 4 +- src/mbgl/renderer/painter.cpp | 16 +-- src/mbgl/renderer/painter.hpp | 2 +- src/mbgl/shaders/shaders.cpp | 14 +++ src/mbgl/shaders/shaders.hpp | 2 + src/mbgl/util/io.cpp | 10 ++ src/mbgl/util/io.hpp | 3 + test/programs/binary_program.test.cpp | 39 +++++++ 29 files changed, 541 insertions(+), 47 deletions(-) create mode 100644 proto/binary_program.proto create mode 100644 src/mbgl/gl/features.hpp create mode 100644 src/mbgl/gl/program_binary.cpp create mode 100644 src/mbgl/gl/program_binary.hpp create mode 100644 src/mbgl/programs/binary_program.cpp create mode 100644 src/mbgl/programs/binary_program.hpp create mode 100644 test/programs/binary_program.test.cpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index afc45a24cc..61bc79c334 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -67,6 +67,7 @@ set(MBGL_CORE_FILES src/mbgl/gl/draw_mode.hpp src/mbgl/gl/extension.cpp src/mbgl/gl/extension.hpp + src/mbgl/gl/features.hpp src/mbgl/gl/framebuffer.hpp src/mbgl/gl/gl.cpp src/mbgl/gl/index_buffer.hpp @@ -75,6 +76,8 @@ set(MBGL_CORE_FILES src/mbgl/gl/object.hpp src/mbgl/gl/primitives.hpp src/mbgl/gl/program.hpp + src/mbgl/gl/program_binary.cpp + src/mbgl/gl/program_binary.hpp src/mbgl/gl/renderbuffer.hpp src/mbgl/gl/segment.cpp src/mbgl/gl/segment.hpp @@ -134,6 +137,8 @@ set(MBGL_CORE_FILES # programs src/mbgl/programs/attributes.hpp + src/mbgl/programs/binary_program.cpp + src/mbgl/programs/binary_program.hpp src/mbgl/programs/circle_program.cpp src/mbgl/programs/circle_program.hpp src/mbgl/programs/collision_box_program.cpp diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index 873afa3d9f..951514e38a 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -38,6 +38,9 @@ set(MBGL_TEST_FILES test/math/minmax.test.cpp test/math/wrap.test.cpp + # programs + test/programs/binary_program.test.cpp + # sprite test/sprite/sprite_atlas.test.cpp test/sprite/sprite_image.test.cpp diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 02e14bea4e..6b07173747 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -41,7 +41,8 @@ public: MapMode mapMode = MapMode::Continuous, GLContextMode contextMode = GLContextMode::Unique, ConstrainMode constrainMode = ConstrainMode::HeightOnly, - ViewportMode viewportMode = ViewportMode::Default); + ViewportMode viewportMode = ViewportMode::Default, + const std::string& programCacheDir = ""); ~Map(); // Register a callback that will get called (on the render thread) when all resources have diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 1460f08e10..a9394d0b66 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -97,7 +97,8 @@ final class NativeMapView { onMapChangedListeners = new CopyOnWriteArrayList<>(); this.mapView = mapView; - nativeInitialize(this, fileSource, pixelRatio, availableProcessors, totalMemory); + String programCacheDir = context.getCacheDir().getAbsolutePath(); + nativeInitialize(this, fileSource, pixelRatio, programCacheDir, availableProcessors, totalMemory); } // @@ -957,8 +958,12 @@ final class NativeMapView { // JNI methods // - private native void nativeInitialize(NativeMapView nativeMapView, FileSource fileSource, - float pixelRatio, int availableProcessors, long totalMemory); + private native void nativeInitialize(NativeMapView nativeMapView, + FileSource fileSource, + float pixelRatio, + String programCacheDir, + int availableProcessors, + long totalMemory); private native void nativeDestroy(); diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 696849b11b..529ea23304 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -48,13 +48,18 @@ namespace mbgl { namespace android { -NativeMapView::NativeMapView(jni::JNIEnv& _env, jni::Object _obj, jni::Object jFileSource, - jni::jfloat _pixelRatio, jni::jint _availableProcessors, jni::jlong _totalMemory) : - javaPeer(_obj.NewWeakGlobalRef(_env)), - pixelRatio(_pixelRatio), - availableProcessors(_availableProcessors), - totalMemory(_totalMemory), - threadPool(sharedThreadPool()) { +NativeMapView::NativeMapView(jni::JNIEnv& _env, + jni::Object _obj, + jni::Object jFileSource, + jni::jfloat _pixelRatio, + jni::String _programCacheDir, + jni::jint _availableProcessors, + jni::jlong _totalMemory) + : javaPeer(_obj.NewWeakGlobalRef(_env)), + pixelRatio(_pixelRatio), + availableProcessors(_availableProcessors), + totalMemory(_totalMemory), + threadPool(sharedThreadPool()) { // Get a reference to the JavaVM for callbacks if (_env.GetJavaVM(&vm) < 0) { @@ -65,8 +70,9 @@ NativeMapView::NativeMapView(jni::JNIEnv& _env, jni::Object _obj, // Create the core map map = std::make_unique( *this, mbgl::Size{ static_cast(width), static_cast(height) }, - pixelRatio, mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource) - , *threadPool, MapMode::Continuous); + pixelRatio, mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource), *threadPool, + MapMode::Continuous, GLContextMode::Unique, ConstrainMode::HeightOnly, + ViewportMode::Default, jni::Make(_env, _programCacheDir)); //Calculate a fitting cache size based on device parameters float zoomFactor = map->getMaxZoom() - map->getMinZoom() + 1; @@ -1398,7 +1404,7 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { // Register the peer jni::RegisterNativePeer(env, NativeMapView::javaClass, "nativePtr", - std::make_unique, jni::Object, jni::jfloat, jni::jint, jni::jlong>, + std::make_unique, jni::Object, jni::jfloat, jni::String, jni::jint, jni::jlong>, "nativeInitialize", "nativeDestroy", METHOD(&NativeMapView::render, "nativeRender"), diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index 4afea036bf..08133b85cd 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -40,7 +40,13 @@ public: static void registerNative(jni::JNIEnv&); - NativeMapView(jni::JNIEnv&, jni::Object, jni::Object, jni::jfloat, jni::jint, jni::jlong); + NativeMapView(jni::JNIEnv&, + jni::Object, + jni::Object, + jni::jfloat pixelRatio, + jni::String programCacheDir, + jni::jint availableProcessors, + jni::jlong totalMemory); virtual ~NativeMapView(); diff --git a/proto/binary_program.proto b/proto/binary_program.proto new file mode 100644 index 0000000000..9d06a209c3 --- /dev/null +++ b/proto/binary_program.proto @@ -0,0 +1,18 @@ +// Protocol Version 1 + +package mapboxgl.binary_program; + +option optimize_for = LITE_RUNTIME; + +message binding { + required string name = 1; + required uint32 value = 2; +} + +message binary_program { + required uint32 format = 1; + required bytes code = 2; + repeated binding attribute = 3; + repeated binding uniform = 4; + optional string identifier = 5; +} diff --git a/src/mbgl/gl/attribute.hpp b/src/mbgl/gl/attribute.hpp index 43e2c2d794..e23c4f9e47 100644 --- a/src/mbgl/gl/attribute.hpp +++ b/src/mbgl/gl/attribute.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace mbgl { @@ -241,16 +242,26 @@ public: using VariableBindings = IndexedTuple< TypeList, TypeList...>>; + using NamedLocations = std::vector>; using Vertex = detail::Vertex; template static constexpr std::size_t Index = TypeIndex::value; - static Locations locations(const ProgramID& id) { + static Locations bindLocations(const ProgramID& id) { return Locations { bindAttributeLocation(id, Index, As::name())... }; } + template + static Locations loadNamedLocations(const Program& program) { + return Locations{ program.attributeLocation(As::name())... }; + } + + static NamedLocations getNamedLocations(const Locations& locations) { + return NamedLocations{ { As::name(), locations.template get() }... }; + } + template static Bindings allVariableBindings(const VertexBuffer& buffer) { return Bindings { As::variableBinding(buffer, Index)... }; diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index 9c2031ccfd..97ebf687f9 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,8 @@ static_assert(std::is_same, GLenum>::value static_assert(underlying_type(TextureFormat::RGBA) == GL_RGBA, "OpenGL type mismatch"); static_assert(underlying_type(TextureFormat::Alpha) == GL_ALPHA, "OpenGL type mismatch"); +static_assert(std::is_same::value, "OpenGL type mismatch"); + Context::~Context() { reset(); } @@ -72,9 +75,27 @@ UniqueProgram Context::createProgram(ShaderID vertexShader, ShaderID fragmentSha return result; } +#if MBGL_HAS_BINARY_PROGRAMS +UniqueProgram Context::createProgram(BinaryProgramFormat binaryFormat, + const std::string& binaryProgram) { + UniqueProgram result{ MBGL_CHECK_ERROR(glCreateProgram()), { this } }; + MBGL_CHECK_ERROR(ProgramBinary(result, static_cast(binaryFormat), binaryProgram.data(), + static_cast(binaryProgram.size()))); + verifyProgramLinkage(result); + return result; +} +#else +UniqueProgram Context::createProgram(BinaryProgramFormat, const std::string&) { + throw std::runtime_error("binary programs are not supported"); +} +#endif + void Context::linkProgram(ProgramID program_) { MBGL_CHECK_ERROR(glLinkProgram(program_)); + verifyProgramLinkage(program_); +} +void Context::verifyProgramLinkage(ProgramID program_) { GLint status; MBGL_CHECK_ERROR(glGetProgramiv(program_, GL_LINK_STATUS, &status)); if (status == GL_TRUE) { @@ -129,6 +150,34 @@ bool Context::supportsVertexArrays() const { !disableVAOExtension; } +#if MBGL_HAS_BINARY_PROGRAMS +bool Context::supportsProgramBinaries() const { + return gl::ProgramBinary && gl::GetProgramBinary; +} + +optional> +Context::getBinaryProgram(ProgramID program_) const { + if (!supportsProgramBinaries()) { + return {}; + } + GLint binaryLength; + MBGL_CHECK_ERROR(glGetProgramiv(program_, GL_PROGRAM_BINARY_LENGTH, &binaryLength)); + std::string binary; + binary.resize(binaryLength); + GLenum binaryFormat; + MBGL_CHECK_ERROR(GetProgramBinary(program_, binaryLength, &binaryLength, &binaryFormat, + const_cast(binary.data()))); + if (size_t(binaryLength) != binary.size()) { + return {}; + } + return { { binaryFormat, std::move(binary) } }; +} +#else +optional> Context::getBinaryProgram(ProgramID) const { + return {}; +} +#endif + UniqueVertexArray Context::createVertexArray() { assert(supportsVertexArrays()); VertexArrayID id = 0; diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 9d3ecec662..fd6683a0cc 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -37,12 +38,21 @@ public: UniqueShader createShader(ShaderType type, const std::string& source); UniqueProgram createProgram(ShaderID vertexShader, ShaderID fragmentShader); + UniqueProgram createProgram(BinaryProgramFormat binaryFormat, const std::string& binaryProgram); + void verifyProgramLinkage(ProgramID); void linkProgram(ProgramID); UniqueTexture createTexture(); bool supportsVertexArrays() const; UniqueVertexArray createVertexArray(); +#if MBGL_HAS_BINARY_PROGRAMS + bool supportsProgramBinaries() const; +#else + constexpr bool supportsProgramBinaries() const { return false; } +#endif + optional> getBinaryProgram(ProgramID) const; + template VertexBuffer createVertexBuffer(VertexVector&& v) { return VertexBuffer { diff --git a/src/mbgl/gl/features.hpp b/src/mbgl/gl/features.hpp new file mode 100644 index 0000000000..04dc4fa02e --- /dev/null +++ b/src/mbgl/gl/features.hpp @@ -0,0 +1,7 @@ +#pragma once + +#if __APPLE__ + #define MBGL_HAS_BINARY_PROGRAMS 0 +#else + #define MBGL_HAS_BINARY_PROGRAMS 1 +#endif \ No newline at end of file diff --git a/src/mbgl/gl/program.hpp b/src/mbgl/gl/program.hpp index fa0470796e..7d7fca5263 100644 --- a/src/mbgl/gl/program.hpp +++ b/src/mbgl/gl/program.hpp @@ -24,11 +24,29 @@ public: using AttributeBindings = typename Attributes::Bindings; Program(Context& context, const std::string& vertexSource, const std::string& fragmentSource) - : vertexShader(context.createShader(ShaderType::Vertex, vertexSource)), - fragmentShader(context.createShader(ShaderType::Fragment, fragmentSource)), - program(context.createProgram(vertexShader, fragmentShader)), - attributeLocations(Attributes::locations(program)), - uniformsState((context.linkProgram(program), Uniforms::state(program))) {} + : program( + context.createProgram(context.createShader(ShaderType::Vertex, vertexSource), + context.createShader(ShaderType::Fragment, fragmentSource))), + attributeLocations(Attributes::bindLocations(program)), + uniformsState((context.linkProgram(program), Uniforms::bindLocations(program))) { + } + + template + Program(Context& context, const BinaryProgram& binaryProgram) + : program(context.createProgram(binaryProgram.format(), binaryProgram.code())), + attributeLocations(Attributes::loadNamedLocations(binaryProgram)), + uniformsState(Uniforms::loadNamedLocations(binaryProgram)) { + } + + template + optional get(Context& context, const std::string& identifier) const { + if (auto binaryProgram = context.getBinaryProgram(program)) { + return BinaryProgram{ binaryProgram->first, std::move(binaryProgram->second), + identifier, Attributes::getNamedLocations(attributeLocations), + Uniforms::getNamedLocations(uniformsState) }; + } + return {}; + } template void draw(Context& context, @@ -64,8 +82,6 @@ public: } private: - UniqueShader vertexShader; - UniqueShader fragmentShader; UniqueProgram program; typename Attributes::Locations attributeLocations; diff --git a/src/mbgl/gl/program_binary.cpp b/src/mbgl/gl/program_binary.cpp new file mode 100644 index 0000000000..ad147c819f --- /dev/null +++ b/src/mbgl/gl/program_binary.cpp @@ -0,0 +1,24 @@ +#include + +#if MBGL_HAS_BINARY_PROGRAMS + +namespace mbgl { +namespace gl { + +ExtensionFunction< + void(GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, GLvoid* binary)> + GetProgramBinary({ + { "GL_OES_get_program_binary", "glGetProgramBinaryOES" }, + { "GL_ARB_get_program_binary", "glGetProgramBinary" }, + }); + +ExtensionFunction + ProgramBinary({ + { "GL_OES_get_program_binary", "glProgramBinaryOES" }, + { "GL_ARB_get_program_binary", "glProgramBinary" }, + }); + +} // namespace gl +} // namespace mbgl + +#endif diff --git a/src/mbgl/gl/program_binary.hpp b/src/mbgl/gl/program_binary.hpp new file mode 100644 index 0000000000..e888ed3d4a --- /dev/null +++ b/src/mbgl/gl/program_binary.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +#if MBGL_HAS_BINARY_PROGRAMS + +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF + +namespace mbgl { +namespace gl { + +extern ExtensionFunction + GetProgramBinary; + +extern ExtensionFunction + ProgramBinary; + +} // namespace gl +} // namespace mbgl + +#endif diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index 565ca5754f..7d436693c9 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -73,5 +73,7 @@ constexpr bool operator!=(const PixelStorageType& a, const PixelStorageType& b) #endif // MBGL_USE_GLES2 +using BinaryProgramFormat = uint32_t; + } // namespace gl } // namespace mbgl diff --git a/src/mbgl/gl/uniform.hpp b/src/mbgl/gl/uniform.hpp index 92136b61c2..34a32aeee9 100644 --- a/src/mbgl/gl/uniform.hpp +++ b/src/mbgl/gl/uniform.hpp @@ -6,6 +6,7 @@ #include #include +#include #include namespace mbgl { @@ -66,11 +67,21 @@ public: using Types = TypeList; using State = IndexedTuple, TypeList>; using Values = IndexedTuple, TypeList>; + using NamedLocations = std::vector>; - static State state(const ProgramID& id) { + static State bindLocations(const ProgramID& id) { return State { { uniformLocation(id, Us::name()) }... }; } + template + static State loadNamedLocations(const Program& program) { + return State{ { program.uniformLocation(Us::name()) }... }; + } + + static NamedLocations getNamedLocations(const State& state) { + return NamedLocations{ { Us::name(), state.template get().location }... }; + } + static void bind(State& state, Values&& values) { util::ignore({ (state.template get() = values.template get(), 0)... }); } diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index f4e994c931..aca1b77e7a 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -56,7 +56,8 @@ public: MapMode, GLContextMode, ConstrainMode, - ViewportMode); + ViewportMode, + const std::string& programCacheDir); void onSourceAttributionChanged(style::Source&, const std::string&) override; void onUpdate(Update) override; @@ -80,6 +81,7 @@ public: const MapMode mode; const GLContextMode contextMode; const float pixelRatio; + const std::string programCacheDir; MapDebugOptions debugOptions { MapDebugOptions::NoDebug }; @@ -111,7 +113,8 @@ Map::Map(Backend& backend, MapMode mapMode, GLContextMode contextMode, ConstrainMode constrainMode, - ViewportMode viewportMode) + ViewportMode viewportMode, + const std::string& programCacheDir) : impl(std::make_unique(*this, backend, pixelRatio, @@ -120,7 +123,8 @@ Map::Map(Backend& backend, mapMode, contextMode, constrainMode, - viewportMode)) { + viewportMode, + programCacheDir)) { impl->transform.resize(size); } @@ -132,7 +136,8 @@ Map::Impl::Impl(Map& map_, MapMode mode_, GLContextMode contextMode_, ConstrainMode constrainMode_, - ViewportMode viewportMode_) + ViewportMode viewportMode_, + const std::string& programCacheDir_) : map(map_), backend(backend_), fileSource(fileSource_), @@ -143,6 +148,7 @@ Map::Impl::Impl(Map& map_, mode(mode_), contextMode(contextMode_), pixelRatio(pixelRatio_), + programCacheDir(programCacheDir_), annotationManager(std::make_unique(pixelRatio)), asyncInvalidate([this] { if (mode == MapMode::Continuous) { @@ -259,7 +265,7 @@ void Map::Impl::render(View& view) { updateFlags = Update::Nothing; if (!painter) { - painter = std::make_unique(backend.getContext(), transform.getState(), pixelRatio); + painter = std::make_unique(backend.getContext(), transform.getState(), pixelRatio, programCacheDir); } if (mode == MapMode::Continuous) { diff --git a/src/mbgl/programs/binary_program.cpp b/src/mbgl/programs/binary_program.cpp new file mode 100644 index 0000000000..3b37cfa442 --- /dev/null +++ b/src/mbgl/programs/binary_program.cpp @@ -0,0 +1,117 @@ +#include + +#include +#include + +template +static std::pair parseBinding(protozero::pbf_reader&& pbf) { + bool hasName = false, hasValue = false; + std::pair binding; + while (pbf.next()) { + switch (pbf.tag()) { + case 1: // name + binding.first = pbf.get_string(); + hasName = true; + break; + case 2: // value + binding.second = pbf.get_uint32(); + hasValue = true; + break; + default: + pbf.skip(); + break; + } + } + if (!hasName || !hasValue) { + throw std::runtime_error("BinaryProgram binding is missing required fields"); + } + return binding; +} + +namespace mbgl { + +BinaryProgram::BinaryProgram(std::string&& data) { + bool hasFormat = false, hasCode = false; + protozero::pbf_reader pbf(data); + while (pbf.next()) { + switch (pbf.tag()) { + case 1: // format + binaryFormat = pbf.get_uint32(); + hasFormat = true; + break; + case 2: // code + binaryCode = pbf.get_bytes(); + hasCode = true; + break; + case 3: // variable + attributes.emplace_back(parseBinding(pbf.get_message())); + break; + case 4: // uniform + uniforms.emplace_back(parseBinding(pbf.get_message())); + break; + case 5: // identifier + default: + binaryIdentifier = pbf.get_string(); + break; + } + } + + if (!hasFormat || !hasCode) { + throw std::runtime_error("BinaryProgram is missing required fields"); + } +} + +BinaryProgram::BinaryProgram( + gl::BinaryProgramFormat binaryFormat_, + std::string&& binaryCode_, + const std::string& binaryIdentifier_, + std::vector>&& attributes_, + std::vector>&& uniforms_) + : binaryFormat(binaryFormat_), + binaryCode(std::move(binaryCode_)), + binaryIdentifier(binaryIdentifier_), + attributes(std::move(attributes_)), + uniforms(std::move(uniforms_)) { +} + +std::string BinaryProgram::serialize() const { + std::string data; + data.reserve(32 + binaryCode.size() + uniforms.size() * 32 + attributes.size() * 32); + protozero::pbf_writer pbf(data); + pbf.add_uint32(1 /* format */, binaryFormat); + pbf.add_bytes(2 /* code */, binaryCode.data(), binaryCode.size()); + for (const auto& binding : attributes) { + protozero::pbf_writer pbf_binding(pbf, 3 /* attribute */); + pbf_binding.add_string(1 /* name */, binding.first); + pbf_binding.add_uint32(2 /* value */, binding.second); + } + for (const auto& binding : uniforms) { + protozero::pbf_writer pbf_binding(pbf, 4 /* uniform */); + pbf_binding.add_string(1 /* name */, binding.first); + pbf_binding.add_uint32(2 /* value */, binding.second); + } + if (!binaryIdentifier.empty()) { + pbf.add_string(5 /* identifier */, binaryIdentifier); + } + return data; +} + +gl::AttributeLocation BinaryProgram::attributeLocation(const std::string& name) const { + for (const auto& pair : attributes) { + if (pair.first == name) { + return pair.second; + } + } + return {}; +} + +gl::UniformLocation BinaryProgram::uniformLocation(const std::string& name) const { + for (const auto& pair : uniforms) { + if (pair.first == name) { + return pair.second; + } + } + return {}; +} + +} // namespace mbgl diff --git a/src/mbgl/programs/binary_program.hpp b/src/mbgl/programs/binary_program.hpp new file mode 100644 index 0000000000..8ff3863dc1 --- /dev/null +++ b/src/mbgl/programs/binary_program.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include +#include + +namespace mbgl { + +class BinaryProgram { +public: + // Initialize a BinaryProgram object from a serialized represenation. + BinaryProgram(std::string&& data); + + BinaryProgram(gl::BinaryProgramFormat, + std::string&& binaryCode, + const std::string& binaryIdentifier, + std::vector>&&, + std::vector>&&); + + std::string serialize() const; + + gl::BinaryProgramFormat format() const { + return binaryFormat; + } + const std::string& code() const { + return binaryCode; + } + const std::string& identifier() const { + return binaryIdentifier; + } + gl::AttributeLocation attributeLocation(const std::string& name) const; + gl::UniformLocation uniformLocation(const std::string& name) const; + +private: + gl::BinaryProgramFormat binaryFormat = 0; + std::string binaryCode; + std::string binaryIdentifier; + std::vector> attributes; + std::vector> uniforms; +}; + +} // namespace mbgl diff --git a/src/mbgl/programs/program.hpp b/src/mbgl/programs/program.hpp index 8437e3a651..8925bc75d6 100644 --- a/src/mbgl/programs/program.hpp +++ b/src/mbgl/programs/program.hpp @@ -1,9 +1,13 @@ #pragma once #include +#include +#include #include +#include #include #include +#include namespace mbgl { @@ -30,10 +34,56 @@ public: ProgramType program; Program(gl::Context& context, const ProgramParameters& programParameters) - : program(context, - shaders::vertexSource(programParameters, Shaders::vertexSource), - shaders::fragmentSource(programParameters, Shaders::fragmentSource)) - {} + : program([&] { +#if MBGL_HAS_BINARY_PROGRAMS + if (!programParameters.cacheDir.empty() && context.supportsProgramBinaries()) { + const std::string vertexSource = + shaders::vertexSource(programParameters, Shaders::vertexSource); + const std::string fragmentSource = + shaders::fragmentSource(programParameters, Shaders::fragmentSource); + const std::string cachePath = + shaders::programCachePath(programParameters, Shaders::name); + const std::string identifier = + shaders::programIdentifier(vertexSource, fragmentSource); + + try { + if (auto cachedBinaryProgram = util::readFile(cachePath)) { + const BinaryProgram binaryProgram(std::move(*cachedBinaryProgram)); + if (binaryProgram.identifier() == identifier) { + return ProgramType{ context, binaryProgram }; + } else { + Log::Warning(Event::OpenGL, + "Cached program %s changed. Recompilation required.", + Shaders::name); + } + } + } catch (std::runtime_error& error) { + Log::Warning(Event::OpenGL, "Could not load cached program: %s", + error.what()); + } + + // Compile the shader + ProgramType result{ context, vertexSource, fragmentSource }; + + try { + if (const auto binaryProgram = + result.template get(context, identifier)) { + util::write_file(cachePath, binaryProgram->serialize()); + Log::Warning(Event::OpenGL, "Caching program in: %s", cachePath.c_str()); + } + } catch (std::runtime_error& error) { + Log::Warning(Event::OpenGL, "Failed to cache program: %s", error.what()); + } + + return std::move(result); + } +#endif + return ProgramType{ + context, shaders::vertexSource(programParameters, Shaders::vertexSource), + shaders::fragmentSource(programParameters, Shaders::fragmentSource) + }; + }()) { + } template void draw(gl::Context& context, diff --git a/src/mbgl/programs/program_parameters.hpp b/src/mbgl/programs/program_parameters.hpp index ad8cbf1bf8..b91b41f358 100644 --- a/src/mbgl/programs/program_parameters.hpp +++ b/src/mbgl/programs/program_parameters.hpp @@ -1,15 +1,20 @@ #pragma once +#include + namespace mbgl { class ProgramParameters { public: - ProgramParameters(float pixelRatio_ = 1.0, bool overdraw_ = false) - : pixelRatio(pixelRatio_), - overdraw(overdraw_) {} + ProgramParameters(float pixelRatio_ = 1.0, + bool overdraw_ = false, + const std::string& cacheDir_ = "") + : pixelRatio(pixelRatio_), overdraw(overdraw_), cacheDir(cacheDir_) { + } - float pixelRatio; - bool overdraw; + const float pixelRatio; + const bool overdraw; + const std::string cacheDir; }; } // namespace mbgl diff --git a/src/mbgl/programs/programs.hpp b/src/mbgl/programs/programs.hpp index 742c5a221b..dbf5b9e87d 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -26,8 +26,8 @@ public: symbolIcon(context, programParameters), symbolIconSDF(context, programParameters), symbolGlyph(context, programParameters), - debug(context, ProgramParameters(programParameters.pixelRatio, false)), - collisionBox(context, ProgramParameters(programParameters.pixelRatio, false)) { + debug(context, ProgramParameters(programParameters.pixelRatio, false, programParameters.cacheDir)), + collisionBox(context, ProgramParameters(programParameters.pixelRatio, false, programParameters.cacheDir)) { } CircleProgram circle; diff --git a/src/mbgl/renderer/painter.cpp b/src/mbgl/renderer/painter.cpp index 27d24d14a9..754959438b 100644 --- a/src/mbgl/renderer/painter.cpp +++ b/src/mbgl/renderer/painter.cpp @@ -33,6 +33,8 @@ #include +#include + #include #include #include @@ -77,7 +79,10 @@ static gl::VertexVector rasterVertices() { return result; } -Painter::Painter(gl::Context& context_, const TransformState& state_, float pixelRatio) +Painter::Painter(gl::Context& context_, + const TransformState& state_, + float pixelRatio, + const std::string& programCacheDir) : context(context_), state(state_), tileVertexBuffer(context.createVertexBuffer(tileVertices())), @@ -91,12 +96,11 @@ Painter::Painter(gl::Context& context_, const TransformState& state_, float pixe gl::debugging::enable(); - ProgramParameters programParameters{ pixelRatio, false }; - programs = std::make_unique(context, programParameters); + programs = std::make_unique(context, + ProgramParameters{ pixelRatio, false, programCacheDir }); #ifndef NDEBUG - - ProgramParameters programParametersOverdraw{ pixelRatio, true }; - overdrawPrograms = std::make_unique(context, programParametersOverdraw); + overdrawPrograms = + std::make_unique(context, ProgramParameters{ pixelRatio, true, programCacheDir }); #endif } diff --git a/src/mbgl/renderer/painter.hpp b/src/mbgl/renderer/painter.hpp index 91f329a6eb..3dcc1d5d46 100644 --- a/src/mbgl/renderer/painter.hpp +++ b/src/mbgl/renderer/painter.hpp @@ -68,7 +68,7 @@ struct FrameData { class Painter : private util::noncopyable { public: - Painter(gl::Context&, const TransformState&, float pixelRatio); + Painter(gl::Context&, const TransformState&, float pixelRatio, const std::string& programCacheDir); ~Painter(); void render(const style::Style&, diff --git a/src/mbgl/shaders/shaders.cpp b/src/mbgl/shaders/shaders.cpp index f7f5b4d44f..03d796edba 100644 --- a/src/mbgl/shaders/shaders.cpp +++ b/src/mbgl/shaders/shaders.cpp @@ -4,6 +4,7 @@ #include #include +#include namespace mbgl { namespace shaders { @@ -29,5 +30,18 @@ std::string vertexSource(const ProgramParameters& parameters, const char* vertex return pixelRatioDefine(parameters) + vertexPrelude + vertexSource; } +std::string programCachePath(const ProgramParameters& parameters, const char* name) { + return parameters.cacheDir + "/com.mapbox.gl.shader." + name + + (parameters.overdraw ? ".overdraw.pbf" : ".pbf"); +} + +std::string programIdentifier(const std::string& vertexSource, const std::string& fragmentSource) { + std::ostringstream ss; + ss << std::setfill('0') << std::setw(sizeof(size_t) * 2) << std::hex; + ss << std::hash()(vertexSource); + ss << std::hash()(fragmentSource); + return ss.str(); +} + } // namespace shaders } // namespace mbgl diff --git a/src/mbgl/shaders/shaders.hpp b/src/mbgl/shaders/shaders.hpp index e2912c5688..126c64bb9e 100644 --- a/src/mbgl/shaders/shaders.hpp +++ b/src/mbgl/shaders/shaders.hpp @@ -10,6 +10,8 @@ namespace shaders { std::string fragmentSource(const ProgramParameters&, const char* fragmentSource); std::string vertexSource(const ProgramParameters&, const char* vertexSource); +std::string programCachePath(const ProgramParameters&, const char* name); +std::string programIdentifier(const std::string& vertexSource, const std::string& fragmentSource); } // namespace shaders } // namespace mbgl diff --git a/src/mbgl/util/io.cpp b/src/mbgl/util/io.cpp index f37ce09cff..9adc3b8988 100644 --- a/src/mbgl/util/io.cpp +++ b/src/mbgl/util/io.cpp @@ -32,6 +32,16 @@ std::string read_file(const std::string &filename) { } } +optional readFile(const std::string &filename) { + std::ifstream file(filename); + if (file.good()) { + std::stringstream data; + data << file.rdbuf(); + return data.str(); + } + return {}; +} + void deleteFile(const std::string& filename) { const int ret = unlink(filename.c_str()); if (ret == -1) { diff --git a/src/mbgl/util/io.hpp b/src/mbgl/util/io.hpp index 795a465328..847271acf0 100644 --- a/src/mbgl/util/io.hpp +++ b/src/mbgl/util/io.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include @@ -15,6 +17,7 @@ struct IOException : std::runtime_error { void write_file(const std::string &filename, const std::string &data); std::string read_file(const std::string &filename); +optional readFile(const std::string &filename); void deleteFile(const std::string& filename); } // namespace util diff --git a/test/programs/binary_program.test.cpp b/test/programs/binary_program.test.cpp new file mode 100644 index 0000000000..ce544e7652 --- /dev/null +++ b/test/programs/binary_program.test.cpp @@ -0,0 +1,39 @@ +#include + +#include + +using namespace mbgl; + +TEST(BinaryProgram, ObtainValues) { + const BinaryProgram binaryProgram{ 42, + "binary code", + "identifier", + { { "a_pos", 1 }, { "a_data", 4 } }, + { { "u_world", 1 }, { "u_ratio", 3 } } }; + + EXPECT_EQ(42u, binaryProgram.format()); + EXPECT_EQ("binary code", binaryProgram.code()); + EXPECT_EQ("identifier", binaryProgram.identifier()); + EXPECT_EQ(1, binaryProgram.attributeLocation("a_pos")); + EXPECT_EQ(0, binaryProgram.attributeLocation("u_world")); + EXPECT_EQ(4, binaryProgram.attributeLocation("a_data")); + EXPECT_EQ(1, binaryProgram.uniformLocation("u_world")); + EXPECT_EQ(3, binaryProgram.uniformLocation("u_ratio")); + EXPECT_EQ(0, binaryProgram.uniformLocation("a_data")); + + auto serialized = binaryProgram.serialize(); + + const BinaryProgram binaryProgram2(std::move(serialized)); + + EXPECT_EQ(42u, binaryProgram2.format()); + EXPECT_EQ("binary code", binaryProgram2.code()); + EXPECT_EQ("identifier", binaryProgram2.identifier()); + EXPECT_EQ(1, binaryProgram2.attributeLocation("a_pos")); + EXPECT_EQ(0, binaryProgram2.attributeLocation("u_world")); + EXPECT_EQ(4, binaryProgram2.attributeLocation("a_data")); + EXPECT_EQ(1, binaryProgram2.uniformLocation("u_world")); + EXPECT_EQ(3, binaryProgram2.uniformLocation("u_ratio")); + EXPECT_EQ(0, binaryProgram2.uniformLocation("a_data")); + + EXPECT_THROW(BinaryProgram(""), std::runtime_error); +} -- cgit v1.2.1