Skip to content

Commit

Permalink
Painter: Memory optimizations
Browse files Browse the repository at this point in the history
This adds a number of memory optimizations to the painter:

* Downsampling is now done in-place when using AA. Memory is freed after
  the downsample. This avoids an extra allocation for the downsampled
  surface.

* Additionally in AA, if our pixel source is full opaque alpha, we
  fast-path to just using the mask as the foreground. This avoids
  another allocation under this scenario!

* Finally, we have moved the edge calculations to a
  FixedBufferAllocator. The default allocation size (which can be tuned
  if you're okay calling the painter functions directly from the type
  function) is 1024 items, or 4096 bytes under our new Polygon.Edge
  packed struct. This has allowed us to drop the use of external
  allocators completely from non-AA paint. As part of this, we've
  dropped the X-coordinate in an edge to an i30 (this might be dropped
  lower as well, depending on the result of our internal numerics work).

* We've also dropped the ArenaAllocator from AA paint as it's not
  bringing value anymore, and probably just getting in the way of our
  resize.

All of these are in advance of fully addressing #44 - lowering the
allocation footprint of the painter will make the path to infallibility
much easier.
  • Loading branch information
vancluever committed Oct 22, 2024
1 parent 3eb8352 commit caaae94
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 305 deletions.
46 changes: 46 additions & 0 deletions spec/046_fill_triangle_alpha.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: 0BSD
// Copyright © 2024 Chris Marchesi

//! Case: Renders and fills a triangle on a 300x300 surface.
//!
//! This is similar to the 003_fill_triangle.zig, but uses alpha8 as its source
//! versus RGB.
const mem = @import("std").mem;

const z2d = @import("z2d");

pub const filename = "046_fill_triangle_alpha";

pub fn render(alloc: mem.Allocator, aa_mode: z2d.options.AntiAliasMode) !z2d.Surface {
const width = 300;
const height = 300;
const sfc = try z2d.Surface.initPixel(
.{ .rgb = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF } }, // White so that srcOver shows up correctly
alloc,
width,
height,
);

var context: z2d.Context = .{
.surface = sfc,
.pattern = .{
.opaque_pattern = .{
.pixel = .{ .alpha8 = .{ .a = 255 } },
},
},
.anti_aliasing_mode = aa_mode,
};

var path = z2d.Path.init(alloc);
defer path.deinit();

const margin = 10;
try path.moveTo(0 + margin, 0 + margin);
try path.lineTo(width - margin - 1, 0 + margin);
try path.lineTo(width / 2 - 1, height - margin - 1);
try path.close();

try context.fill(alloc, path);

return sfc;
}
Binary file added spec/files/046_fill_triangle_alpha_pixelated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added spec/files/046_fill_triangle_alpha_smooth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions spec/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const _042_arc_ellipses = @import("042_arc_ellipses.zig");
const _043_rect_transforms = @import("043_rect_transforms.zig");
const _044_line_transforms = @import("044_line_transforms.zig");
const _045_round_join_transforms = @import("045_round_join_transforms.zig");
const _046_fill_triangle_alpha = @import("046_fill_triangle_alpha.zig");

//////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -108,6 +109,7 @@ pub fn main() !void {
try pathExportRun(alloc, _043_rect_transforms);
try pathExportRun(alloc, _044_line_transforms);
try pathExportRun(alloc, _045_round_join_transforms);
try pathExportRun(alloc, _046_fill_triangle_alpha);
}

//////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -292,6 +294,10 @@ test "045_round_join_transforms" {
try pathTestRun(testing.allocator, _045_round_join_transforms);
}

test "046_fill_triangle_alpha" {
try pathTestRun(testing.allocator, _046_fill_triangle_alpha);
}

//////////////////////////////////////////////////////////////////////////////

fn compositorExportRun(alloc: mem.Allocator, subject: anytype) !void {
Expand Down
18 changes: 12 additions & 6 deletions src/Context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ const Context = @This();
const mem = @import("std").mem;

const options = @import("options.zig");
const paintpkg = @import("internal/Painter.zig");

const Path = @import("Path.zig");
const Pattern = @import("pattern.zig").Pattern;
const Painter = @import("internal/Painter.zig");
const Surface = @import("surface.zig").Surface;
const Transformation = @import("Transformation.zig");
const PathError = @import("errors.zig").PathError;

/// The default value to use for the edge cache during rasterization, in number
/// of edges that can be stored per scanline.
///
/// Note that this cannot be modified in the context. If you require a
/// different value, either due to OOM issues due to a large number of edges,
/// or if you want a smaller buffer, see internal/Painter.zig. Note that this
/// API is currently unstable.
comptime default_edge_cache_size: usize = 1024,

/// The underlying surface.
surface: Surface,

Expand Down Expand Up @@ -91,9 +100,7 @@ transformation: Transformation = Transformation.identity,
///
/// This is a no-op if there are no nodes.
pub fn fill(self: *Context, alloc: mem.Allocator, path: Path) !void {
if (path.nodes.items.len == 0) return;
if (!path.isClosed()) return PathError.PathNotClosed;
try (Painter{ .context = self }).fill(alloc, path.nodes);
try (paintpkg.Painter(self.default_edge_cache_size){ .context = self }).fill(alloc, path.nodes);
}

/// Strokes a line for the path(s) in the supplied set.
Expand All @@ -108,6 +115,5 @@ pub fn fill(self: *Context, alloc: mem.Allocator, path: Path) !void {
///
/// This is a no-op if there are no nodes.
pub fn stroke(self: *Context, alloc: mem.Allocator, path: Path) !void {
if (path.nodes.items.len == 0) return;
try (Painter{ .context = self }).stroke(alloc, path.nodes);
try (paintpkg.Painter(self.default_edge_cache_size){ .context = self }).stroke(alloc, path.nodes);
}
Loading

0 comments on commit caaae94

Please sign in to comment.