diff --git a/include/brisk/graphics/RenderState.hpp b/include/brisk/graphics/RenderState.hpp index de80702..dc92e6e 100644 --- a/include/brisk/graphics/RenderState.hpp +++ b/include/brisk/graphics/RenderState.hpp @@ -208,11 +208,6 @@ struct TextureChannel { static void apply(const Type& value, RenderStateEx& state); }; -struct ContourFlags { - using Type = int; - static void apply(const Type& value, RenderStateEx& state); -}; - struct CoordMatrix { using Type = Matrix; static void apply(const Type& value, RenderStateEx& state); @@ -245,7 +240,6 @@ constexpr inline Argument patterns{}; constexpr inline Argument blurRadius{}; constexpr inline Argument blurDirections{}; constexpr inline Argument textureChannel{}; -constexpr inline Argument contourFlags{}; constexpr inline Argument coordMatrix{}; constexpr inline Argument samplerMode{}; @@ -313,7 +307,7 @@ struct RenderState { float strokeWidth = 1.f; ///< Stroke or shadow width. Defaults to 1. Set to 0 to disable GradientType gradient = GradientType::Linear; - int shadowFlags = 3; // 1 - inner, 2 - outer + int reserved4 = 0; float reserved5 = 0; union { diff --git a/include/brisk/gui/GUI.hpp b/include/brisk/gui/GUI.hpp index 5a5db78..f1807c0 100644 --- a/include/brisk/gui/GUI.hpp +++ b/include/brisk/gui/GUI.hpp @@ -1066,7 +1066,7 @@ class WIDGET Widget : public BindingObject { Internal::Transition m_backgroundColor{ Palette::transparent }; Internal::Transition m_borderColor{ Palette::transparent }; Internal::Transition m_color{ Palette::white }; - Internal::Transition m_shadowColor{ Palette::black.multiplyAlpha(0.4f) }; + Internal::Transition m_shadowColor{ Palette::black.multiplyAlpha(0.66f) }; Internal::Transition m_scrollBarColor{ Palette::grey }; float m_backgroundColorTransition = 0; float m_borderColorTransition = 0; diff --git a/resources/shaders/webgpu.wgsl b/resources/shaders/webgpu.wgsl index c9ab447..27bb481 100644 --- a/resources/shaders/webgpu.wgsl +++ b/resources/shaders/webgpu.wgsl @@ -112,7 +112,7 @@ struct UniformBlock { stroke_width: f32, gradient: gradient_type, - shadow_flags: i32, + reserved_4: i32, reserved_5: f32, } @@ -547,21 +547,53 @@ fn atlasSubpixel(sprite: i32, pos: vec2i, stride: u32) -> vec3f { } } -fn shadow(signed_distance: f32) -> vec4f { - var op: f32 = 1.; - if (constants.shadow_flags & 1) == 0 && signed_distance < 0. { - op = 0.; - } - if (constants.shadow_flags & 2) == 0 && signed_distance > 0. { - op = 0.; +// Code by Evan Wallace, CC0 license + +fn gaussian(x: f32, sigma: f32) -> f32 { + let pi: f32 = 3.141592653589793; + return exp(-((x * x) / (2.0 * sigma * sigma))) / (sqrt(2.0 * pi) * sigma); +} + +// This approximates the error function, needed for the gaussian integral +fn erf(x: vec2) -> vec2 { + let s: vec2 = sign(x); + let a: vec2 = abs(x); + let x1 = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; + let x2 = x1 * x1; + return s - s / (x2 * x2); +} + +// Return the blurred mask along the x dimension +fn roundedBoxShadowX(x: f32, y: f32, sigma: f32, corner: f32, halfSize: vec2) -> f32 { + let delta: f32 = min(halfSize.y - corner - abs(y), 0.0); + let curved: f32 = halfSize.x - corner + sqrt(max(0.0, corner * corner - delta * delta)); + let integral: vec2 = 0.5 + 0.5 * erf((vec2(x) + vec2(-curved, curved)) * (sqrt(0.5) / sigma)); + return integral.y - integral.x; +} + +// Return the mask for the shadow of a box +fn roundedBoxShadow(halfSize: vec2, point: vec2, sigma: f32, corner: f32) -> f32 { + // The signal is only non-zero in a limited range, so don't waste samples + let low: f32 = point.y - halfSize.y; + let high: f32 = point.y + halfSize.y; + let start: f32 = clamp(-3.0 * sigma, low, high); + let end: f32 = clamp(3.0 * sigma, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + let step: f32 = (end - start) / 4.0; + var y: f32 = start + step * 0.5; + var value: f32 = 0.0; + + for (var i: i32 = 0; i < 4; i = i + 1) { + value = value + roundedBoxShadowX(point.x, point.y - y, sigma, corner, halfSize) * gaussian(y, sigma) * step; + y = y + step; } - let shadow_size = constants.stroke_width / 2.0; - var sh = (signed_distance + shadow_size * 0.25) / (shadow_size * 0.5); - sh = clamp(exp(-sh * sh), 0.0, 1.0); - let col = vec4f(constants.fill_color1 * sh) * sign(shadow_size); - return op * col; + + return value; } +// End of code by Evan Wallace, CC0 license + fn applyGamma(in: vec4f, gamma: f32) -> vec4f { return pow(max(in, vec4f(0.)), vec4f(gamma)); } @@ -636,7 +668,7 @@ fn postprocessColor(in: FragOut, mask_value: f32, canvas_coord: vec2u) -> FragOu } outColor = signedDistanceToColor(sd, in.canvas_coord, in.uv, in.data0.xy); } else if constants.shader == shader_shadow { - outColor = shadow(sd_rectangle(in.uv, in.data0.xy, in.data1.y, i32(in.data1.z))); + outColor = constants.fill_color1 * (roundedBoxShadow(in.data0.xy * 0.5, in.uv, constants.stroke_width * 0.18, in.data1.y)); } else if constants.shader == shader_mask || constants.shader == shader_color_mask || constants.shader == shader_text { let sprite = i32(in.data0.z); let stride = u32(in.data0.w); diff --git a/src/graphics/D3D11Renderer/fragment.hlsl b/src/graphics/D3D11Renderer/fragment.hlsl index 77df074..100be16 100644 --- a/src/graphics/D3D11Renderer/fragment.hlsl +++ b/src/graphics/D3D11Renderer/fragment.hlsl @@ -96,8 +96,8 @@ SignedDistance signedDistanceArc(float2 pt, float outer_radius, float inner_radi } circle = max(circle, pie); } - SignedDistance tint_symbol_36 = {circle, -1000.0f}; - return tint_symbol_36; + SignedDistance tint_symbol_40 = {circle, -1000.0f}; + return tint_symbol_40; } SignedDistance signedDistanceRectangle(float2 uv, float2 rectSize, float borderRadius, int corners) { @@ -134,8 +134,8 @@ SignedDistance signedDistanceRectangle(float2 uv, float2 rectSize, float borderR } } } - SignedDistance tint_symbol_37 = {sd, intersect_sd}; - return tint_symbol_37; + SignedDistance tint_symbol_41 = {sd, intersect_sd}; + return tint_symbol_41; } struct Colors { @@ -384,27 +384,45 @@ float3 atlasSubpixel(int sprite, int2 pos, uint stride) { } } -float4 shadow(float signed_distance) { - float op = 1.0f; - bool tint_tmp_4 = ((asint(constants[14].z) & 1) == 0); - if (tint_tmp_4) { - tint_tmp_4 = (signed_distance < 0.0f); - } - if ((tint_tmp_4)) { - op = 0.0f; - } - bool tint_tmp_5 = ((asint(constants[14].z) & 2) == 0); - if (tint_tmp_5) { - tint_tmp_5 = (signed_distance > 0.0f); - } - if ((tint_tmp_5)) { - op = 0.0f; +float gaussian(float x, float sigma) { + float pi = 3.14159274101257324219f; + return (exp(-(((x * x) / ((2.0f * sigma) * sigma)))) / (sqrt((2.0f * pi)) * sigma)); +} + +float2 erf(float2 x) { + float2 s = float2(sign(x)); + float2 a = abs(x); + float2 x1 = (1.0f + ((0.27839300036430358887f + ((0.23038899898529052734f + (0.07810799777507781982f * (a * a))) * a)) * a)); + float2 x2 = (x1 * x1); + return (s - (s / (x2 * x2))); +} + +float roundedBoxShadowX(float x, float y, float sigma, float corner, float2 halfSize) { + float delta = min(((halfSize.y - corner) - abs(y)), 0.0f); + float curved = ((halfSize.x - corner) + sqrt(max(0.0f, ((corner * corner) - (delta * delta))))); + float2 tint_symbol_28 = erf(((float2((x).xx) + float2(-(curved), curved)) * (0.70710676908493041992f / sigma))); + float2 integral = (0.5f + (0.5f * tint_symbol_28)); + return (integral.y - integral.x); +} + +float roundedBoxShadow(float2 halfSize, float2 tint_symbol, float sigma, float corner) { + float low = (tint_symbol.y - halfSize.y); + float high = (tint_symbol.y + halfSize.y); + float start = clamp((-3.0f * sigma), low, high); + float end = clamp((3.0f * sigma), low, high); + float step = ((end - start) / 4.0f); + float y = (start + (step * 0.5f)); + float value = 0.0f; + { + for(int i = 0; (i < 4); i = (i + 1)) { + float tint_symbol_29 = value; + float tint_symbol_30 = roundedBoxShadowX(tint_symbol.x, (tint_symbol.y - y), sigma, corner, halfSize); + float tint_symbol_31 = gaussian(y, sigma); + value = (tint_symbol_29 + ((tint_symbol_30 * tint_symbol_31) * step)); + y = (y + step); + } } - float shadow_size = (asfloat(constants[14].x) / 2.0f); - float sh = ((signed_distance + (shadow_size * 0.25f)) / (shadow_size * 0.5f)); - sh = clamp(exp((-(sh) * sh)), 0.0f, 1.0f); - float4 col = (float4((asfloat(constants[9]) * sh)) * float(sign(shadow_size))); - return (op * col); + return value; } float4 applyGamma(float4 tint_symbol_2, float gamma) { @@ -421,24 +439,24 @@ struct FragOut { }; bool useBlending() { - bool tint_tmp_7 = (asint(constants[1].x) == 2); - if (!tint_tmp_7) { - tint_tmp_7 = (asint(constants[1].x) == 4); + bool tint_tmp_5 = (asint(constants[1].x) == 2); + if (!tint_tmp_5) { + tint_tmp_5 = (asint(constants[1].x) == 4); } - bool tint_tmp_6 = (tint_tmp_7); - if (tint_tmp_6) { - tint_tmp_6 = (asint(constants[3].w) != 0); + bool tint_tmp_4 = (tint_tmp_5); + if (tint_tmp_4) { + tint_tmp_4 = (asint(constants[3].w) != 0); } - return (tint_tmp_6); + return (tint_tmp_4); } FragOut postprocessColor(FragOut tint_symbol_2, float mask_value, uint2 canvas_coord) { FragOut tint_symbol_3 = tint_symbol_2; float opacity = (asfloat(constants[4].w) * mask_value); if (((constants[4].x | constants[4].y) != 0u)) { - uint tint_symbol_28 = get_pattern(tint_div(canvas_coord.x, uint(asint(constants[4].z))), constants[4].x); - uint tint_symbol_29 = get_pattern(tint_div(canvas_coord.y, uint(asint(constants[4].z))), constants[4].y); - uint p = (tint_symbol_28 & tint_symbol_29); + uint tint_symbol_32 = get_pattern(tint_div(canvas_coord.x, uint(asint(constants[4].z))), constants[4].x); + uint tint_symbol_33 = get_pattern(tint_div(canvas_coord.y, uint(asint(constants[4].z))), constants[4].y); + uint p = (tint_symbol_32 & tint_symbol_33); opacity = (opacity * float(p)); } tint_symbol_3.color = (tint_symbol_3.color * opacity); @@ -463,14 +481,14 @@ FragOut postprocessColor(FragOut tint_symbol_2, float mask_value, uint2 canvas_c return tint_symbol_3; } -struct tint_symbol_34 { +struct tint_symbol_38 { noperspective float4 data0 : TEXCOORD0; noperspective float4 data1 : TEXCOORD1; noperspective float2 uv : TEXCOORD2; noperspective float2 canvas_coord : TEXCOORD3; float4 position : SV_Position; }; -struct tint_symbol_35 { +struct tint_symbol_39 { float4 color : SV_Target0; float4 blend : SV_Target1; }; @@ -489,11 +507,11 @@ FragOut fragmentMain_inner(VertexOutput tint_symbol_2) { float4 outColor = float4(0.0f, 0.0f, 0.0f, 0.0f); float4 outBlend = float4(0.0f, 0.0f, 0.0f, 0.0f); bool useBlend = false; - bool tint_tmp_8 = (asint(constants[1].x) == 0); - if (!tint_tmp_8) { - tint_tmp_8 = (asint(constants[1].x) == 1); + bool tint_tmp_6 = (asint(constants[1].x) == 0); + if (!tint_tmp_6) { + tint_tmp_6 = (asint(constants[1].x) == 1); } - if ((tint_tmp_8)) { + if ((tint_tmp_6)) { SignedDistance sd = (SignedDistance)0; if ((asint(constants[1].x) == 0)) { sd = signedDistanceRectangle(tint_symbol_2.uv, tint_symbol_2.data0.xy, tint_symbol_2.data1.y, tint_ftoi(tint_symbol_2.data1.z)); @@ -503,18 +521,18 @@ FragOut fragmentMain_inner(VertexOutput tint_symbol_2) { outColor = signedDistanceToColor(sd, tint_symbol_2.canvas_coord, tint_symbol_2.uv, tint_symbol_2.data0.xy); } else { if ((asint(constants[1].x) == 3)) { - float tint_symbol_30 = sd_rectangle(tint_symbol_2.uv, tint_symbol_2.data0.xy, tint_symbol_2.data1.y, tint_ftoi(tint_symbol_2.data1.z)); - outColor = shadow(tint_symbol_30); + float tint_symbol_34 = roundedBoxShadow((tint_symbol_2.data0.xy * 0.5f), tint_symbol_2.uv, (asfloat(constants[14].x) * 0.18000000715255737305f), tint_symbol_2.data1.y); + outColor = (asfloat(constants[9]) * tint_symbol_34); } else { - bool tint_tmp_10 = (asint(constants[1].x) == 4); - if (!tint_tmp_10) { - tint_tmp_10 = (asint(constants[1].x) == 5); + bool tint_tmp_8 = (asint(constants[1].x) == 4); + if (!tint_tmp_8) { + tint_tmp_8 = (asint(constants[1].x) == 5); } - bool tint_tmp_9 = (tint_tmp_10); - if (!tint_tmp_9) { - tint_tmp_9 = (asint(constants[1].x) == 2); + bool tint_tmp_7 = (tint_tmp_8); + if (!tint_tmp_7) { + tint_tmp_7 = (asint(constants[1].x) == 2); } - if ((tint_tmp_9)) { + if ((tint_tmp_7)) { int sprite = tint_ftoi(tint_symbol_2.data0.z); uint stride = tint_ftou_1(tint_symbol_2.data0.w); int2 tuv = tint_ftoi_1(tint_symbol_2.uv); @@ -525,9 +543,9 @@ FragOut fragmentMain_inner(VertexOutput tint_symbol_2) { outBlend = float4((colors.brush.a * rgb), 1.0f); } else { if ((asint(constants[1].x) == 5)) { - float4 tint_symbol_31 = colors.brush; - float4 tint_symbol_32 = atlasRGBA(sprite, tuv, stride); - outColor = (tint_symbol_31 * tint_symbol_32); + float4 tint_symbol_35 = colors.brush; + float4 tint_symbol_36 = atlasRGBA(sprite, tuv, stride); + outColor = (tint_symbol_35 * tint_symbol_36); } else { float alpha = atlasAccum(sprite, tuv, stride); outColor = (colors.brush * float4((alpha).xxxx)); @@ -536,14 +554,14 @@ FragOut fragmentMain_inner(VertexOutput tint_symbol_2) { } } } - FragOut tint_symbol_38 = {outColor, outBlend}; - return postprocessColor(tint_symbol_38, mask_value, tint_ftou(tint_symbol_2.canvas_coord)); + FragOut tint_symbol_42 = {outColor, outBlend}; + return postprocessColor(tint_symbol_42, mask_value, tint_ftou(tint_symbol_2.canvas_coord)); } -tint_symbol_35 fragmentMain(tint_symbol_34 tint_symbol_33) { - VertexOutput tint_symbol_39 = {float4(tint_symbol_33.position.xyz, (1.0f / tint_symbol_33.position.w)), tint_symbol_33.data0, tint_symbol_33.data1, tint_symbol_33.uv, tint_symbol_33.canvas_coord}; - FragOut inner_result = fragmentMain_inner(tint_symbol_39); - tint_symbol_35 wrapper_result = (tint_symbol_35)0; +tint_symbol_39 fragmentMain(tint_symbol_38 tint_symbol_37) { + VertexOutput tint_symbol_43 = {float4(tint_symbol_37.position.xyz, (1.0f / tint_symbol_37.position.w)), tint_symbol_37.data0, tint_symbol_37.data1, tint_symbol_37.uv, tint_symbol_37.canvas_coord}; + FragOut inner_result = fragmentMain_inner(tint_symbol_43); + tint_symbol_39 wrapper_result = (tint_symbol_39)0; wrapper_result.color = inner_result.color; wrapper_result.blend = inner_result.blend; if (tint_discarded) { diff --git a/src/graphics/RenderState.cpp b/src/graphics/RenderState.cpp index 4fa7ecc..79ebcdc 100644 --- a/src/graphics/RenderState.cpp +++ b/src/graphics/RenderState.cpp @@ -111,10 +111,6 @@ void Tag::TextureChannel::apply(const Tag::TextureChannel::Type& value, RenderSt state.textureChannel = value; } -void Tag::ContourFlags::apply(const Tag::ContourFlags::Type& value, RenderStateEx& state) { - state.shadowFlags = value; -} - void Tag::CoordMatrix::apply(const Tag::CoordMatrix::Type& value, RenderStateEx& state) { state.coordMatrix = value; } diff --git a/src/graphics/Renderer_test.cpp b/src/graphics/Renderer_test.cpp index 4600bec..8a2ea40 100644 --- a/src/graphics/Renderer_test.cpp +++ b/src/graphics/Renderer_test.cpp @@ -571,4 +571,34 @@ TEST_CASE("Multi-pass render") { }); } +TEST_CASE("Shadow") { + renderTest( + "shadows", Size{ 1536, 256 }, + [&](RenderContext& context) { + RawCanvas canvas(context); + for (int i = 0; i < 6; ++i) { + RectangleF box{ 256.f * i, 0, 256.f * i + 256, 256 }; + context.setClipRect(box); + float shadowSize = 2 << i; + canvas.drawShadow(box.withPadding(64.f), 0.f, 0.f, contourSize = shadowSize, + contourColor = Palette::black); + } + }, + Palette::white); + + renderTest( + "shadows-rounded", Size{ 1536, 256 }, + [&](RenderContext& context) { + RawCanvas canvas(context); + for (int i = 0; i < 6; ++i) { + RectangleF box{ 256.f * i, 0, 256.f * i + 256, 256 }; + context.setClipRect(box); + float boxRadius = 2 << i; + canvas.drawShadow(box.withPadding(64.f), boxRadius, 0.f, contourSize = 16.f, + contourColor = Palette::black); + } + }, + Palette::white); +} + } // namespace Brisk diff --git a/src/graphics/testdata/shadows-rounded.png b/src/graphics/testdata/shadows-rounded.png new file mode 100644 index 0000000..58e64ac Binary files /dev/null and b/src/graphics/testdata/shadows-rounded.png differ diff --git a/src/graphics/testdata/shadows.png b/src/graphics/testdata/shadows.png new file mode 100644 index 0000000..9f96d9d Binary files /dev/null and b/src/graphics/testdata/shadows.png differ diff --git a/src/gui/GUI.cpp b/src/gui/GUI.cpp index 98f5cf8..7f76f6b 100644 --- a/src/gui/GUI.cpp +++ b/src/gui/GUI.cpp @@ -2307,9 +2307,7 @@ void boxPainter(Canvas& canvas_, const Widget& widget, RectangleF rect) { std::max(std::abs(widget.borderRadius.resolved().max()), std::min(widget.shadowSize.resolved(), dp(8.f))), 0.f, contourSize = widget.shadowSize.resolved(), - contourColor = widget.shadowColor.current().multiplyAlpha(widget.opacity.get()), - contourFlags = 2 // outer shadow only - ); + contourColor = widget.shadowColor.current().multiplyAlpha(widget.opacity.get())); } if (m_backgroundColor != ColorF(0, 0, 0, 0) || !widget.computedBorderWidth().empty()) { @@ -3014,7 +3012,7 @@ Rectangle Widget::clipRect() const noexcept { } static Rectangle adjustForShadowSize(RectangleF rect, float shadowSize) { - RectangleF result = rect.withMargin(std::ceil(shadowSize * 2.f)); + RectangleF result = rect.withMargin(std::ceil(shadowSize + 1.f)); return result.roundOutward(); } diff --git a/src/widgets/Graphene.cpp b/src/widgets/Graphene.cpp index e786dfb..18be571 100644 --- a/src/widgets/Graphene.cpp +++ b/src/widgets/Graphene.cpp @@ -39,7 +39,7 @@ Rules darkColors() { menuColor = 0xFDFDFD_rgb, animationSpeed = 1.f, boxBorderColor = 0x000000'A0_rgba, - shadeColor = 0x000000'58_rgba, + shadeColor = 0x000000'88_rgba, deepColor = 0x000000_rgb, }; } @@ -55,7 +55,7 @@ Rules lightColors() { menuColor = 0xFDFDFD_rgb, animationSpeed = 1.f, boxBorderColor = 0x000000'1F_rgba, - shadeColor = 0x000000'34_rgba, + shadeColor = 0x000000'54_rgba, deepColor = 0x8D8D8D_rgb, }; }