]> git.nega.tv - josh/narcissus/commitdiff
shark-shaders: Draw the rest of the rectangles
authorJosh Simmons <josh@nega.tv>
Thu, 14 Nov 2024 20:13:20 +0000 (21:13 +0100)
committerJosh Simmons <josh@nega.tv>
Thu, 14 Nov 2024 20:13:20 +0000 (21:13 +0100)
title/shark-shaders/shaders/draw_2d.h
title/shark-shaders/shaders/draw_2d_bin_1_scatter.comp
title/shark-shaders/shaders/draw_2d_bin_3_resolve.comp
title/shark-shaders/shaders/draw_2d_rasterize.comp
title/shark-shaders/shaders/sdf.h [new file with mode: 0644]
title/shark-shaders/src/pipelines.rs
title/shark/src/main.rs

index f79e3dd135de1e78a5c0c60d7c301937820f9054..37af77f0f57971f345be37a04bb05e8288d553aa 100644 (file)
@@ -25,35 +25,33 @@ struct Draw2dCmd {
 };
 
 struct Draw2dCmdRect {
-    float border_width;
-    vec2 position;
-    vec2 half_extent;
-    uint background_color;
+    vec2 bounds_min;
+    vec2 bounds_max;
+
+    uint border_radii;
     uint border_color;
+
+    uint background_color;
 };
 
 struct Draw2dCmdGlyph {
-    uint index;
     vec2 position;
     uint color;
 };
 
 Draw2dCmdRect decode_rect(Draw2dCmd cmd) {
-    return Draw2dCmdRect(
-        uintBitsToFloat(cmd.words[0]),
-        vec2(uintBitsToFloat(cmd.words[1]), uintBitsToFloat(cmd.words[2])),
-        vec2(uintBitsToFloat(cmd.words[3]), uintBitsToFloat(cmd.words[4])),
-        cmd.words[5],
-        cmd.words[6]
-    );
+    Draw2dCmdRect rect = {
+        { uintBitsToFloat(cmd.words[0]), uintBitsToFloat(cmd.words[1]) }, // bounds_min
+        { uintBitsToFloat(cmd.words[2]), uintBitsToFloat(cmd.words[3]) }, // bounds_max
+        cmd.words[4], // border_radii
+        cmd.words[5], // border_color
+        cmd.words[6], // background_color
+    };
+    return rect;
 }
 
-Draw2dCmdGlyph decode_glyph(Draw2dCmd cmd) {
-    return Draw2dCmdGlyph(
-        cmd.packed_type & 0xffffff,
-        vec2(uintBitsToFloat(cmd.words[0]), uintBitsToFloat(cmd.words[1])),
-        cmd.words[2]
-    );
+Draw2dCmdGlyph decode_glyph(in Draw2dCmd cmd) {
+    return Draw2dCmdGlyph(vec2(uintBitsToFloat(cmd.words[0]), uintBitsToFloat(cmd.words[1])), cmd.words[2]);
 }
 
 layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer Draw2dCommandRef
index c9cfbb65a2289fd5af2e7a0e66c1057d8dbcee92..ad4ad57248b68586b2dbe07192016b2ae8b974eb 100644 (file)
@@ -46,21 +46,23 @@ void main() {
     vec2 cmd_min = vec2(99999.9);
     vec2 cmd_max = vec2(-99999.9);
     if (in_bounds) {
-        const Draw2dCmd cmd = constants.draw_buffer.values[draw_index];
-        const uint cmd_type = cmd.packed_type >> 24;
+        const uint packed_type = constants.draw_buffer.values[draw_index].packed_type;
+        const uint cmd_type = packed_type >> 24;
+        const uint cmd_packed = packed_type & 0xffffff;
+
         for (;;) {
             const uint scalar_type = subgroupBroadcastFirst(cmd_type);
             [[branch]]
             if (scalar_type == cmd_type) {
                 switch (scalar_type) {
                     case DRAW_2D_CMD_RECT:
-                        const Draw2dCmdRect cmd_rect = decode_rect(cmd);
-                        cmd_min = cmd_rect.position - cmd_rect.half_extent - cmd_rect.border_width;
-                        cmd_max = cmd_rect.position + cmd_rect.half_extent + cmd_rect.border_width;
+                        const Draw2dCmdRect cmd_rect = decode_rect(constants.draw_buffer.values[draw_index]);
+                        cmd_min = cmd_rect.bounds_min;
+                        cmd_max = cmd_rect.bounds_max;
                         break;
                     case DRAW_2D_CMD_GLYPH:
-                        const Draw2dCmdGlyph cmd_glyph = decode_glyph(cmd);
-                        const Glyph glyph = constants.glyph_buffer.values[cmd_glyph.index];
+                        const Draw2dCmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[draw_index]);
+                        const Glyph glyph = constants.glyph_buffer.values[cmd_packed];
                         cmd_min = cmd_glyph.position + glyph.offset_min;
                         cmd_max = cmd_glyph.position + glyph.offset_max;
                         break;
@@ -73,7 +75,7 @@ void main() {
     // For any out-of-bounds draws, we'll get the defaults of 99999.9 and -99999.9, which will fail
     // here. Out-of-bounds draws are therefore off-screen. Well, so long as you don't have 27 4k
     // monitors arranged horizontally.
-    const bool offscreen = any(greaterThan(cmd_min, constants.screen_resolution)) || any(lessThan(cmd_max, vec2(0.0)));
+    const bool offscreen = any(greaterThanEqual(cmd_min, cmd_max)) || any(greaterThan(cmd_min, constants.screen_resolution)) || any(lessThan(cmd_max, vec2(0.0)));
 
     // Are all draws off-screen?
     if (subgroupAll(offscreen)) {
index bde745d97b4231fcadcc633cd0e45ff70e3662f7..43fc0d928832ac838bd4849dfdbf7434f3df902e 100644 (file)
@@ -84,8 +84,9 @@ void main() {
             vec2 cmd_min = vec2(99999.9);
             vec2 cmd_max = vec2(-99999.9);
 
-            const Draw2dCmd cmd = constants.draw_buffer.values[draw_index];
-            const uint cmd_type = cmd.packed_type >> 24;
+            const uint packed_type = constants.draw_buffer.values[draw_index].packed_type;
+            const uint cmd_type = packed_type >> 24;
+            const uint cmd_packed = packed_type & 0xffffff;
 
             for (;;) {
                 const uint scalar_type = subgroupBroadcastFirst(cmd_type);
@@ -93,15 +94,20 @@ void main() {
                 if (scalar_type == cmd_type) {
                     switch (scalar_type) {
                         case DRAW_2D_CMD_RECT:
-                            const Draw2dCmdRect cmd_rect = decode_rect(cmd);
-                            cmd_min = cmd_rect.position - cmd_rect.half_extent - cmd_rect.border_width;
-                            cmd_max = cmd_rect.position + cmd_rect.half_extent + cmd_rect.border_width;
-                            opaque_tile = all(greaterThanEqual(tile_min, cmd_min)) && all(lessThanEqual(tile_max, cmd_max));
-                            opaque_tile = opaque_tile && ((cmd_rect.background_color & 0xff000000) == 0xff000000);
+                            const Draw2dCmdRect cmd_rect = decode_rect(constants.draw_buffer.values[draw_index]);
+                            cmd_min = cmd_rect.bounds_min;
+                            cmd_max = cmd_rect.bounds_max;
+                            if ((cmd_rect.background_color & 0xff000000) == 0xff000000) {
+                                const float border_width = float(cmd_packed & 0xff);
+                                const vec4 border_radii = unpackUnorm4x8(cmd_rect.border_radii);
+                                const float max_border_radius = max(border_radii.x, max(border_radii.y, max(border_radii.z, border_radii.w))) * 255.0;
+                                const float shrink = (2.0 - sqrt(2.0)) * max_border_radius + (cmd_rect.border_color & 0xff000000) == 0xff000000 ? 0.0 : border_width;
+                                opaque_tile = all(greaterThanEqual(tile_min, cmd_min + shrink)) && all(lessThanEqual(tile_max, cmd_max - shrink));
+                            }
                             break;
                         case DRAW_2D_CMD_GLYPH:
-                            const Draw2dCmdGlyph cmd_glyph = decode_glyph(cmd);
-                            const Glyph glyph = constants.glyph_buffer.values[cmd_glyph.index];
+                            const Draw2dCmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[draw_index]);
+                            const Glyph glyph = constants.glyph_buffer.values[cmd_packed];
                             cmd_min = cmd_glyph.position + glyph.offset_min;
                             cmd_max = cmd_glyph.position + glyph.offset_max;
                             break;
index d36bf76da86adf55d88c53c9154b8aaca064fec8..4ba591ce8fe4ba9341a710802b45865faeab88e0 100644 (file)
@@ -12,6 +12,7 @@
 
 #include "compute_bindings.h"
 #include "draw_2d.h"
+#include "sdf.h"
 
 struct Draw2dRasterizeConstants {
     uvec2 screen_resolution;
@@ -87,22 +88,42 @@ void main() {
             bitmap ^= bitmap & -bitmap;
 
             const uint base_index = (constants.coarse_buffer.values[i] & 0xffff) * 32;
-            const Draw2dCmd cmd = constants.draw_buffer.values[base_index + index];
-            const uint cmd_type = cmd.packed_type >> 24;
+            const uint packed_type = constants.draw_buffer.values[base_index + index].packed_type;
+            const uint cmd_type = packed_type >> 24;
+            const uint cmd_packed = packed_type & 0xffffff;
 
             vec4 primitive_color = vec4(0.0);
             switch (cmd_type) {
                 case DRAW_2D_CMD_RECT:
-                    const Draw2dCmdRect cmd_rect = decode_rect(cmd);
-                    const vec2 rect_min = cmd_rect.position - cmd_rect.half_extent - cmd_rect.border_width;
-                    const vec2 rect_max = cmd_rect.position + cmd_rect.half_extent + cmd_rect.border_width;
-                    if (all(greaterThanEqual(sample_center, rect_min)) && all(lessThanEqual(sample_center, rect_max))) {
+                    const Draw2dCmdRect cmd_rect = decode_rect(constants.draw_buffer.values[base_index + index]);
+
+                    const float border_width = float(cmd_packed & 0xff);
+                    const vec4 border_radii = unpackUnorm4x8(cmd_rect.border_radii) * 255.0;
+                    const float max_border_radius = max(border_radii.x, max(border_radii.y, max(border_radii.z, border_radii.w)));
+                    const float shrink = (2.0 - sqrt(2.0)) * max_border_radius;
+
+                    if (all(greaterThan(sample_center, cmd_rect.bounds_min + border_width + shrink)) && all(lessThan(sample_center, cmd_rect.bounds_max - border_width - shrink))) {
                         primitive_color = unpackUnorm4x8(cmd_rect.background_color).bgra;
+                    } else {
+                        const vec2 b = (cmd_rect.bounds_max - cmd_rect.bounds_min) / 2.0;
+                        const vec2 p = cmd_rect.bounds_min + b - sample_center;
+
+                        float d;
+                        if (all(equal(border_radii, vec4(0.0)))) {
+                            d = sdf_box(p, b);
+                        } else {
+                            d = sdf_rounded_box(p, b, border_radii);
+                        }
+
+                        const vec4 background_color = unpackUnorm4x8(cmd_rect.background_color).bgra;
+                        const vec4 border_color = unpackUnorm4x8(cmd_rect.border_color).bgra;
+                        primitive_color = mix(background_color, border_color, smoothstep(1.0, 0.0, 1.0 - d - border_width));
+                        primitive_color = mix(primitive_color, vec4(0), smoothstep(1.0, 0.0, 1.0 - d));
                     }
                     break;
                 case DRAW_2D_CMD_GLYPH:
-                    const Draw2dCmdGlyph cmd_glyph = decode_glyph(cmd);
-                    const Glyph glyph = constants.glyph_buffer.values[cmd_glyph.index];
+                    const Draw2dCmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[base_index + index]);
+                    const Glyph glyph = constants.glyph_buffer.values[cmd_packed];
                     const vec2 glyph_min = cmd_glyph.position + glyph.offset_min;
                     const vec2 glyph_max = cmd_glyph.position + glyph.offset_max;
                     if (all(greaterThanEqual(sample_center, glyph_min)) && all(lessThanEqual(sample_center, glyph_max))) {
diff --git a/title/shark-shaders/shaders/sdf.h b/title/shark-shaders/shaders/sdf.h
new file mode 100644 (file)
index 0000000..a7b71c6
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef SDF_H
+#define SDF_H
+
+// https://iquilezles.org/articles/distfunctions2d/
+
+float sdf_box(in vec2 p, in vec2 b)
+{
+    const vec2 d = abs(p) - b;
+    return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
+}
+
+float sdf_rounded_box( in vec2 p, in vec2 b, in vec4 r )
+{
+    r.xy = (p.x > 0.0) ? r.xy : r.zw;
+    r.x  = (p.y > 0.0) ? r.x : r.y;
+    const vec2 q = abs(p) - b + r.x;
+    return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
+}
+
+#endif
\ No newline at end of file
index 9809d0f4d807c5323c93b5075432872205a7809b..b3c314b374044d2dbeb74d144e874facc61d5bf3 100644 (file)
@@ -7,7 +7,7 @@ use narcissus_gpu::{
     Sampler, SamplerAddressMode, SamplerDesc, SamplerFilter, ShaderDesc, ShaderStageFlags,
     SpecConstant, Topology,
 };
-use narcissus_maths::Mat4;
+use narcissus_maths::{Mat4, Vec2};
 
 pub const DRAW_2D_TILE_SIZE: u32 = 32;
 
@@ -38,8 +38,7 @@ const _: () = assert!(std::mem::size_of::<Draw2dCmd>() == 32);
 #[derive(Clone, Copy)]
 struct CmdGlyph {
     packed: u32,
-    x: f32,
-    y: f32,
+    position: Vec2,
     color: u32,
 }
 
@@ -48,27 +47,32 @@ const _: () = assert!(std::mem::size_of::<CmdGlyph>() == 16);
 #[repr(C)]
 #[derive(Clone, Copy)]
 struct CmdRect {
+    /// 31       .          .          .          0
+    ///  tttt tttt  0000 0000  0000 0000  bbbb bbbb
+    ///
+    /// t: Type
+    /// b: Border width
     packed: u32,
-    border_width: f32,
-    x: f32,
-    y: f32,
-    half_extent_x: f32,
-    half_extent_y: f32,
-    background_color: u32,
+
+    bounds_min: Vec2,
+    bounds_max: Vec2,
+
+    border_radii: u32,
     border_color: u32,
+
+    background_color: u32,
 }
 
 const _: () = assert!(std::mem::size_of::<CmdRect>() == 32);
 
 impl Draw2dCmd {
     #[inline(always)]
-    pub fn glyph(touched_glyph_index: TouchedGlyphIndex, color: u32, x: f32, y: f32) -> Self {
+    pub fn glyph(touched_glyph_index: TouchedGlyphIndex, color: u32, position: Vec2) -> Self {
         Self {
             glyph: CmdGlyph {
                 packed: (Draw2dCmdType::Glyph as u32) << 24
                     | (touched_glyph_index.as_u32() & 0xffffff),
-                x,
-                y,
+                position,
                 color,
             },
         }
@@ -76,22 +80,19 @@ impl Draw2dCmd {
 
     #[inline(always)]
     pub fn rect(
-        x: f32,
-        y: f32,
-        half_extent_x: f32,
-        half_extent_y: f32,
-        border_width: f32,
-        background_color: u32,
+        bounds_min: Vec2,
+        bounds_max: Vec2,
+        border_width: u8,
+        border_radii: [u8; 4],
         border_color: u32,
+        background_color: u32,
     ) -> Self {
         Self {
             rect: CmdRect {
-                packed: (Draw2dCmdType::Rect as u32) << 24,
-                border_width,
-                x,
-                y,
-                half_extent_x,
-                half_extent_y,
+                packed: (Draw2dCmdType::Rect as u32) << 24 | border_width as u32,
+                bounds_min,
+                bounds_max,
+                border_radii: u32::from_ne_bytes(border_radii),
                 background_color,
                 border_color,
             },
index d0eed25c29ea17f985f77ebf0d22373399230fc0..bfa98065aa74890817d62aa53bd0bfb2fe225605 100644 (file)
@@ -29,8 +29,8 @@ use narcissus_gpu::{
 };
 use narcissus_image as image;
 use narcissus_maths::{
-    clamp, perlin_noise3, sin_cos_pi_f32, sin_pi_f32, vec3, Affine3, Deg, HalfTurn, Mat3, Mat4,
-    Point3, Vec3,
+    clamp, perlin_noise3, sin_cos_pi_f32, sin_pi_f32, vec2, vec3, Affine3, Deg, HalfTurn, Mat3,
+    Mat4, Point3, Vec2, Vec3,
 };
 use spring::simple_spring_damper_exact;
 
@@ -469,20 +469,28 @@ impl<'a> UiState<'a> {
         }
     }
 
-    fn rect(&mut self, x: f32, y: f32, width: f32, height: f32, background_color: u32) {
-        let half_extent_x = width / 2.0;
-        let half_extent_y = height / 2.0;
-        let center_x = x + half_extent_x;
-        let center_y = y + half_extent_y;
+    fn rect(
+        &mut self,
+        position: Vec2,
+        bounds: Vec2,
+        border_width: f32,
+        border_radii: [f32; 4],
+        border_color: u32,
+        background_color: u32,
+    ) {
+        let bounds_min = position;
+        let bounds_max = position + bounds;
+
+        let border_width = border_width.clamp(0.0, 255.0).floor() as u8;
+        let border_radii = border_radii.map(|radius| radius.clamp(0.0, 255.0).floor() as u8);
 
         self.draw_cmds.push(Draw2dCmd::rect(
-            center_x,
-            center_y,
-            half_extent_x,
-            half_extent_y,
-            5.0,
+            bounds_min,
+            bounds_max,
+            border_width,
+            border_radii,
+            border_color,
             background_color,
-            0xffff0000,
         ))
     }
 
@@ -526,8 +534,11 @@ impl<'a> UiState<'a> {
 
             x += advance * scale;
 
-            self.draw_cmds
-                .push(Draw2dCmd::glyph(touched_glyph_index, 0xff0000ff, x, y));
+            self.draw_cmds.push(Draw2dCmd::glyph(
+                touched_glyph_index,
+                microshades::GRAY_RGBA8[4].rotate_right(8),
+                vec2(x, y),
+            ));
 
             x += advance_width * scale;
         }
@@ -1799,11 +1810,12 @@ pub fn main() {
 
             for _ in 0..100 {
                 ui_state.rect(
-                    100.0,
-                    100.0,
-                    width as f32 - 200.0,
-                    height as f32 - 200.0,
-                    0x88008800,
+                    vec2(100.0, 100.0),
+                    vec2(1000.0, 1000.0),
+                    0.0,
+                    [25.0; 4],
+                    0,
+                    0x88442211,
                 );
             }
 
@@ -1821,16 +1833,27 @@ pub fn main() {
             }
 
             for i in 0..500 {
-                let (rect_x, rect_y) = sin_cos_pi_f32(game_state.time * 0.1 + i as f32 * 0.01);
+                let (rect_x, rect_y) = sin_cos_pi_f32(game_state.time * 0.1 + i as f32 * 1.01);
                 ui_state.rect(
-                    (width as f32 / 2.0) - rect_x * 1000.0,
-                    (height as f32 / 2.0) - rect_y * 1000.0,
-                    500.0,
-                    500.0,
-                    0xff00ff00,
+                    (vec2(width as f32, height as f32) / 2.0)
+                        - 250.0
+                        - vec2(rect_x, rect_y) * 1000.0,
+                    vec2(500.0, 500.0),
+                    10.0,
+                    [rect_x * 50.0, rect_y * 50.0, 25.0, 25.0],
+                    0xffffffff,
+                    microshades::BLUE_RGBA8[4].rotate_right(8),
                 );
             }
-            ui_state.rect(base_x * 60.0, base_y * 60.0, 100.0, 100.0, 0xffff0000);
+
+            ui_state.rect(
+                vec2(base_x, base_y) * 60.0,
+                vec2(400.0, 400.0),
+                0.0,
+                [0.0; 4],
+                0,
+                microshades::ORANGE_RGBA8[2].rotate_right(8),
+            );
 
             for i in 0..10 {
                 if i & 1 != 0 {