[core] Dynamic program compilation for data-driven properties

This commit is contained in:
John Firebaugh
2017-06-05 12:15:27 -07:00
parent 212ae5273d
commit d618d4a274
19 changed files with 148 additions and 69 deletions

View File

@@ -44,7 +44,7 @@ public:
GLContextMode contextMode = GLContextMode::Unique,
ConstrainMode constrainMode = ConstrainMode::HeightOnly,
ViewportMode viewportMode = ViewportMode::Default,
const std::string& programCacheDir = "");
const optional<std::string>& programCacheDir = {});
~Map();
// Register a callback that will get called (on the render thread) when all resources have

View File

@@ -50,7 +50,8 @@ public:
const char* vertexSource_,
const char* fragmentSource_) {
#if MBGL_HAS_BINARY_PROGRAMS
if (!programParameters.cacheDir.empty() && context.supportsProgramBinaries()) {
optional<std::string> cachePath = programParameters.cachePath(name);
if (cachePath && context.supportsProgramBinaries()) {
const std::string vertexSource =
shaders::vertexSource(programParameters, vertexSource_);
const std::string fragmentSource =
@@ -58,10 +59,8 @@ public:
const std::string identifier =
shaders::programIdentifier(vertexSource, fragmentSource_);
const std::string cachePath = programParameters.cachePath(name);
try {
if (auto cachedBinaryProgram = util::readFile(cachePath)) {
if (auto cachedBinaryProgram = util::readFile(*cachePath)) {
const BinaryProgram binaryProgram(std::move(*cachedBinaryProgram));
if (binaryProgram.identifier() == identifier) {
return Program { context, binaryProgram };
@@ -82,8 +81,8 @@ public:
try {
if (const auto binaryProgram =
result.template get<BinaryProgram>(context, identifier)) {
util::write_file(cachePath, binaryProgram->serialize());
Log::Warning(Event::OpenGL, "Caching program in: %s", cachePath.c_str());
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());

View File

@@ -58,7 +58,7 @@ public:
GLContextMode,
ConstrainMode,
ViewportMode,
const std::string& programCacheDir);
optional<std::string> programCacheDir);
void onSourceChanged(style::Source&) override;
void onUpdate(Update) override;
@@ -83,7 +83,7 @@ public:
const MapMode mode;
const GLContextMode contextMode;
const float pixelRatio;
const std::string programCacheDir;
const optional<std::string> programCacheDir;
MapDebugOptions debugOptions { MapDebugOptions::NoDebug };
@@ -116,7 +116,7 @@ Map::Map(Backend& backend,
GLContextMode contextMode,
ConstrainMode constrainMode,
ViewportMode viewportMode,
const std::string& programCacheDir)
const optional<std::string>& programCacheDir)
: impl(std::make_unique<Impl>(*this,
backend,
pixelRatio,
@@ -139,7 +139,7 @@ Map::Impl::Impl(Map& map_,
GLContextMode contextMode_,
ConstrainMode constrainMode_,
ViewportMode viewportMode_,
const std::string& programCacheDir_)
optional<std::string> programCacheDir_)
: map(map_),
observer(backend_),
backend(backend_),

View File

@@ -9,18 +9,21 @@
#include <mbgl/shaders/shaders.hpp>
#include <mbgl/util/io.hpp>
#include <unordered_map>
namespace mbgl {
template <class Shaders,
class Primitive,
class LayoutAttrs,
class Uniforms,
class PaintProperties>
class PaintProps>
class Program {
public:
using LayoutAttributes = LayoutAttrs;
using LayoutVertex = typename LayoutAttributes::Vertex;
using PaintProperties = PaintProps;
using PaintPropertyBinders = typename PaintProperties::Binders;
using PaintAttributes = typename PaintPropertyBinders::Attributes;
using Attributes = gl::ConcatenateAttributes<LayoutAttributes, PaintAttributes>;
@@ -71,4 +74,34 @@ public:
}
};
template <class Program>
class ProgramMap {
public:
using PaintProperties = typename Program::PaintProperties;
using PaintPropertyBinders = typename Program::PaintPropertyBinders;
using Bitset = typename PaintPropertyBinders::Bitset;
ProgramMap(gl::Context& context_, ProgramParameters parameters_)
: context(context_),
parameters(std::move(parameters_)) {
}
Program& get(const typename PaintProperties::Evaluated& currentProperties) {
Bitset bits = PaintPropertyBinders::constants(currentProperties);
auto it = programs.find(bits);
if (it != programs.end()) {
return it->second;
}
return programs.emplace(std::piecewise_construct,
std::forward_as_tuple(bits),
std::forward_as_tuple(context,
parameters.withAdditionalDefines(PaintPropertyBinders::defines(currentProperties)))).first->second;
}
private:
gl::Context& context;
ProgramParameters parameters;
std::unordered_map<Bitset, Program> programs;
};
} // namespace mbgl

View File

@@ -7,7 +7,7 @@ namespace mbgl {
ProgramParameters::ProgramParameters(const float pixelRatio,
const bool overdraw,
std::string cacheDir_)
optional<std::string> cacheDir_)
: defines([&] {
std::ostringstream ss;
ss.imbue(std::locale("C"));
@@ -18,15 +18,31 @@ ProgramParameters::ProgramParameters(const float pixelRatio,
}
return ss.str();
}()),
hash(std::hash<std::string>()(defines)),
cacheDir(std::move(cacheDir_)) {
}
std::string ProgramParameters::cachePath(const char* name) const {
std::ostringstream ss;
ss << cacheDir << "/com.mapbox.gl.shader." << name << "." << std::setfill('0')
<< std::setw(sizeof(size_t) * 2) << std::hex << hash << ".pbf";
return ss.str();
const std::string& ProgramParameters::getDefines() const {
return defines;
}
optional<std::string> ProgramParameters::cachePath(const char* name) const {
if (!cacheDir) {
return {};
} else {
std::ostringstream ss;
ss << *cacheDir << "/com.mapbox.gl.shader." << name << "." << std::setfill('0')
<< std::setw(sizeof(size_t) * 2) << std::hex << std::hash<std::string>()(defines) << ".pbf";
return ss.str();
}
}
ProgramParameters ProgramParameters::withAdditionalDefines(const std::vector<std::string>& additionalDefines) const {
ProgramParameters result(*this);
for (const auto& define : additionalDefines) {
result.defines += define;
result.defines += "\n";
}
return result;
}
} // namespace mbgl

View File

@@ -1,20 +1,24 @@
#pragma once
#include <mbgl/util/optional.hpp>
#include <string>
#include <vector>
namespace mbgl {
class ProgramParameters {
public:
ProgramParameters(float pixelRatio, bool overdraw, std::string cacheDir);
ProgramParameters(float pixelRatio, bool overdraw, optional<std::string> cacheDir);
const std::string defines;
const std::string& getDefines() const;
optional<std::string> cachePath(const char* name) const;
std::string cachePath(const char* name) const;
ProgramParameters withAdditionalDefines(const std::vector<std::string>& defines) const;
private:
const std::size_t hash;
const std::string cacheDir;
std::string defines;
optional<std::string> cacheDir;
};
} // namespace mbgl

View File

@@ -35,21 +35,21 @@ public:
collisionBox(context, programParameters) {
}
CircleProgram circle;
ProgramMap<CircleProgram> circle;
ExtrusionTextureProgram extrusionTexture;
FillProgram fill;
FillExtrusionProgram fillExtrusion;
FillExtrusionPatternProgram fillExtrusionPattern;
FillPatternProgram fillPattern;
FillOutlineProgram fillOutline;
FillOutlinePatternProgram fillOutlinePattern;
LineProgram line;
LineSDFProgram lineSDF;
LinePatternProgram linePattern;
ProgramMap<FillProgram> fill;
ProgramMap<FillExtrusionProgram> fillExtrusion;
ProgramMap<FillExtrusionPatternProgram> fillExtrusionPattern;
ProgramMap<FillPatternProgram> fillPattern;
ProgramMap<FillOutlineProgram> fillOutline;
ProgramMap<FillOutlinePatternProgram> fillOutlinePattern;
ProgramMap<LineProgram> line;
ProgramMap<LineSDFProgram> lineSDF;
ProgramMap<LinePatternProgram> linePattern;
RasterProgram raster;
SymbolIconProgram symbolIcon;
SymbolSDFIconProgram symbolIconSDF;
SymbolSDFTextProgram symbolGlyph;
ProgramMap<SymbolIconProgram> symbolIcon;
ProgramMap<SymbolSDFIconProgram> symbolIconSDF;
ProgramMap<SymbolSDFTextProgram> symbolGlyph;
DebugProgram debug;
CollisionBoxProgram collisionBox;

View File

@@ -324,7 +324,7 @@ template <class Shaders,
class Primitive,
class LayoutAttrs,
class Uniforms,
class PaintProperties>
class PaintProps>
class SymbolProgram {
public:
using LayoutAttributes = LayoutAttrs;
@@ -332,6 +332,7 @@ public:
using LayoutAndSizeAttributes = gl::ConcatenateAttributes<LayoutAttributes, SymbolSizeAttributes>;
using PaintProperties = PaintProps;
using PaintPropertyBinders = typename PaintProperties::Binders;
using PaintAttributes = typename PaintPropertyBinders::Attributes;
using Attributes = gl::ConcatenateAttributes<LayoutAndSizeAttributes, PaintAttributes>;

View File

@@ -6,6 +6,8 @@
#include <mbgl/util/type_list.hpp>
#include <mbgl/renderer/paint_property_statistics.hpp>
#include <bitset>
namespace mbgl {
/*
@@ -54,8 +56,7 @@ std::array<float, N*2> zoomInterpolatedAttributeValue(const std::array<float, N>
* For _constant_ properties -- those whose value is a constant, or the constant
result of evaluating a camera function at a particular camera position -- we
don't need a vertex buffer, and can instead use a constant attribute binding
via the `glVertexAttrib*` family of functions.
don't need a vertex buffer, and instead use a uniform.
* For source functions, we use a vertex buffer with a single attribute value,
the evaluated result of the source function for the given feature.
* For composite functions, we use a vertex buffer with two attributes: min and
@@ -66,15 +67,8 @@ std::array<float, N*2> zoomInterpolatedAttributeValue(const std::array<float, N>
between the min and max value at the final displayed zoom level. The use of a
uniform allows us to cheaply update the value on every frame.
Note that the shader source is the same regardless of the strategy used to bind
the attribute -- in all cases the attribute is declared as a vec2, in order to
support composite min and max values (color attributes use a vec4 with special
packing). When the constant or source function strategies are used, the
interpolation uniform value is set to zero, and the second attribute element is
unused. This differs from the GL JS implementation, which dynamically generates
shader source based on the strategy used. We found that in WebGL, using
`glVertexAttrib*` was unnacceptably slow. Additionally, in GL Native we have
implemented binary shader caching, which works better if the shaders are constant.
Note that the shader source varies depending on whether we're using a uniform or
attribute. Like GL JS, we dynamically compile shaders at runtime to accomodate this.
*/
template <class T, class A>
class PaintPropertyBinder {
@@ -170,9 +164,13 @@ public:
return 0.0f;
}
T uniformValue(const PossiblyEvaluatedPropertyValue<T>&) const override {
// Uniform values for vertex attribute arrays are unused.
return {};
T uniformValue(const PossiblyEvaluatedPropertyValue<T>& currentValue) const override {
if (currentValue.isConstant()) {
return *currentValue.constant();
} else {
// Uniform values for vertex attribute arrays are unused.
return {};
}
}
private:
@@ -230,9 +228,13 @@ public:
return util::interpolationFactor(1.0f, std::get<0>(coveringRanges), currentZoom);
}
T uniformValue(const PossiblyEvaluatedPropertyValue<T>&) const override {
// Uniform values for vertex attribute arrays are unused.
return {};
T uniformValue(const PossiblyEvaluatedPropertyValue<T>& currentValue) const override {
if (currentValue.isConstant()) {
return *currentValue.constant();
} else {
// Uniform values for vertex attribute arrays are unused.
return {};
}
}
private:
@@ -342,6 +344,30 @@ public:
return binders.template get<P>()->statistics;
}
using Bitset = std::bitset<sizeof...(Ps)>;
template <class EvaluatedProperties>
static Bitset constants(const EvaluatedProperties& currentProperties) {
Bitset result;
util::ignore({
result.set(TypeIndex<Ps, Ps...>::value,
currentProperties.template get<Ps>().isConstant())...
});
return result;
}
template <class EvaluatedProperties>
static std::vector<std::string> defines(const EvaluatedProperties& currentProperties) {
std::vector<std::string> result;
util::ignore({
(result.push_back(currentProperties.template get<Ps>().isConstant()
? std::string("#define HAS_UNIFORM_") + Ps::Uniform::name()
: std::string()), 0)...
});
return result;
}
private:
Binders binders;
};

View File

@@ -93,7 +93,7 @@ static gl::VertexVector<ExtrusionTextureLayoutVertex> extrusionTextureVertices()
Painter::Painter(gl::Context& context_,
const TransformState& state_,
float pixelRatio,
const std::string& programCacheDir)
const optional<std::string>& programCacheDir)
: context(context_),
state(state_),
tileVertexBuffer(context.createVertexBuffer(tileVertices())),

View File

@@ -74,7 +74,7 @@ struct FrameData {
class Painter : private util::noncopyable {
public:
Painter(gl::Context&, const TransformState&, float pixelRatio, const std::string& programCacheDir);
Painter(gl::Context&, const TransformState&, float pixelRatio, const optional<std::string>& programCacheDir);
~Painter();
void render(const style::Style&,

View File

@@ -33,7 +33,7 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou
spriteAtlas->bind(true, context, 0);
for (const auto& tileID : util::tileCover(state, state.getIntegerZoom())) {
parameters.programs.fillPattern.draw(
parameters.programs.fillPattern.get(properties).draw(
context,
gl::Triangles(),
depthModeForSublayer(0, gl::DepthMode::ReadOnly),
@@ -58,7 +58,7 @@ void Painter::renderBackground(PaintParameters& parameters, const RenderBackgrou
}
} else {
for (const auto& tileID : util::tileCover(state, state.getIntegerZoom())) {
parameters.programs.fill.draw(
parameters.programs.fill.get(properties).draw(
context,
gl::Triangles(),
depthModeForSublayer(0, gl::DepthMode::ReadOnly),

View File

@@ -23,7 +23,7 @@ void Painter::renderCircle(PaintParameters& parameters,
const CirclePaintProperties::Evaluated& properties = layer.evaluated;
const bool scaleWithMap = properties.get<CirclePitchScale>() == CirclePitchScaleType::Map;
parameters.programs.circle.draw(
parameters.programs.circle.get(properties).draw(
context,
gl::Triangles(),
depthModeForSublayer(0, gl::DepthMode::ReadOnly),

View File

@@ -8,7 +8,7 @@ namespace mbgl {
void Painter::renderClippingMask(const UnwrappedTileID& tileID, const ClipID& clip) {
static const style::FillPaintProperties::Evaluated properties {};
static const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0);
programs->fill.draw(
programs->fill.get(properties).draw(
context,
gl::Triangles(),
gl::DepthMode::disabled(),

View File

@@ -38,7 +38,7 @@ void Painter::renderFill(PaintParameters& parameters,
const auto& drawMode,
const auto& indexBuffer,
const auto& segments) {
program.draw(
program.get(properties).draw(
context,
drawMode,
depthModeForSublayer(sublayer, gl::DepthMode::ReadWrite),
@@ -85,7 +85,7 @@ void Painter::renderFill(PaintParameters& parameters,
const auto& drawMode,
const auto& indexBuffer,
const auto& segments) {
program.draw(
program.get(properties).draw(
context,
drawMode,
depthModeForSublayer(sublayer, gl::DepthMode::ReadWrite),

View File

@@ -36,7 +36,7 @@ void Painter::renderFillExtrusion(PaintParameters& parameters,
spriteAtlas->bind(true, context, 0);
parameters.programs.fillExtrusionPattern.draw(
parameters.programs.fillExtrusionPattern.get(properties).draw(
context,
gl::Triangles(),
depthModeForSublayer(0, gl::DepthMode::ReadWrite),
@@ -62,7 +62,7 @@ void Painter::renderFillExtrusion(PaintParameters& parameters,
state.getZoom());
} else {
parameters.programs.fillExtrusion.draw(
parameters.programs.fillExtrusion.get(properties).draw(
context,
gl::Triangles(),
depthModeForSublayer(0, gl::DepthMode::ReadWrite),

View File

@@ -24,7 +24,7 @@ void Painter::renderLine(PaintParameters& parameters,
const LinePaintProperties::Evaluated& properties = layer.evaluated;
auto draw = [&] (auto& program, auto&& uniformValues) {
program.draw(
program.get(properties).draw(
context,
gl::Triangles(),
depthModeForSublayer(0, gl::DepthMode::ReadOnly),

View File

@@ -41,7 +41,7 @@ void Painter::renderSymbol(PaintParameters& parameters,
// We clip symbols to their tile extent in still mode.
const bool needsClipping = frame.mapMode == MapMode::Still;
program.draw(
program.get(paintProperties).draw(
context,
gl::Triangles(),
values_.pitchAlignment == AlignmentType::Map

View File

@@ -10,11 +10,11 @@ namespace mbgl {
namespace shaders {
std::string fragmentSource(const ProgramParameters& parameters, const char* fragmentSource) {
return parameters.defines + fragmentPrelude + fragmentSource;
return parameters.getDefines() + fragmentPrelude + fragmentSource;
}
std::string vertexSource(const ProgramParameters& parameters, const char* vertexSource) {
return parameters.defines + vertexPrelude + vertexSource;
return parameters.getDefines() + vertexPrelude + vertexSource;
}
std::string programIdentifier(const std::string& vertexSource, const std::string& fragmentSource) {