};
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
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;
// 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)) {
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);
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;
#include "compute_bindings.h"
#include "draw_2d.h"
+#include "sdf.h"
struct Draw2dRasterizeConstants {
uvec2 screen_resolution;
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))) {
--- /dev/null
+#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
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;
#[derive(Clone, Copy)]
struct CmdGlyph {
packed: u32,
- x: f32,
- y: f32,
+ position: Vec2,
color: u32,
}
#[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,
},
}
#[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,
},
};
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;
}
}
- 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,
))
}
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;
}
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,
);
}
}
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 {