]> git.nega.tv - josh/narcissus/commitdiff
shark: Add support for clipping
authorJosh Simmons <josh@nega.tv>
Sun, 17 Nov 2024 19:48:07 +0000 (20:48 +0100)
committerJosh Simmons <josh@nega.tv>
Sun, 17 Nov 2024 19:50:18 +0000 (20:50 +0100)
14 files changed:
engine/narcissus-gpu/src/backend/vulkan/mod.rs
engine/narcissus-gpu/src/lib.rs
title/shark-shaders/shaders/basic.frag
title/shark-shaders/shaders/bindings_compute.h [moved from title/shark-shaders/shaders/compute_bindings.h with 74% similarity]
title/shark-shaders/shaders/bindings_graphics.h [new file with mode: 0644]
title/shark-shaders/shaders/composite.comp
title/shark-shaders/shaders/draw_2d.h
title/shark-shaders/shaders/draw_2d_bin_0_clear.comp
title/shark-shaders/shaders/draw_2d_bin_1_scatter.comp
title/shark-shaders/shaders/draw_2d_bin_2_sort.comp
title/shark-shaders/shaders/draw_2d_bin_3_resolve.comp
title/shark-shaders/shaders/draw_2d_rasterize.comp
title/shark-shaders/src/pipelines.rs
title/shark/src/main.rs

index d0408ce26fe791096780834e7de2d1076912bf10..2d60c00d2ef296329d29f0e025bd741b9cac55a1 100644 (file)
@@ -1200,6 +1200,8 @@ impl Device for VulkanDevice {
             Some(SamplerCompareOp::GreaterEq) => (vk::Bool32::True, vk::CompareOp::GreaterOrEqual),
         };
 
+        let unnormalized_coordinates = sampler_desc.unnormalized_coordinates.into();
+
         let mut sampler = vk::Sampler::null();
         vk_check!(self.device_fn.create_sampler(
             self.device,
@@ -1217,6 +1219,7 @@ impl Device for VulkanDevice {
                 address_mode_w: address_mode,
                 compare_enable,
                 compare_op,
+                unnormalized_coordinates,
                 ..default()
             },
             None,
index 7cab81b449a807331ef12f2e56b92a38e8ab25c9..a2b2cab98de083046f1c04d25c08a081b78ca091 100644 (file)
@@ -332,6 +332,7 @@ pub struct SamplerDesc {
     pub mip_lod_bias: f32,
     pub min_lod: f32,
     pub max_lod: f32,
+    pub unnormalized_coordinates: bool,
 }
 
 #[derive(Clone, Copy, PartialEq, Eq)]
index 1693e95b1a476ea169996f0c4996d06d98535c6f..23b4b03f862f53001ba9da53a0bff84a605c4494 100644 (file)
@@ -1,7 +1,8 @@
 #version 460
 
-layout(set = 0, binding = 0) uniform sampler bilinear_sampler;
-layout(set = 0, binding = 1) uniform texture2D albedo;
+#extension GL_GOOGLE_include_directive : require
+
+#include "bindings_graphics.h"
 
 layout(location = 0) in vec2 tex_coord;
 layout(location = 1) in vec3 normal;
@@ -9,6 +10,6 @@ layout(location = 0) out vec4 out_color;
 
 void main() {
     const float n_dot_l = max(dot(normal, vec3(0.0, 1.0, 0.0)), 0.1);
-    const vec3 rgb = texture(sampler2D(albedo, bilinear_sampler), vec2(tex_coord.x, tex_coord.y)).rgb;
+    const vec3 rgb = texture(sampler2D(albedo, samplers[SAMPLER_BILINEAR]), vec2(tex_coord.x, tex_coord.y)).rgb;
     out_color = vec4(rgb * n_dot_l, 1.0);
 }
similarity index 74%
rename from title/shark-shaders/shaders/compute_bindings.h
rename to title/shark-shaders/shaders/bindings_compute.h
index dc694d547ac65e5b3f31daeae9b341e2571a4b2f..5792348e6698e527e99a0ae6648ebf95af33734d 100644 (file)
@@ -1,7 +1,11 @@
 #ifndef COMPUTE_BINDINGS_INCLUDE
 #define COMPUTE_BINDINGS_INCLUDE
 
-layout (set = 0, binding = 0) uniform sampler bilinear_sampler;
+const uint SAMPLER_BILINEAR = 0;
+const uint SAMPLER_BILINEAR_UNNORMALIZED = 1;
+const uint SAMPLER_COUNT = 2;
+
+layout (set = 0, binding = 0) uniform sampler samplers[SAMPLER_COUNT];
 layout (set = 0, binding = 1) uniform texture3D tony_mc_mapface_lut;
 layout (set = 0, binding = 2) uniform texture2D glyph_atlas;
 layout (set = 0, binding = 3, rgba16f) uniform writeonly image2D ui_layer_write;
diff --git a/title/shark-shaders/shaders/bindings_graphics.h b/title/shark-shaders/shaders/bindings_graphics.h
new file mode 100644 (file)
index 0000000..b9fb83c
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef GRAPHICS_BINDINGS_INCLUDE
+#define GRAPHICS_BINDINGS_INCLUDE
+
+const uint SAMPLER_BILINEAR = 0;
+const uint SAMPLER_BILINEAR_UNNORMALIZED = 1;
+const uint SAMPLER_COUNT = 2;
+
+layout (set = 0, binding = 0) uniform sampler samplers[SAMPLER_COUNT];
+layout (set = 0, binding = 1) uniform texture2D albedo;
+
+#endif
index 26be8387f66457e4091537a9b539328fb6adf033..8c153c84c276d8ca8b37f5c84b62e71813377e94 100644 (file)
@@ -6,7 +6,7 @@
 #extension GL_EXT_buffer_reference2 : require
 #extension GL_EXT_scalar_block_layout : require
 
-#include "compute_bindings.h"
+#include "bindings_compute.h"
 #include "draw_2d.h"
 
 float srgb_oetf(float a) {
@@ -21,7 +21,7 @@ vec3 tony_mc_mapface(vec3 stimulus) {
     const vec3 encoded = stimulus / (stimulus + 1.0);
     const float LUT_DIMS = 48.0;
     const vec3 uv = (encoded * ((LUT_DIMS - 1.0) / LUT_DIMS) + 0.5 / LUT_DIMS);
-    return textureLod(sampler3D(tony_mc_mapface_lut, bilinear_sampler), uv, 0.0).rgb;
+    return textureLod(sampler3D(tony_mc_mapface_lut, samplers[SAMPLER_BILINEAR]), uv, 0.0).rgb;
 }
 
 struct CompositeConstants {
@@ -39,8 +39,8 @@ void main() {
     const uvec2 tile_coord = gl_WorkGroupID.xy / (TILE_SIZE / gl_WorkGroupSize.xy);
     const uint tile_index = tile_coord.y * constants.tile_resolution.x + tile_coord.x;
 
-    const uint lo = constants.tile_buffer.values[tile_index].min_index;
-    const uint hi = constants.tile_buffer.values[tile_index].max_index;
+    const uint lo = constants.tile_buffer.values[tile_index].index_min;
+    const uint hi = constants.tile_buffer.values[tile_index].index_max;
 
     // Display transform
     const vec3 stimulus = imageLoad(color_layer, ivec2(gl_GlobalInvocationID.xy)).rgb;
index 37af77f0f57971f345be37a04bb05e8288d553aa..bcef539bc119b5dbe04c395928b6d93ac9c571cd 100644 (file)
@@ -7,8 +7,8 @@ const uint DRAW_2D_CMD_RECT = 0;
 const uint DRAW_2D_CMD_GLYPH = 1;
 
 struct Tile {
-    uint min_index;
-    uint max_index;
+    uint index_min;
+    uint index_max;
 };
 
 struct Glyph {
@@ -19,14 +19,19 @@ struct Glyph {
     vec2 offset_max;
 };
 
-struct Draw2dCmd {
+struct Scissor {
+    vec2 offset_min;
+    vec2 offset_max;
+};
+
+struct Cmd {
     uint packed_type;
     uint words[7];
 };
 
-struct Draw2dCmdRect {
-    vec2 bounds_min;
-    vec2 bounds_max;
+struct CmdRect {
+    vec2 position;
+    vec2 bound;
 
     uint border_radii;
     uint border_color;
@@ -34,15 +39,16 @@ struct Draw2dCmdRect {
     uint background_color;
 };
 
-struct Draw2dCmdGlyph {
+struct CmdGlyph {
+    uint index;
     vec2 position;
     uint color;
 };
 
-Draw2dCmdRect decode_rect(Draw2dCmd cmd) {
-    Draw2dCmdRect rect = {
-        { uintBitsToFloat(cmd.words[0]), uintBitsToFloat(cmd.words[1]) }, // bounds_min
-        { uintBitsToFloat(cmd.words[2]), uintBitsToFloat(cmd.words[3]) }, // bounds_max
+CmdRect decode_rect(Cmd cmd) {
+    CmdRect rect = {
+        { uintBitsToFloat(cmd.words[0]), uintBitsToFloat(cmd.words[1]) }, // position
+        { uintBitsToFloat(cmd.words[2]), uintBitsToFloat(cmd.words[3]) }, // bound
         cmd.words[4], // border_radii
         cmd.words[5], // border_color
         cmd.words[6], // background_color
@@ -50,13 +56,23 @@ Draw2dCmdRect decode_rect(Draw2dCmd cmd) {
     return rect;
 }
 
-Draw2dCmdGlyph decode_glyph(in Draw2dCmd cmd) {
-    return Draw2dCmdGlyph(vec2(uintBitsToFloat(cmd.words[0]), uintBitsToFloat(cmd.words[1])), cmd.words[2]);
+CmdGlyph decode_glyph(Cmd cmd) {
+    CmdGlyph glyph = {
+        cmd.words[0], // index
+        { uintBitsToFloat(cmd.words[1]), uintBitsToFloat(cmd.words[2]) }, // position
+        cmd.words[3], // color
+    };
+    return glyph;
 }
 
-layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer Draw2dCommandRef
+layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer CommandRef
+{
+    Cmd values[];
+};
+
+layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer ScissorRef
 {
-    Draw2dCmd values[];
+    Scissor values[];
 };
 
 layout(buffer_reference, std430, buffer_reference_align = 16) readonly buffer GlyphRef
index 9f3501b5fbd0ddae52aa332ac34909f2bdb2c94c..f5fb9b91f49ad34149c49eb4c897e12f46339409 100644 (file)
 #include "draw_2d.h"
 #include "radix_sort.h"
 
-struct Draw2dClearConstants {
+struct ClearConstants {
     FinishedRef finished_buffer;
     CoarseRef coarse_buffer;
 };
 
-layout(std430, push_constant) uniform Draw2dClearConstantsBlock {
-    Draw2dClearConstants constants;
+layout(std430, push_constant) uniform ClearConstantsBlock {
+    ClearConstants constants;
 };
 
 layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
index ad4ad57248b68586b2dbe07192016b2ae8b974eb..f3cbef0af62745efb78bd9c15615c331a09384fb 100644 (file)
 
 #include "draw_2d.h"
 
-struct Draw2dScatterConstants {
-    uvec2 screen_resolution;
+struct ScatterConstants {
     uvec2 tile_resolution;
 
     uint draw_buffer_len;
     uint coarse_buffer_len;
 
-    Draw2dCommandRef draw_buffer;
+    CommandRef draw_buffer;
+    ScissorRef scissor_buffer;
     GlyphRef glyph_buffer;
     CoarseRef coarse_buffer;
 };
 
-layout(std430, push_constant) uniform Draw2dScatterConstantsBlock {
-    Draw2dScatterConstants constants;
+layout(std430, push_constant) uniform ScatterConstantsBlock {
+    ScatterConstants constants;
 };
 
 const uint MAX_TILES = 256;
@@ -43,12 +43,13 @@ void main() {
 
     const bool in_bounds = draw_index < constants.draw_buffer_len;
 
+    uint cmd_scissor = 0;
     vec2 cmd_min = vec2(99999.9);
     vec2 cmd_max = vec2(-99999.9);
     if (in_bounds) {
         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;
+        cmd_scissor = packed_type & 0xffff;
 
         for (;;) {
             const uint scalar_type = subgroupBroadcastFirst(cmd_type);
@@ -56,13 +57,13 @@ void main() {
             if (scalar_type == cmd_type) {
                 switch (scalar_type) {
                     case DRAW_2D_CMD_RECT:
-                        const Draw2dCmdRect cmd_rect = decode_rect(constants.draw_buffer.values[draw_index]);
-                        cmd_min = cmd_rect.bounds_min;
-                        cmd_max = cmd_rect.bounds_max;
+                        const CmdRect cmd_rect = decode_rect(constants.draw_buffer.values[draw_index]);
+                        cmd_min = cmd_rect.position;
+                        cmd_max = cmd_rect.position + cmd_rect.bound;
                         break;
                     case DRAW_2D_CMD_GLYPH:
-                        const Draw2dCmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[draw_index]);
-                        const Glyph glyph = constants.glyph_buffer.values[cmd_packed];
+                        const CmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[draw_index]);
+                        const Glyph glyph = constants.glyph_buffer.values[cmd_glyph.index];
                         cmd_min = cmd_glyph.position + glyph.offset_min;
                         cmd_max = cmd_glyph.position + glyph.offset_max;
                         break;
@@ -72,27 +73,29 @@ 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(greaterThanEqual(cmd_min, cmd_max)) || any(greaterThan(cmd_min, constants.screen_resolution)) || any(lessThan(cmd_max, vec2(0.0)));
+    const Scissor scissor = constants.scissor_buffer.values[cmd_scissor];
+
+    const bool out_of_bounds = any(greaterThanEqual(cmd_min, cmd_max)) || any(greaterThan(cmd_min, scissor.offset_max)) || any(lessThan(cmd_max, scissor.offset_min));
 
     // Are all draws off-screen?
-    if (subgroupAll(offscreen)) {
+    if (subgroupAll(out_of_bounds)) {
         return;
     }
 
+    cmd_min = max(cmd_min, scissor.offset_min);
+    cmd_max = min(cmd_max, scissor.offset_max);
+
     // Make sure off-screen commands don't contribute to the bounds.
-    const uvec2 cmds_min_tile = uvec2(clamp(subgroupMin(offscreen ? ivec2(999999) : ivec2(floor(cmd_min / TILE_SIZE))), ivec2(0), ivec2(constants.tile_resolution)));
-    const uvec2 cmds_max_tile = uvec2(clamp(subgroupMax(offscreen ? ivec2(-999999) : ivec2(floor(cmd_max / TILE_SIZE))), ivec2(0), ivec2(constants.tile_resolution)));
-    const uvec2 cmd_min_tile = uvec2(clamp(ivec2(floor(cmd_min / TILE_SIZE)), ivec2(0), ivec2(constants.tile_resolution)));
-    const uvec2 cmd_max_tile = uvec2(clamp(ivec2(floor(cmd_max / TILE_SIZE)), ivec2(0), ivec2(constants.tile_resolution)));
+    const uvec2 cmds_tile_min = uvec2(clamp(subgroupMin(out_of_bounds ? ivec2(999999) : ivec2(floor(cmd_min / TILE_SIZE))), ivec2(0), constants.tile_resolution));
+    const uvec2 cmds_tile_max = uvec2(clamp(subgroupMax(out_of_bounds ? ivec2(-999999) : ivec2(floor(cmd_max / TILE_SIZE))), ivec2(0), constants.tile_resolution));
+    const uvec2 cmd_tile_min = uvec2(clamp(ivec2(floor(cmd_min / TILE_SIZE)), ivec2(0), constants.tile_resolution));
+    const uvec2 cmd_tile_max = uvec2(clamp(ivec2(floor(cmd_max / TILE_SIZE)), ivec2(0), constants.tile_resolution));
 
-    const bool cmd_dominates_bounds = all(equal(cmd_min_tile, cmds_min_tile)) && all(equal(cmd_max_tile, cmds_max_tile));
+    const bool cmd_dominates_bounds = all(equal(cmd_tile_min, cmds_tile_min)) && all(equal(cmd_tile_max, cmds_tile_max));
     const bool use_combined_bounds = subgroupAny(cmd_dominates_bounds);
 
     if (use_combined_bounds) {
-        const uvec2 tile_count = cmds_max_tile - cmds_min_tile + ivec2(1);
+        const uvec2 tile_count = cmds_tile_max - cmds_tile_min + ivec2(1);
 
         uint offset;
         if (subgroupElect()) {
@@ -103,8 +106,8 @@ void main() {
         for (uint i = 0; i < tile_count.y; i++) {
             for (uint j = 0; j < tile_count.x; j += gl_SubgroupSize) {
                 const uint jj = j + gl_SubgroupInvocationID;
-                const uint y = cmds_min_tile.y + i;
-                const uint x = cmds_min_tile.x + jj;
+                const uint y = cmds_tile_min.y + i;
+                const uint x = cmds_tile_min.x + jj;
                 if (jj < tile_count.x) {
                     const uint packed = ((y & 0xff) << 24) | ((x & 0xff) << 16) | (gl_WorkGroupID.x & 0xffff);
                     const uint index = offset + i * tile_count.x + jj;
@@ -115,8 +118,8 @@ void main() {
             }
         }
     } else {
-        const uint start = cmds_min_tile.y * BITMAP_STRIDE + cmds_min_tile.x / 32;
-        const uint end = cmds_max_tile.y * BITMAP_STRIDE + cmds_max_tile.x / 32;
+        const uint start = cmds_tile_min.y * BITMAP_STRIDE + cmds_tile_min.x / 32;
+        const uint end = cmds_tile_max.y * BITMAP_STRIDE + cmds_tile_max.x / 32;
 
         for (uint i = start; i <= end; i += gl_SubgroupSize) {
             const uint ii = i + gl_SubgroupInvocationID;
@@ -127,19 +130,19 @@ void main() {
 
         subgroupBarrier();
 
-        if (!offscreen) {
-            const uint min_word = cmd_min_tile.x / 32;
-            const uint max_word = cmd_max_tile.x / 32;
-            const uint min_bit = cmd_min_tile.x & 31;
-            const uint max_bit = cmd_max_tile.x & 31;
+        if (!out_of_bounds) {
+            const uint min_word = cmd_tile_min.x / 32;
+            const uint max_word = cmd_tile_max.x / 32;
+            const uint min_bit = cmd_tile_min.x & 31;
+            const uint max_bit = cmd_tile_max.x & 31;
             const uint lsb = ~((1 << min_bit) - 1);
             const uint msb = ((1 << max_bit) - 1) | 1 << max_bit;
             if (min_word == max_word) {
-                for (uint y = cmd_min_tile.y; y <= cmd_max_tile.y; y++) {
+                for (uint y = cmd_tile_min.y; y <= cmd_tile_max.y; y++) {
                     atomicOr(intersected_tiles[y * BITMAP_STRIDE + min_word], lsb & msb);
                 }
             } else {
-                for (uint y = cmd_min_tile.y; y <= cmd_max_tile.y; y++) {
+                for (uint y = cmd_tile_min.y; y <= cmd_tile_max.y; y++) {
                     atomicOr(intersected_tiles[y * BITMAP_STRIDE + min_word], lsb);
                     for (uint i = min_word + 1; i <= (max_word - 1); i++) {
                         intersected_tiles[y * BITMAP_STRIDE + i] = 0xffffffff;
index 5a99c222dc4f72cbd76276c32c27e0565d4e25d4..3df9805970e1438cdb8a346b06bf76e88e96a062 100644 (file)
@@ -12,8 +12,6 @@
 #extension GL_KHR_shader_subgroup_shuffle_relative: enable
 #extension GL_KHR_shader_subgroup_vote : require
 
-#include "compute_bindings.h"
-
 #include "draw_2d.h"
 #include "indirect.h"
 #include "radix_sort.h"
@@ -22,15 +20,15 @@ layout(buffer_reference, std430, buffer_reference_align = 4) buffer VkDispatchIn
     VkDispatchIndirectCommand dimensions;
 };
 
-struct Draw2dSortConstants {
+struct SortConstants {
     uint coarse_buffer_len;
     uint _pad;
     VkDispatchIndirectCommandRef indirect_dispatch_buffer;
     CoarseRef coarse_buffer;
 };
 
-layout(std430, push_constant) uniform Draw2dSortConstantsBlock {
-    Draw2dSortConstants constants;
+layout(std430, push_constant) uniform SortConstantsBlock {
+    SortConstants constants;
 };
 
 layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
index 43fc0d928832ac838bd4849dfdbf7434f3df902e..46427d4ab8062e55ddf951d605f04bf584b240b1 100644 (file)
 
 #include "draw_2d.h"
 
-struct Draw2dResolveConstants {
-    uvec2 screen_resolution;
-    uvec2 tile_resolution;
-
+struct ResolveConstants {
+    uint tile_stride;
     uint draw_buffer_len;
-    uint _pad;
 
-    Draw2dCommandRef draw_buffer;
+    CommandRef draw_buffer;
+    ScissorRef scissor_buffer;
     GlyphRef glyph_buffer;
     CoarseRef coarse_buffer;
     FineRef fine_buffer;
     TileRef tile_buffer;
 };
 
-layout(std430, push_constant) uniform Draw2dResolveConstantsBlock {
-    Draw2dResolveConstants constants;
+layout(std430, push_constant) uniform ResolveConstantsBlock {
+    ResolveConstants constants;
 };
 
 layout (local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
@@ -37,13 +35,13 @@ void main() {
     const uint local_id = gl_SubgroupID * gl_SubgroupSize + gl_SubgroupInvocationID;
     const uint x = gl_GlobalInvocationID.y;
     const uint y = gl_GlobalInvocationID.z;
-    const uint tile_offset = (constants.tile_resolution.x * y + x);
+    const uint tile_offset = constants.tile_stride * y + x;
     const uint search = ((y & 0xff) << 24) | ((x & 0xff) << 16);
     const uint count = constants.coarse_buffer.values[0];
 
     if (count == 0) {
-        constants.tile_buffer.values[tile_offset].min_index = 0;
-        constants.tile_buffer.values[tile_offset].max_index = 0;
+        constants.tile_buffer.values[tile_offset].index_min = 0;
+        constants.tile_buffer.values[tile_offset].index_max = 0;
         return;
     }
 
@@ -61,7 +59,7 @@ void main() {
     }
 
     const vec2 tile_min = uvec2(x, y) * TILE_SIZE;
-    const vec2 tile_max = min(tile_min + TILE_SIZE, constants.screen_resolution);
+    const vec2 tile_max = tile_min + TILE_SIZE;
 
     bool hit_opaque = false;
     uint lo = base + 1;
@@ -86,37 +84,52 @@ void main() {
 
             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(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(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;
+            const uint cmd_scissor = packed_type & 0xffff;
+
+            const Scissor scissor = constants.scissor_buffer.values[cmd_scissor];
+
+            // If the tile doesn't intersect the scissor region it doesn't need to do work here.
+            if (any(lessThan(scissor.offset_max, tile_min)) || any(greaterThan(scissor.offset_min, tile_max))) {
+               intersects = false;
+            } else {
+                for (;;) {
+                    const uint scalar_type = subgroupBroadcastFirst(cmd_type);
+                    [[branch]]
+                    if (scalar_type == cmd_type) {
+                        switch (scalar_type) {
+                            case DRAW_2D_CMD_RECT:
+                                const CmdRect cmd_rect = decode_rect(constants.draw_buffer.values[draw_index]);
+                                cmd_min = cmd_rect.position;
+                                cmd_max = cmd_rect.position + cmd_rect.bound;
+
+                                const bool background_opaque = (cmd_rect.background_color & 0xff000000) == 0xff000000;
+                                if (background_opaque) {
+                                    const float border_width = float((packed_type >> 16) & 0xff);
+                                    const bool border_opaque = (cmd_rect.border_color & 0xff000000) == 0xff000000;
+                                    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) + (border_opaque ? 0.0 : border_width);
+
+                                    const vec2 cmd_shrunk_min = max(scissor.offset_min, cmd_min + shrink);
+                                    const vec2 cmd_shrunk_max = min(scissor.offset_max, cmd_max - shrink);
+                                    opaque_tile = all(greaterThan(cmd_shrunk_max, cmd_shrunk_min)) && all(greaterThan(tile_min, cmd_shrunk_min)) && all(lessThan(tile_max, cmd_shrunk_max));
+                                }
+                                break;
+                            case DRAW_2D_CMD_GLYPH:
+                                const CmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[draw_index]);
+                                const Glyph glyph = constants.glyph_buffer.values[cmd_glyph.index];
+                                cmd_min = cmd_glyph.position + glyph.offset_min;
+                                cmd_max = cmd_glyph.position + glyph.offset_max;
+                                break;
+                        }
+                        break;
                     }
-                    break;
                 }
-            }
 
-            intersects = !(any(lessThan(tile_max, cmd_min)) || any(greaterThan(tile_min, cmd_max)));
+                cmd_min = max(cmd_min, scissor.offset_min);
+                cmd_max = min(cmd_max, scissor.offset_max);
+                intersects = !(any(lessThan(tile_max, cmd_min)) || any(greaterThan(tile_min, cmd_max)));
+            }
         }
 
         uint intersects_mask = subgroupBallot(intersects).x;
@@ -133,6 +146,6 @@ void main() {
         }
     }
 
-    constants.tile_buffer.values[tile_offset].min_index = lo + 1;
-    constants.tile_buffer.values[tile_offset].max_index = hi + 1;
+    constants.tile_buffer.values[tile_offset].index_min = lo + 1;
+    constants.tile_buffer.values[tile_offset].index_max = hi + 1;
 }
index 4ba591ce8fe4ba9341a710802b45865faeab88e0..3e9c1c40e269f350d96410eba2ec9b92f888714b 100644 (file)
 #extension GL_KHR_shader_subgroup_vote : require
 #extension GL_KHR_shader_subgroup_ballot : require
 
-#include "compute_bindings.h"
+#include "bindings_compute.h"
 #include "draw_2d.h"
 #include "sdf.h"
 
-struct Draw2dRasterizeConstants {
-    uvec2 screen_resolution;
-    uvec2 tile_resolution;
-    uvec2 atlas_resolution;
+struct RasterizeConstants {
+    uint tile_stride;
+    uint _pad;
 
-    Draw2dCommandRef draw_buffer;
+    CommandRef draw_buffer;
+    ScissorRef scissor_buffer;
     GlyphRef glyph_buffer;
     CoarseRef coarse_buffer;
     FineRef fine_buffer;
     TileRef tile_buffer;
 };
 
-layout(std430, push_constant) uniform Draw2dRasterizeConstantsBlock {
-    Draw2dRasterizeConstants constants;
+layout(std430, push_constant) uniform RasterizeConstantsBlock {
+    RasterizeConstants constants;
 };
 
 /// x = (((index >> 2) & 0x0007) & 0xFFFE) | index & 0x0001
@@ -51,10 +51,10 @@ layout (local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
 
 void main() {
     const uvec2 tile_coord = gl_WorkGroupID.xy / (TILE_SIZE / gl_WorkGroupSize.xy);
-    const uint tile_index = tile_coord.y * constants.tile_resolution.x + tile_coord.x;
+    const uint tile_index = tile_coord.y * constants.tile_stride + tile_coord.x;
 
-    const uint lo = constants.tile_buffer.values[tile_index].min_index;
-    const uint hi = constants.tile_buffer.values[tile_index].max_index;
+    const uint lo = constants.tile_buffer.values[tile_index].index_min;
+    const uint hi = constants.tile_buffer.values[tile_index].index_max;
 
     if (lo == hi) {
         return;
@@ -90,23 +90,32 @@ void main() {
             const uint base_index = (constants.coarse_buffer.values[i] & 0xffff) * 32;
             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;
+            const uint cmd_scissor = packed_type & 0xffff;
+
+            const Scissor scissor = constants.scissor_buffer.values[cmd_scissor];
 
             vec4 primitive_color = vec4(0.0);
             switch (cmd_type) {
                 case DRAW_2D_CMD_RECT:
-                    const Draw2dCmdRect cmd_rect = decode_rect(constants.draw_buffer.values[base_index + index]);
+                {
+                    const CmdRect cmd_rect = decode_rect(constants.draw_buffer.values[base_index + index]);
+
+                    const vec2 cmd_min = cmd_rect.position;
+                    const vec2 cmd_max = cmd_rect.position + cmd_rect.bound;
 
-                    const float border_width = float(cmd_packed & 0xff);
+                    const float border_width = float((packed_type >> 16) & 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))) {
+                    const vec2 cmd_min_clipped = max(scissor.offset_min, cmd_min + border_width + shrink);
+                    const vec2 cmd_max_clipped = min(scissor.offset_max, cmd_max - border_width - shrink);
+
+                    if (all(greaterThan(sample_center, cmd_min_clipped)) && all(lessThan(sample_center, cmd_max_clipped))) {
                         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;
+                        const vec2 b = cmd_rect.bound / 2.0;
+                        const vec2 p = cmd_rect.position + b - sample_center;
 
                         float d;
                         if (all(equal(border_radii, vec4(0.0)))) {
@@ -119,21 +128,29 @@ void main() {
                         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));
+
+                        const vec2 clip_b = (scissor.offset_max - scissor.offset_min) / 2.0;
+                        const vec2 clip_p = scissor.offset_min + clip_b - sample_center;
+                        d = max(d, sdf_box(clip_p, clip_b));
+                        primitive_color = d < 0.0 ? primitive_color : vec4(0);
                     }
                     break;
+                }
                 case DRAW_2D_CMD_GLYPH:
-                    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))) {
+                {
+                    const CmdGlyph cmd_glyph = decode_glyph(constants.draw_buffer.values[base_index + index]);
+                    const Glyph glyph = constants.glyph_buffer.values[cmd_glyph.index];
+                    const vec2 cmd_min = cmd_glyph.position + glyph.offset_min;
+                    const vec2 cmd_max = cmd_glyph.position + glyph.offset_max;
+                    if (all(greaterThanEqual(sample_center, max(scissor.offset_min, cmd_min))) && all(lessThanEqual(sample_center, min(scissor.offset_max, cmd_max)))) {
                         const vec2 glyph_size = glyph.offset_max - glyph.offset_min;
-                        const vec2 uv = mix(glyph.atlas_min, glyph.atlas_max, (sample_center - glyph_min) / glyph_size) / constants.atlas_resolution;
+                        const vec2 uv = mix(glyph.atlas_min, glyph.atlas_max, (sample_center - cmd_min) / glyph_size);
                         const vec4 color = unpackUnorm4x8(cmd_glyph.color).bgra;
-                        const float coverage = textureLod(sampler2D(glyph_atlas, bilinear_sampler), uv, 0.0).r * color.a;
+                        const float coverage = textureLod(sampler2D(glyph_atlas, samplers[SAMPLER_BILINEAR_UNNORMALIZED]), uv, 0.0).r * color.a;
                         primitive_color = color * coverage;
                     }
                     break;
+                }
             }
 
             // does it blend?
index b3c314b374044d2dbeb74d144e874facc61d5bf3..4304d6eb48c7d4d3be9efebb483eec5ecdb67921 100644 (file)
@@ -38,24 +38,26 @@ const _: () = assert!(std::mem::size_of::<Draw2dCmd>() == 32);
 #[derive(Clone, Copy)]
 struct CmdGlyph {
     packed: u32,
+    index: u32,
     position: Vec2,
     color: u32,
 }
 
-const _: () = assert!(std::mem::size_of::<CmdGlyph>() == 16);
+const _: () = assert!(std::mem::size_of::<CmdGlyph>() == 20);
 
 #[repr(C)]
 #[derive(Clone, Copy)]
 struct CmdRect {
     /// 31       .          .          .          0
-    ///  tttt tttt  0000 0000  0000 0000  bbbb bbbb
+    ///  tttt tttt  bbbb bbbb  ssss ssss  ssss ssss
     ///
     /// t: Type
     /// b: Border width
+    /// s: Scissor index
     packed: u32,
 
-    bounds_min: Vec2,
-    bounds_max: Vec2,
+    position: Vec2,
+    bound: Vec2,
 
     border_radii: u32,
     border_color: u32,
@@ -67,11 +69,17 @@ const _: () = assert!(std::mem::size_of::<CmdRect>() == 32);
 
 impl Draw2dCmd {
     #[inline(always)]
-    pub fn glyph(touched_glyph_index: TouchedGlyphIndex, color: u32, position: Vec2) -> Self {
+    pub fn glyph(
+        scissor_index: u32,
+        touched_glyph_index: TouchedGlyphIndex,
+        color: u32,
+        position: Vec2,
+    ) -> Self {
+        let glyph_index = touched_glyph_index.as_u32();
         Self {
             glyph: CmdGlyph {
-                packed: (Draw2dCmdType::Glyph as u32) << 24
-                    | (touched_glyph_index.as_u32() & 0xffffff),
+                packed: ((Draw2dCmdType::Glyph as u32) << 24) | (scissor_index & 0xffff),
+                index: glyph_index,
                 position,
                 color,
             },
@@ -80,8 +88,9 @@ impl Draw2dCmd {
 
     #[inline(always)]
     pub fn rect(
-        bounds_min: Vec2,
-        bounds_max: Vec2,
+        scissor_index: u32,
+        position: Vec2,
+        bound: Vec2,
         border_width: u8,
         border_radii: [u8; 4],
         border_color: u32,
@@ -89,9 +98,11 @@ impl Draw2dCmd {
     ) -> Self {
         Self {
             rect: CmdRect {
-                packed: (Draw2dCmdType::Rect as u32) << 24 | border_width as u32,
-                bounds_min,
-                bounds_max,
+                packed: ((Draw2dCmdType::Rect as u32) << 24)
+                    | ((border_width as u32) << 16)
+                    | (scissor_index & 0xffff),
+                position,
+                bound,
                 border_radii: u32::from_ne_bytes(border_radii),
                 background_color,
                 border_color,
@@ -100,8 +111,15 @@ impl Draw2dCmd {
     }
 }
 
+#[repr(C)]
+pub struct Draw2dScissor {
+    pub offset_min: Vec2,
+    pub offset_max: Vec2,
+}
+
 pub struct Samplers {
     pub bilinear: Sampler,
+    pub bilinear_unnormalized: Sampler,
 }
 
 impl Samplers {
@@ -113,8 +131,23 @@ impl Samplers {
             mip_lod_bias: 0.0,
             min_lod: 0.0,
             max_lod: 0.0,
+            unnormalized_coordinates: false,
         });
-        Samplers { bilinear }
+
+        let bilinear_unnormalized = gpu.create_sampler(&SamplerDesc {
+            filter: SamplerFilter::Bilinear,
+            address_mode: SamplerAddressMode::Clamp,
+            compare_op: None,
+            mip_lod_bias: 0.0,
+            min_lod: 0.0,
+            max_lod: 0.0,
+            unnormalized_coordinates: true,
+        });
+
+        Samplers {
+            bilinear,
+            bilinear_unnormalized,
+        }
     }
 }
 
@@ -149,8 +182,6 @@ pub struct Draw2dClearConstants<'a> {
 
 #[repr(C)]
 pub struct Draw2dScatterConstants<'a> {
-    pub screen_resolution_x: u32,
-    pub screen_resolution_y: u32,
     pub tile_resolution_x: u32,
     pub tile_resolution_y: u32,
 
@@ -158,6 +189,7 @@ pub struct Draw2dScatterConstants<'a> {
     pub coarse_buffer_len: u32,
 
     pub draw_buffer_address: BufferAddress<'a>,
+    pub scissor_buffer_address: BufferAddress<'a>,
     pub glyph_buffer_address: BufferAddress<'a>,
     pub coarse_buffer_address: BufferAddress<'a>,
 }
@@ -172,15 +204,11 @@ pub struct Draw2dSortConstants<'a> {
 
 #[repr(C)]
 pub struct Draw2dResolveConstants<'a> {
-    pub screen_resolution_x: u32,
-    pub screen_resolution_y: u32,
-    pub tile_resolution_x: u32,
-    pub tile_resolution_y: u32,
-
+    pub tile_stride: u32,
     pub draw_buffer_len: u32,
-    pub _pad: u32,
 
     pub draw_buffer_address: BufferAddress<'a>,
+    pub scissor_buffer_address: BufferAddress<'a>,
     pub glyph_buffer_address: BufferAddress<'a>,
     pub coarse_buffer_address: BufferAddress<'a>,
     pub fine_buffer_address: BufferAddress<'a>,
@@ -189,14 +217,11 @@ pub struct Draw2dResolveConstants<'a> {
 
 #[repr(C)]
 pub struct Draw2dRasterizeConstants<'a> {
-    pub screen_resolution_x: u32,
-    pub screen_resolution_y: u32,
-    pub tile_resolution_x: u32,
-    pub tile_resolution_y: u32,
-    pub atlas_resolution_x: u32,
-    pub atlas_resolution_y: u32,
+    pub tile_stride: u32,
+    pub _pad: u32,
 
     pub draw_buffer_address: BufferAddress<'a>,
+    pub scissor_buffer_address: BufferAddress<'a>,
     pub glyph_buffer_address: BufferAddress<'a>,
     pub coarse_buffer_address: BufferAddress<'a>,
     pub fine_buffer_address: BufferAddress<'a>,
@@ -266,7 +291,7 @@ pub struct Pipelines {
 impl Pipelines {
     pub fn load(gpu: &Gpu) -> Self {
         let samplers = Samplers::load(gpu);
-        let immutable_samplers = &[samplers.bilinear];
+        let immutable_samplers = &[samplers.bilinear, samplers.bilinear_unnormalized];
 
         let graphics_bind_group_layout = gpu.create_bind_group_layout(&[
             // Samplers
index 71bda09b490cccb73fdb6e83ee2f95a3faa25419..b97833c0b945d4037dc1219099428387f28f5026 100644 (file)
@@ -3,12 +3,12 @@ use std::ops::Index;
 use std::path::Path;
 use std::time::{Duration, Instant};
 
-use narcissus_core::dds;
+use narcissus_core::{dds, Widen};
 
 use shark_shaders::pipelines::{
     calculate_spine_size, BasicConstants, CompositeConstants, ComputeBinds, Draw2dClearConstants,
     Draw2dCmd, Draw2dRasterizeConstants, Draw2dResolveConstants, Draw2dScatterConstants,
-    Draw2dSortConstants, GraphicsBinds, Pipelines, RadixSortDownsweepConstants,
+    Draw2dScissor, Draw2dSortConstants, GraphicsBinds, Pipelines, RadixSortDownsweepConstants,
     RadixSortUpsweepConstants, DRAW_2D_TILE_SIZE,
 };
 
@@ -450,10 +450,15 @@ struct UiState<'a> {
     fonts: &'a Fonts<'a>,
     glyph_cache: GlyphCache<'a, Fonts<'a>>,
 
+    width: f32,
+    height: f32,
     scale: f32,
 
     tmp_string: String,
 
+    scissors: Vec<Draw2dScissor>,
+    scissor_stack: Vec<u32>,
+
     draw_cmds: Vec<Draw2dCmd>,
 }
 
@@ -464,33 +469,85 @@ impl<'a> UiState<'a> {
         Self {
             fonts,
             glyph_cache,
+            width: 0.0,
+            height: 0.0,
             scale: 1.0,
             tmp_string: default(),
+            scissors: vec![],
+            scissor_stack: vec![],
+
             draw_cmds: vec![],
         }
     }
 
+    fn begin_frame(&mut self, width: f32, height: f32, scale: f32) {
+        self.width = width;
+        self.height = height;
+        self.scale = scale;
+
+        self.draw_cmds.clear();
+
+        self.scissor_stack.clear();
+        self.scissors.clear();
+
+        // Scissor 0 is always the screen bounds.
+        self.scissors.push(Draw2dScissor {
+            offset_min: vec2(0.0, 0.0),
+            offset_max: vec2(width, height),
+        });
+    }
+
+    fn push_scissor(
+        &mut self,
+        mut offset_min: Vec2,
+        mut offset_max: Vec2,
+        intersect_with_current: bool,
+    ) {
+        if intersect_with_current {
+            let current_scissor_index = self.scissor_stack.last().copied().unwrap_or(0);
+            let current_scissor = &self.scissors[current_scissor_index.widen()];
+            offset_min = Vec2::max(offset_min, current_scissor.offset_min);
+            offset_max = Vec2::min(offset_max, current_scissor.offset_max);
+        }
+
+        let scissor_index = self.scissors.len() as u32;
+        self.scissors.push(Draw2dScissor {
+            offset_min,
+            offset_max,
+        });
+        self.scissor_stack.push(scissor_index);
+    }
+
+    fn push_fullscreen_scissor(&mut self) {
+        // The fullscreen scissor is always at index 0
+        self.scissor_stack.push(0);
+    }
+
+    fn pop_scissor(&mut self) {
+        // It's invalid to pop more than we've pushed.
+        self.scissor_stack.pop().expect("unbalanced push / pop");
+    }
+
     fn rect(
         &mut self,
-        position: Vec2,
-        bounds: Vec2,
+        x: f32,
+        y: f32,
+        width: f32,
+        height: f32,
         border_width: f32,
         border_radii: [f32; 4],
         border_color: u32,
         background_color: u32,
     ) {
-        let bounds = bounds * self.scale;
-
-        let bounds_min = position;
-        let bounds_max = position + bounds;
+        let scissor_index = self.scissor_stack.last().copied().unwrap_or(0);
 
-        let border_width = (border_width * self.scale).clamp(0.0, 255.0).floor() as u8;
-        let border_radii =
-            border_radii.map(|radius| (radius * self.scale).clamp(0.0, 255.0).floor() as u8);
+        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(
-            bounds_min,
-            bounds_max,
+            scissor_index,
+            vec2(x, y),
+            vec2(width, height),
             border_width,
             border_radii,
             border_color,
@@ -506,6 +563,8 @@ impl<'a> UiState<'a> {
         font_size_px: f32,
         args: std::fmt::Arguments,
     ) -> f32 {
+        let scissor_index = self.scissor_stack.last().copied().unwrap_or(0);
+
         let font = self.fonts.font(font_family);
         let font_size_px = font_size_px * self.scale;
         let scale = font.scale_for_size_px(font_size_px);
@@ -539,6 +598,7 @@ impl<'a> UiState<'a> {
             x += advance * scale;
 
             self.draw_cmds.push(Draw2dCmd::glyph(
+                scissor_index,
                 touched_glyph_index,
                 microshades::GRAY_RGBA8[4].rotate_right(8),
                 vec2(x, y),
@@ -1319,7 +1379,13 @@ impl<'gpu> DrawState<'gpu> {
                 );
 
                 let draw_buffer_len = ui_state.draw_cmds.len() as u32;
-                ui_state.draw_cmds.clear();
+
+                let scissor_buffer = gpu.request_transient_buffer_with_data(
+                    frame,
+                    thread_token,
+                    BufferUsageFlags::STORAGE,
+                    ui_state.scissors.as_slice(),
+                );
 
                 let glyph_buffer = gpu.request_transient_buffer_with_data(
                     frame,
@@ -1365,6 +1431,7 @@ impl<'gpu> DrawState<'gpu> {
                 );
 
                 let draw_buffer_address = gpu.get_buffer_address(draw_buffer.to_arg());
+                let scissor_buffer_address = gpu.get_buffer_address(scissor_buffer.to_arg());
                 let glyph_buffer_address = gpu.get_buffer_address(glyph_buffer.to_arg());
                 let coarse_buffer_address = gpu.get_buffer_address(coarse_buffer.to_arg());
                 let indirect_dispatch_buffer_address =
@@ -1402,13 +1469,12 @@ impl<'gpu> DrawState<'gpu> {
                     ShaderStageFlags::COMPUTE,
                     0,
                     &Draw2dScatterConstants {
-                        screen_resolution_x: self.width,
-                        screen_resolution_y: self.height,
                         tile_resolution_x: self.tile_resolution_x,
                         tile_resolution_y: self.tile_resolution_y,
                         draw_buffer_len,
                         coarse_buffer_len: COARSE_BUFFER_LEN as u32,
                         draw_buffer_address,
+                        scissor_buffer_address,
                         glyph_buffer_address,
                         coarse_buffer_address,
                     },
@@ -1541,13 +1607,10 @@ impl<'gpu> DrawState<'gpu> {
                     ShaderStageFlags::COMPUTE,
                     0,
                     &Draw2dResolveConstants {
-                        screen_resolution_x: self.width,
-                        screen_resolution_y: self.height,
-                        tile_resolution_x: self.tile_resolution_x,
-                        tile_resolution_y: self.tile_resolution_y,
+                        tile_stride: self.tile_resolution_x,
                         draw_buffer_len,
-                        _pad: 0,
                         draw_buffer_address,
+                        scissor_buffer_address,
                         glyph_buffer_address,
                         coarse_buffer_address,
                         fine_buffer_address: tmp_buffer_address,
@@ -1577,13 +1640,10 @@ impl<'gpu> DrawState<'gpu> {
                     ShaderStageFlags::COMPUTE,
                     0,
                     &Draw2dRasterizeConstants {
-                        screen_resolution_x: self.width,
-                        screen_resolution_y: self.height,
-                        tile_resolution_x: self.tile_resolution_x,
-                        tile_resolution_y: self.tile_resolution_y,
-                        atlas_resolution_x: atlas_width,
-                        atlas_resolution_y: atlas_height,
+                        tile_stride: self.tile_resolution_x,
+                        _pad: 0,
                         draw_buffer_address,
+                        scissor_buffer_address,
                         glyph_buffer_address,
                         coarse_buffer_address,
                         fine_buffer_address: tmp_buffer_address,
@@ -1813,83 +1873,94 @@ pub fn main() {
                 tick_accumulator -= target_dt;
             }
 
-            ui_state.scale = ui_scale_override.unwrap_or(window_display_scale);
-
             let draw_start = Instant::now();
             let tick_duration = draw_start - tick_start;
-            let (base_x, base_y) = sin_cos_pi_f32(game_state.time);
-            let base_x = (base_x + 1.0) * 0.5 * ui_state.scale;
-            let base_y = (base_y + 1.0) * 0.5 * ui_state.scale;
 
-            for _ in 0..100 {
-                ui_state.rect(
-                    vec2(100.0, 100.0),
-                    vec2(1000.0, 1000.0),
-                    0.0,
-                    [25.0; 4],
-                    0,
-                    0x88442211,
-                );
-            }
+            ui_state.begin_frame(
+                width as f32,
+                height as f32,
+                ui_scale_override.unwrap_or(window_display_scale),
+            );
 
-            let (s, _) = sin_cos_pi_f32(game_state.time * 0.1);
-
-            let mut y = 8.0 * ui_state.scale;
-            for _ in 0..224 {
-                let vertical_advance = ui_state.text_fmt(
-                        5.0,
-                        y,
-                        FontFamily::NotoSansJapanese,
-                        16.0 + s * 8.0,
-                        format_args!(
-                            "お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog.  ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog.  ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog.  ████████"
-                        ),
-                    );
-                y += vertical_advance;
-            }
+            {
+                let width = width as f32;
+                let height = height as f32;
+
+                let (s, c) = sin_cos_pi_f32(game_state.time * 0.1);
 
-            for i in 0..500 {
-                let (rect_x, rect_y) = sin_cos_pi_f32(game_state.time * 0.1 + i as f32 * 1.01);
+                let w = width / 5.0;
+                let h = height / 5.0;
+                let x = width / 2.0 + w * s;
+                let y = height / 2.0 + w * c;
+
+                ui_state.push_scissor(vec2(x - w, y - h), vec2(x + w, y + h), true);
                 ui_state.rect(
-                    (vec2(width as f32, height as f32) / 2.0)
-                        - 250.0
-                        - vec2(rect_x, rect_y) * 1000.0,
-                    vec2(400.0, 400.0),
-                    10.0,
-                    [rect_x * 50.0, rect_y * 50.0, 25.0, 25.0],
-                    0xffffffff,
-                    microshades::BLUE_RGBA8[4].rotate_right(8),
+                    0.0, 0.0, width, height, 0.0, [0.0; 4], 0xffffffff, 0xffffffff,
                 );
-            }
+                ui_state.pop_scissor();
 
-            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),
-            );
+                ui_state.push_scissor(vec2(x - w, y - h), vec2(x + w, y + h), true);
 
-            y = base_y * 150.0;
-            for i in 0..10 {
-                if i & 1 != 0 {
-                    y += ui_state.text_fmt(
-                        base_x * 100.0 - 5.0,
-                        y,
-                        FontFamily::RobotoRegular,
-                        20.0,
-                        format_args!("tick: {:?}", tick_duration),
-                    );
-                } else {
-                    y += ui_state.text_fmt(
-                        base_x * 100.0 - 5.0,
-                        y,
-                        FontFamily::RobotoRegular,
-                        20.0,
-                        format_args!("draw: {:?}", draw_duration),
+                let mut y = 8.0 * ui_state.scale;
+                for i in 0..224 {
+                    if i & 1 == 0 {
+                        ui_state.push_fullscreen_scissor();
+                    }
+                    let vertical_advance = ui_state.text_fmt(
+                            5.0,
+                            y,
+                            FontFamily::NotoSansJapanese,
+                            16.0 + s * 8.0,
+                            format_args!(
+                                "お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog.  ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog.  ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog. ████████お握り The Quick Brown Fox Jumped Over The Lazy Dog.  ████████"
+                            ),
+                        );
+                    y += vertical_advance;
+                    if i & 1 == 0 {
+                        ui_state.pop_scissor();
+                    }
+                }
+
+                ui_state.pop_scissor();
+
+                for i in 0..500 {
+                    let (s, c) = sin_cos_pi_f32(game_state.time * 0.1 + i as f32 * 0.01);
+
+                    let x = width / 2.0 + w * s;
+                    let y = height / 2.0 + w * c;
+                    ui_state.rect(
+                        x - 200.0,
+                        y - 200.0,
+                        400.0,
+                        400.0,
+                        100.0,
+                        [100.0, 50.0, 25.0, 0.0],
+                        0x33333333,
+                        microshades::BLUE_RGBA8[4].rotate_right(8),
                     );
                 }
+
+                let x = 10.0 * ui_state.scale;
+                let mut y = 20.0 * ui_state.scale;
+                for i in 0..10 {
+                    if i & 1 != 0 {
+                        y += ui_state.text_fmt(
+                            x,
+                            y,
+                            FontFamily::RobotoRegular,
+                            20.0,
+                            format_args!("this tick: {:?}", tick_duration),
+                        );
+                    } else {
+                        y += ui_state.text_fmt(
+                            x,
+                            y,
+                            FontFamily::NotoSansJapanese,
+                            20.0,
+                            format_args!("last draw: {:?}", draw_duration),
+                        );
+                    }
+                }
             }
 
             draw_state.draw(