Add shader registry (#924)

This commit is contained in:
mwilsnd
2023-03-27 12:41:38 -05:00
committed by GitHub
parent 7c96e517c3
commit 1d8c4b527f
23 changed files with 416 additions and 11 deletions

View File

@@ -126,6 +126,8 @@ target_sources(
${PROJECT_SOURCE_DIR}/include/mbgl/gfx/renderable.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gfx/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gfx/rendering_stats.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gfx/shader_registry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gfx/shader.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/background_layer_factory.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/circle_layer_factory.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/fill_extrusion_layer_factory.hpp
@@ -349,6 +351,7 @@ target_sources(
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/renderbuffer.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/rendering_stats.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/shader_registry.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/stencil_mode.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/texture.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/types.hpp

View File

@@ -0,0 +1,49 @@
#pragma once
#include <string_view>
#include <type_traits>
namespace mbgl {
namespace gfx {
class Shader;
// Assert that a type is a valid shader for downcasting.
// A valid shader must:
// * Inherit gfx::Shader
// * Have properly called DECLARE_SHADER_TYPEINFO in the class body
// * Be a final class
template<typename T>
inline constexpr bool is_shader_v =
std::is_base_of_v<gfx::Shader, T> &&
std::is_same_v<
std::remove_cv_t<decltype(T::Name)>,
std::string_view> &&
std::is_final_v<T>;
/// @brief A shader is used as the base class for all programs across any supported
/// backend API. Shaders are registered with a `gfx::ShaderRegistry` instance.
class Shader {
public:
virtual ~Shader() = default;
/// @brief Get the name of this shader
/// @return Shader name
virtual const std::string_view name() const noexcept = 0;
/// @brief Downcast to a type
/// @tparam T Derived type
/// @return Type or nullptr if type info was not a match
template<typename T,
typename std::enable_if_t<is_shader_v<T>, bool>* = nullptr>
T* to() noexcept {
if (name() != T::Name) {
return nullptr;
}
return static_cast<T*>(this);
}
};
} // namespace gfx
} // namespace mbgl

View File

@@ -0,0 +1,77 @@
#pragma once
#include <mbgl/gfx/shader.hpp>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <unordered_map>
namespace mbgl {
namespace gfx {
/// @brief A ShaderRegistry contains a collection of gfx::Shader instances.
/// Using the registry, shaders may be dynamically registered or replaced
/// at runtime.
class ShaderRegistry {
public:
explicit ShaderRegistry();
ShaderRegistry(const ShaderRegistry&) = delete;
ShaderRegistry(ShaderRegistry&&) noexcept = delete;
ShaderRegistry& operator=(const ShaderRegistry&) = delete;
ShaderRegistry& operator=(ShaderRegistry&&) noexcept = delete;
virtual ~ShaderRegistry() = default;
/// @brief Checks if a shader exists in the registry for the given name.
/// @param shaderName Name of shader
/// @return If a shader is found, true
[[nodiscard]] virtual bool isShader(const std::string& shaderName)
const noexcept;
/// @brief Get a shader from the registry by name.
/// @param shaderName Name of shader
/// @return A `gfx::Shader` or `nullptr` if no shader is found with the
/// given name
[[nodiscard]] virtual const std::shared_ptr<gfx::Shader> getShader(
const std::string& shaderName) const noexcept;
/// @brief Replace a matching shader in the registry with the provided
/// instance. Shader names must match.
/// @param shader A `gfx::Shader`. The ShaderRegistry will take ownership.
/// @return True if a match was found and the shader was replaced, false
/// otherwise.
[[nodiscard]] virtual bool replaceShader(
std::shared_ptr<Shader>&& shader) noexcept;
/// @brief Register a new shader with the registry. If a shader is present
/// in the registry with a conflicting name, registration will fail.
/// @param shader A `gfx::Shader` to register. The ShaderRegistry will
/// take ownership.
/// @return True if the shader was registered, false if another shader is
/// already present with a conflicting name.
[[nodiscard]] virtual bool registerShader(
std::shared_ptr<Shader>&& shader) noexcept;
/// @brief Shorthand helper to quickly get a derived type from the registry.
/// @tparam T Derived type, inheriting `gfx::Shader`
/// @return T or nullptr if not found in the registry
template<typename T,
typename std::enable_if_t<is_shader_v<T>, bool>* = nullptr>
std::shared_ptr<T> get() noexcept {
auto shader = getShader(std::string(T::Name));
if (!shader || (shader->name() != T::Name)) {
return nullptr;
}
return std::static_pointer_cast<T>(shader);
}
private:
std::unordered_map<
std::string,
std::shared_ptr<gfx::Shader>
> programs;
mutable std::shared_mutex programLock;
};
} // namespace gfx
} // namespace mbgl

View File

@@ -8,6 +8,10 @@
namespace mbgl {
namespace gfx {
class ShaderRegistry;
}
enum class MapLoadError {
StyleParseError,
StyleLoadError,
@@ -57,6 +61,9 @@ public:
/// This method should return true if unused image can be removed,
/// false otherwise. By default, unused image will be removed.
virtual bool onCanRemoveUnusedStyleImage(const std::string&) { return true; }
// Observe this event to easily mutate or observe shaders as soon
// as the registry becomes available.
virtual void onRegisterShaders(gfx::ShaderRegistry&) {};
};
} // namespace mbgl

View File

@@ -5,6 +5,10 @@
namespace mbgl {
namespace gfx {
class ShaderRegistry;
}
class RendererObserver {
public:
virtual ~RendererObserver() = default;
@@ -36,6 +40,9 @@ public:
using StyleImageMissingCallback = std::function<void()>;
virtual void onStyleImageMissing(const std::string&, const StyleImageMissingCallback& done) { done(); }
virtual void onRemoveUnusedStyleImages(const std::vector<std::string>&) {}
// Entry point for custom shader registration
virtual void onRegisterShaders(gfx::ShaderRegistry&) {};
};
} // namespace mbgl

View File

@@ -0,0 +1,55 @@
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/gfx/shader.hpp>
namespace mbgl {
namespace gfx {
ShaderRegistry::ShaderRegistry() {}
bool ShaderRegistry::isShader(const std::string& shaderName)
const noexcept
{
std::shared_lock<std::shared_mutex> readerLock(programLock);
return programs.find(shaderName) != programs.end();
}
const std::shared_ptr<gfx::Shader> ShaderRegistry::getShader(const std::string& shaderName)
const noexcept
{
std::shared_lock<std::shared_mutex> readerLock(programLock);
const auto it = programs.find(shaderName);
if (it == programs.end()) {
return nullptr;
}
return it->second;
}
bool ShaderRegistry::replaceShader(
std::shared_ptr<gfx::Shader>&& shader) noexcept
{
std::unique_lock<std::shared_mutex> writerLock(programLock);
const std::string programName{shader->name()}; // TODO: C++ 20 heterogenous lookup
if (programs.find(programName) == programs.end()) {
return false;
}
programs[programName] = std::move(shader);
return true;
}
bool ShaderRegistry::registerShader(
std::shared_ptr<gfx::Shader>&& shader) noexcept
{
std::unique_lock<std::shared_mutex> writerLock(programLock);
const std::string programName{shader->name()};
if (programs.find(programName) != programs.end()) {
return false;
}
programs.emplace(programName, std::move(shader));
return true;
}
} // namespace gl
} // namespace mbgl

View File

@@ -60,18 +60,24 @@ public:
const ProgramParameters& programParameters,
const std::string& additionalDefines) {
// Compile the shader
const std::initializer_list<const char*> vertexSource = {
std::initializer_list<const char*> vertexSource = {
programParameters.getDefines().c_str(),
additionalDefines.c_str(),
(programs::gl::shaderSource() + programs::gl::vertexPreludeOffset),
(programs::gl::shaderSource() + vertexOffset)
programParameters.vertexSource().length() == 0 ?
(programs::gl::shaderSource() + vertexOffset) :
programParameters.vertexSource().c_str()
};
const std::initializer_list<const char*> fragmentSource = {
std::initializer_list<const char*> fragmentSource = {
programParameters.getDefines().c_str(),
additionalDefines.c_str(),
(programs::gl::shaderSource() + programs::gl::fragmentPreludeOffset),
(programs::gl::shaderSource() + fragmentOffset)
programParameters.fragmentSource().length() == 0 ?
(programs::gl::shaderSource() + fragmentOffset) :
programParameters.fragmentSource().c_str()
};
return std::make_unique<Instance>(context, vertexSource, fragmentSource);
}

View File

@@ -182,4 +182,8 @@ void Map::Impl::onRemoveUnusedStyleImages(const std::vector<std::string>& unused
}
}
void Map::Impl::onRegisterShaders(gfx::ShaderRegistry& registry) {
observer.onRegisterShaders(registry);
}
} // namespace mbgl

View File

@@ -18,6 +18,10 @@ namespace mbgl {
class FileSource;
class ResourceTransform;
namespace gfx {
class ShaderRegistry;
}
struct StillImageRequest {
StillImageRequest(Map::StillImageCallback&& callback_)
: callback(std::move(callback_)) {
@@ -47,6 +51,7 @@ public:
void onDidFinishRenderingMap() final;
void onStyleImageMissing(const std::string&, const std::function<void()>&) final;
void onRemoveUnusedStyleImages(const std::vector<std::string>&) final;
void onRegisterShaders(gfx::ShaderRegistry&) final;
// Map
void jumpTo(const CameraOptions&);

View File

@@ -1,5 +1,6 @@
#include <mbgl/programs/program_parameters.hpp>
#include <mbgl/util/string.hpp>
#include <string_view>
namespace mbgl {
@@ -15,11 +16,28 @@ ProgramParameters::ProgramParameters(const float pixelRatio,
result += "#define OVERDRAW_INSPECTOR\n";
}
return result;
}()) {
}())
{}
ProgramParameters ProgramParameters::withShaderSource(std::string_view vertexSource,
std::string_view fragmentSource) const noexcept
{
ProgramParameters params = *this;
params.vertexSource_ = vertexSource;
params.fragmentSource_ = fragmentSource;
return params;
}
const std::string& ProgramParameters::getDefines() const {
return defines;
}
const std::string& ProgramParameters::vertexSource() const noexcept {
return vertexSource_;
}
const std::string& ProgramParameters::fragmentSource() const noexcept {
return fragmentSource_;
}
} // namespace mbgl

View File

@@ -8,10 +8,18 @@ class ProgramParameters {
public:
ProgramParameters(float pixelRatio, bool overdraw);
ProgramParameters withShaderSource(std::string_view vertexSource,
std::string_view fragmentSource) const noexcept;
const std::string& getDefines() const;
const std::string& vertexSource() const noexcept;
const std::string& fragmentSource() const noexcept;
private:
std::string defines;
std::string vertexSource_{""};
std::string fragmentSource_{""};
};
} // namespace mbgl

View File

@@ -20,6 +20,8 @@ Programs::Programs(gfx::Context& context_, const ProgramParameters& programParam
Programs::~Programs() = default;
void Programs::registerWith([[maybe_unused]] gfx::ShaderRegistry& registry) noexcept {}
BackgroundLayerPrograms& Programs::getBackgroundLayerPrograms() noexcept {
if (!backgroundPrograms) {
backgroundPrograms = std::make_unique<BackgroundLayerPrograms>(context, programParameters);

View File

@@ -3,6 +3,7 @@
#include <mbgl/programs/clipping_mask_program.hpp>
#include <mbgl/programs/debug_program.hpp>
#include <mbgl/programs/program_parameters.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <memory>
namespace mbgl {
@@ -23,6 +24,10 @@ public:
Programs(gfx::Context&, const ProgramParameters&);
~Programs();
/// @brief Registers built-in programs with the provided registry.
/// @param registry gfx::ShaderRegistry to populate with built-in programs.
void registerWith(gfx::ShaderRegistry& registry) noexcept;
BackgroundLayerPrograms& getBackgroundLayerPrograms() noexcept;
RasterLayerPrograms& getRasterLayerPrograms() noexcept;
HeatmapLayerPrograms& getHeatmapLayerPrograms() noexcept;

View File

@@ -51,10 +51,11 @@ PaintParameters::PaintParameters(gfx::Context& context_,
timePoint(timePoint_),
pixelRatio(pixelRatio_),
#ifndef NDEBUG
programs((debugOptions & MapDebugOptions::Overdraw) ? staticData_.overdrawPrograms : staticData_.programs)
programs((debugOptions & MapDebugOptions::Overdraw) ? staticData_.overdrawPrograms : staticData_.programs),
#else
programs(staticData_.programs)
programs(staticData_.programs),
#endif
shaders(*staticData_.shaders)
{
pixelsToGLUnits = {{ 2.0f / state.getSize().width, -2.0f / state.getSize().height }};

View File

@@ -30,6 +30,7 @@ class Context;
class RendererBackend;
class CommandEncoder;
class RenderPass;
class ShaderRegistry;
} // namespace gfx
@@ -78,7 +79,10 @@ public:
float pixelRatio;
std::array<float, 2> pixelsToGLUnits;
// Programs is, in effect, an immutable shader registry
Programs& programs;
// We're migrating to a dynamic one
gfx::ShaderRegistry& shaders;
gfx::DepthMode depthModeForSublayer(uint8_t n, gfx::DepthMaskType) const;
gfx::DepthMode depthModeFor3D() const;

View File

@@ -49,8 +49,10 @@ static gfx::VertexVector<HeatmapTextureLayoutVertex> heatmapTextureVertices() {
return result;
}
RenderStaticData::RenderStaticData(gfx::Context& context, float pixelRatio)
RenderStaticData::RenderStaticData(gfx::Context& context, float pixelRatio,
std::unique_ptr<gfx::ShaderRegistry>&& shaders_)
: programs(context, ProgramParameters{pixelRatio, false}),
shaders(std::move(shaders_)),
clippingMaskSegments(tileTriangleSegments())
#ifndef NDEBUG
,

View File

@@ -3,6 +3,7 @@
#include <mbgl/gfx/vertex_buffer.hpp>
#include <mbgl/gfx/index_buffer.hpp>
#include <mbgl/gfx/renderbuffer.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/programs/background_program.hpp>
#include <mbgl/programs/heatmap_texture_program.hpp>
#include <mbgl/programs/programs.hpp>
@@ -19,7 +20,8 @@ class UploadPass;
class RenderStaticData {
public:
RenderStaticData(gfx::Context&, float pixelRatio);
RenderStaticData(gfx::Context&, float pixelRatio,
std::unique_ptr<gfx::ShaderRegistry>&& shaders_);
void upload(gfx::UploadPass&);
@@ -40,8 +42,11 @@ public:
bool uploaded = false;
Size backendSize;
// @TODO: Migrate away from and remove `Programs`
Programs programs;
std::unique_ptr<gfx::ShaderRegistry> shaders;
const SegmentVector<BackgroundAttributes> clippingMaskSegments;
#ifndef NDEBUG

View File

@@ -8,6 +8,7 @@
#include <mbgl/gfx/cull_face_mode.hpp>
#include <mbgl/gfx/context.hpp>
#include <mbgl/gfx/renderable.hpp>
#include <mbgl/programs/programs.hpp>
#include <mbgl/renderer/pattern_atlas.hpp>
#include <mbgl/renderer/renderer_observer.hpp>
#include <mbgl/renderer/render_static_data.hpp>
@@ -47,7 +48,10 @@ void Renderer::Impl::render(const RenderTree& renderTree) {
const auto& renderTreeParameters = renderTree.getParameters();
if (!staticData) {
staticData = std::make_unique<RenderStaticData>(backend.getContext(), pixelRatio);
staticData = std::make_unique<RenderStaticData>(backend.getContext(), pixelRatio,
std::make_unique<gfx::ShaderRegistry>());
staticData->programs.registerWith(*staticData->shaders);
observer->onRegisterShaders(*staticData->shaders);
}
staticData->has3D = renderTreeParameters.has3D;

View File

@@ -13,6 +13,7 @@ class RenderTree;
namespace gfx {
class RendererBackend;
class ShadeRegistry;
} // namespace gfx
class Renderer::Impl {
@@ -22,7 +23,7 @@ public:
private:
friend class Renderer;
void setObserver(RendererObserver*);
void render(const RenderTree&);

View File

@@ -21,6 +21,7 @@ add_library(
${PROJECT_SOURCE_DIR}/test/programs/symbol_program.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/image_manager.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/pattern_atlas.test.cpp
${PROJECT_SOURCE_DIR}/test/renderer/shader_registry.test.cpp
${PROJECT_SOURCE_DIR}/test/sprite/sprite_loader.test.cpp
${PROJECT_SOURCE_DIR}/test/sprite/sprite_parser.test.cpp
${PROJECT_SOURCE_DIR}/test/src/mbgl/test/fixture_log_observer.cpp

View File

@@ -7,6 +7,7 @@
#include <mbgl/gfx/backend_scope.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/gl/context.hpp>
#include <mbgl/map/map_options.hpp>
#include <mbgl/math/log2.hpp>
@@ -1576,3 +1577,23 @@ TEST(Map, VolatileSource) {
test.runLoop.run();
EXPECT_EQ(8, requestedTiles);
}
TEST(Map, ObserveShaderRegistration) {
MapTest<> test;
bool observedRegistry = false;
test.observer.onRegisterShadersCallback = [&observedRegistry](gfx::ShaderRegistry&) {
observedRegistry = true;
};
test.map.jumpTo(CameraOptions().withZoom(10));
test.map.getStyle().loadJSON(util::read_file("test/fixtures/api/empty.json"));
test.frontend.render(test.map);
EXPECT_EQ(observedRegistry, true);
// Should not see it again
observedRegistry = false;
test.frontend.render(test.map);
EXPECT_EQ(observedRegistry, false);
}

View File

@@ -0,0 +1,113 @@
#include <mbgl/test/util.hpp>
#include <mbgl/gfx/shader_registry.hpp>
#include <mbgl/gfx/shader.hpp>
#include <string_view>
#include <iostream>
using namespace mbgl;
namespace {
template<uint32_t token_value>
class StubProgramBase : public gfx::Shader {
public:
static constexpr auto Token = token_value;
virtual uint32_t draw() { return token; }
protected:
uint32_t token{token_value};
};
class StubProgram_1 final : public StubProgramBase<10> {
public:
static constexpr std::string_view Name{"StubProgram_1"};
const std::string_view name() const noexcept override {
return Name;
}
void setToken(uint32_t tok) { token = tok; }
};
class StubProgram_2 final : public StubProgramBase<20> {
public:
static constexpr std::string_view Name{"StubProgram_2"};
const std::string_view name() const noexcept override {
return Name;
}
};
class StubShaderConsumer {
public:
template<typename T>
uint32_t useShader(gfx::ShaderRegistry& registry) {
auto program = registry.get<T>();
return program ? program->draw() : 0;
}
};
} // namespace
// Ensure we can register a gfx::Shader-based type with a registry object
TEST(ShaderRegistry, RegisterShader) {
gfx::ShaderRegistry registry;
// The registry should start empty
ASSERT_FALSE(registry.isShader(std::string{StubProgram_1::Name}));
// Register the program
auto program = std::make_shared<StubProgram_1>();
ASSERT_TRUE(registry.registerShader(program));
// We can't re-register the same program name
ASSERT_FALSE(registry.registerShader(program));
// Ensure the shader is present in the registry now
ASSERT_TRUE(registry.isShader(std::string{StubProgram_1::Name}));
// And we can fetch it
ASSERT_EQ(registry.get<StubProgram_1>(), program);
// Make sure downcasting to program1 works as expected
StubShaderConsumer consumer;
ASSERT_EQ(consumer.useShader<StubProgram_1>(registry), 10);
}
// Validate replacing a shader by name with a different instance
TEST(ShaderRegistry, ReplaceShaderType) {
gfx::ShaderRegistry registry;
ASSERT_FALSE(registry.isShader(std::string{StubProgram_1::Name}));
auto program = std::make_shared<StubProgram_1>();
ASSERT_TRUE(registry.registerShader(program));
// Ensure the shader is present in the registry now
ASSERT_TRUE(registry.isShader(std::string{StubProgram_1::Name}));
ASSERT_TRUE(registry.isShader(std::string{program->name()}));
// Make sure downcasting to program1 works as expected
StubShaderConsumer consumer;
ASSERT_EQ(consumer.useShader<StubProgram_1>(registry), 10);
// StubProgram_1 and StubProgram_2 are disconnected types,
// and as such this replacement should fail as StubProgram_2
// has never been registered.
auto program2 = std::make_shared<StubProgram_2>();
ASSERT_FALSE(registry.replaceShader(program2));
// Make a second instance of program1 and change the token
auto program3 = std::make_shared<StubProgram_1>();
program3->setToken(30);
// Replace it in the registry
ASSERT_TRUE(registry.replaceShader(program3));
// Assert the new program downcasts from the registry as expected
ASSERT_EQ(consumer.useShader<StubProgram_1>(registry), 30);
}
// Validate shader type info and downcasting are functioning
TEST(ShaderRegistry, ShaderRTTI) {
auto program1 = StubProgram_1();
auto asBase = static_cast<gfx::Shader*>(&program1);
// Should convert to program 1 but not program 2
ASSERT_EQ(asBase->to<StubProgram_1>(), &program1);
ASSERT_EQ(asBase->to<StubProgram_2>(), nullptr);
}

View File

@@ -44,12 +44,19 @@ public:
}
}
void onRegisterShaders(gfx::ShaderRegistry& registry) final {
if (onRegisterShadersCallback) {
onRegisterShadersCallback(registry);
}
}
std::function<void()> willStartLoadingMapCallback;
std::function<void()> didFinishLoadingMapCallback;
std::function<void()> didFailLoadingMapCallback;
std::function<void()> didFinishLoadingStyleCallback;
std::function<void(RenderFrameStatus)> didFinishRenderingFrameCallback;
std::function<void()> didBecomeIdleCallback;
std::function<void(gfx::ShaderRegistry&)> onRegisterShadersCallback;
};