Files
MapLibreParticleLab/include/mbgl/shaders/mtl/circle.hpp
2023-12-05 01:15:35 +01:00

263 lines
8.7 KiB
C++

#pragma once
#include <mbgl/shaders/circle_layer_ubo.hpp>
#include <mbgl/shaders/shader_source.hpp>
#include <mbgl/shaders/mtl/common.hpp>
#include <mbgl/shaders/mtl/shader_program.hpp>
namespace mbgl {
namespace shaders {
template <>
struct ShaderSource<BuiltIn::CircleShader, gfx::Backend::Type::Metal> {
static constexpr auto name = "CircleShader";
static constexpr auto vertexMainFunction = "vertexMain";
static constexpr auto fragmentMainFunction = "fragmentMain";
static constexpr auto hasPermutations = true;
static const std::array<AttributeInfo, 8> attributes;
static const std::array<UniformBlockInfo, 6> uniforms;
static const std::array<TextureInfo, 0> textures;
static constexpr auto source = R"(
struct VertexStage {
short2 position [[attribute(0)]];
#if !defined(HAS_UNIFORM_u_color)
float4 color [[attribute(1)]];
#endif
#if !defined(HAS_UNIFORM_u_radius)
float2 radius [[attribute(2)]];
#endif
#if !defined(HAS_UNIFORM_u_blur)
float2 blur [[attribute(3)]];
#endif
#if !defined(HAS_UNIFORM_u_opacity)
float2 opacity [[attribute(4)]];
#endif
#if !defined(HAS_UNIFORM_u_stroke_color)
float4 stroke_color [[attribute(5)]];
#endif
#if !defined(HAS_UNIFORM_u_stroke_width)
float2 stroke_width [[attribute(6)]];
#endif
#if !defined(HAS_UNIFORM_u_stroke_opacity)
float2 stroke_opacity [[attribute(7)]];
#endif
};
struct FragmentStage {
float4 position [[position, invariant]];
float2 extrude;
half antialiasblur;
#if !defined(HAS_UNIFORM_u_color)
half4 color;
#endif
#if !defined(HAS_UNIFORM_u_radius)
float radius;
#endif
#if !defined(HAS_UNIFORM_u_blur)
half blur;
#endif
#if !defined(HAS_UNIFORM_u_opacity)
half opacity;
#endif
#if !defined(HAS_UNIFORM_u_stroke_color)
half4 stroke_color;
#endif
#if !defined(HAS_UNIFORM_u_stroke_width)
half stroke_width;
#endif
#if !defined(HAS_UNIFORM_u_stroke_opacity)
half stroke_opacity;
#endif
};
struct alignas(16) CircleDrawableUBO {
float4x4 matrix;
float2 extrude_scale;
float2 padding;
};
struct alignas(16) CirclePaintParamsUBO {
float camera_to_center_distance;
float device_pixel_ratio;
float pad1,pad2;
};
struct alignas(16) CircleEvaluatedPropsUBO {
float4 color;
float4 stroke_color;
float radius;
float blur;
float opacity;
float stroke_width;
float stroke_opacity;
int scale_with_map;
int pitch_with_map;
float padding;
};
struct alignas(16) CircleInterpolateUBO {
float color_t;
float radius_t;
float blur_t;
float opacity_t;
float stroke_color_t;
float stroke_width_t;
float stroke_opacity_t;
float pad1_;
};
struct alignas(16) CirclePermutationUBO {
Attribute color;
Attribute radius;
Attribute blur;
Attribute opacity;
Attribute stroke_color;
Attribute stroke_width;
Attribute stroke_opacity;
bool overdrawInspector;
};
FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]],
device const CircleDrawableUBO& drawable [[buffer(8)]],
device const CirclePaintParamsUBO& params [[buffer(9)]],
device const CircleEvaluatedPropsUBO& props [[buffer(10)]],
device const CircleInterpolateUBO& interp [[buffer(11)]],
device const CirclePermutationUBO& permutation [[buffer(12)]],
device const ExpressionInputsUBO& expr [[buffer(13)]]) {
#if defined(HAS_UNIFORM_u_radius)
const auto radius = props.radius;
#else
const auto radius = valueFor(permutation.radius, props.radius, vertx.radius, interp.radius_t, expr);
#endif
#if defined(HAS_UNIFORM_u_stroke_width)
const auto stroke_width = props.stroke_width;
#else
const auto stroke_width = valueFor(permutation.stroke_width, props.stroke_width, vertx.stroke_width, interp.stroke_width_t, expr);
#endif
// unencode the extrusion vector that we snuck into the a_pos vector
const float2 extrude = glMod(float2(vertx.position), 2.0) * 2.0 - 1.0;
const float2 scaled_extrude = extrude * drawable.extrude_scale;
// multiply a_pos by 0.5, since we had it * 2 in order to sneak in extrusion data
const float2 circle_center = floor(float2(vertx.position) * 0.5);
float4 position;
if (props.pitch_with_map) {
float2 corner_position = circle_center;
if (props.scale_with_map) {
corner_position += scaled_extrude * (radius + stroke_width);
} else {
// Pitching the circle with the map effectively scales it with the map
// To counteract the effect for pitch-scale: viewport, we rescale the
// whole circle based on the pitch scaling effect at its central point
float4 projected_center = drawable.matrix * float4(circle_center, 0, 1);
corner_position += scaled_extrude * (radius + stroke_width) *
(projected_center.w / params.camera_to_center_distance);
}
position = drawable.matrix * float4(corner_position, 0, 1);
} else {
position = drawable.matrix * float4(circle_center, 0, 1);
const float factor = props.scale_with_map ? params.camera_to_center_distance : position.w;
position.xy += scaled_extrude * (radius + stroke_width) * factor;
}
// This is a minimum blur distance that serves as a faux-antialiasing for
// the circle. since blur is a ratio of the circle's size and the intent is
// to keep the blur at roughly 1px, the two are inversely related.
const half antialiasblur = 1.0 / params.device_pixel_ratio / (radius + stroke_width);
return {
.position = position,
.extrude = extrude,
.antialiasblur = antialiasblur,
#if !defined(HAS_UNIFORM_u_color)
.color = half4(colorFor(permutation.color, props.color, vertx.color, interp.color_t, expr)),
#endif
#if !defined(HAS_UNIFORM_u_radius)
.radius = half(radius),
#endif
#if !defined(HAS_UNIFORM_u_blur)
.blur = half(valueFor(permutation.blur, props.blur, vertx.blur, interp.blur_t, expr)),
#endif
#if !defined(HAS_UNIFORM_u_opacity)
.opacity = half(valueFor(permutation.opacity, props.opacity, vertx.opacity, interp.opacity_t, expr)),
#endif
#if !defined(HAS_UNIFORM_u_stroke_color)
.stroke_color = half4(colorFor(permutation.stroke_color, props.stroke_color, vertx.stroke_color, interp.stroke_color_t, expr)),
#endif
#if !defined(HAS_UNIFORM_u_stroke_width)
.stroke_width = half(stroke_width),
#endif
#if !defined(HAS_UNIFORM_u_stroke_opacity)
.stroke_opacity = half(valueFor(permutation.stroke_opacity, props.stroke_opacity, vertx.stroke_opacity, interp.stroke_opacity_t, expr)),
#endif
};
}
half4 fragment fragmentMain(FragmentStage in [[stage_in]],
device const CirclePaintParamsUBO& params [[buffer(9)]],
device const CircleEvaluatedPropsUBO& props [[buffer(10)]],
device const CirclePermutationUBO& permutation [[buffer(12)]]) {
if (permutation.overdrawInspector) {
return half4(1.0);
}
#if defined(HAS_UNIFORM_u_color)
const half4 color = half4(props.color);
#else
const half4 color = in.color;
#endif
#if defined(HAS_UNIFORM_u_radius)
const float radius = props.radius;
#else
const float radius = in.radius;
#endif
#if defined(HAS_UNIFORM_u_blur)
const half blur = props.blur;
#else
const half blur = in.blur;
#endif
#if defined(HAS_UNIFORM_u_opacity)
const half opacity = props.opacity;
#else
const half opacity = in.opacity;
#endif
#if defined(HAS_UNIFORM_u_stroke_color)
const half4 stroke_color = half4(props.stroke_color);
#else
const half4 stroke_color = in.stroke_color;
#endif
#if defined(HAS_UNIFORM_u_stroke_width)
const half stroke_width = props.stroke_width;
#else
const half stroke_width = in.stroke_width;
#endif
#if defined(HAS_UNIFORM_u_stroke_opacity)
const half stroke_opacity = props.stroke_opacity;
#else
const half stroke_opacity = in.stroke_opacity;
#endif
const float extrude_length = length(in.extrude);
const float antialiased_blur = -max(blur, in.antialiasblur);
const float opacity_t = smoothstep(0.0, antialiased_blur, extrude_length - 1.0);
const float color_t = (stroke_width < 0.01) ? 0.0 :
smoothstep(antialiased_blur, 0.0, extrude_length - radius / (radius + stroke_width));
return half4(opacity_t * mix(color * opacity, stroke_color * stroke_opacity, color_t));
}
)";
};
} // namespace shaders
} // namespace mbgl