diff --git a/examples/gg/many_thousands_of_circles.v b/examples/gg/many_thousands_of_circles.v new file mode 100644 index 0000000000..3311110243 --- /dev/null +++ b/examples/gg/many_thousands_of_circles.v @@ -0,0 +1,56 @@ +module main + +import gg +import gx +import rand + +const max_circles_per_pass = 1000 + +// prepare some random colors ahead of time +const colors = []gx.Color{len: max_circles_per_pass, init: gx.Color{ + r: u8(index * 0 + rand.u8()) + g: u8(index * 0 + rand.u8()) + b: u8(index * 0 + rand.u8()) +}} + +fn frame(mut ctx gg.Context) { + // First pass, just clears the background: + ctx.begin() + ctx.end() + + // We want to draw thousands of circles, but sokol has a limit for how + // many primitives can be in a single pass, and if you reach that limit + // you will not see *anything at all* for that pass. + // For the circles below, that limit is ~2520 circles per pass. + // To overcome this, we will use several passes instead, where each one + // will draw just 1000 circles. + // In other words, in total we will have 4 * 1000 = 4000 circles, drawn with + // 4 passes. + for i := 0; i < 4 * max_circles_per_pass; i += max_circles_per_pass { + ctx.begin() + for c in 0 .. max_circles_per_pass { + rx := rand.int_in_range(0, ctx.window.width) or { 0 } + ry := rand.int_in_range(0, ctx.window.height) or { 0 } + ctx.draw_circle_filled(rx, ry, 10, colors[c]) + } + ctx.end(how: .passthru) + } + + // The last pass, is for the fps overlay, that should be *always on top of everything*. + // Drawing it in a separate pass, guarantees, that it *will* be drawn, even if the drawing + // of all the other passes fail. Try increasing max_circles_per_pass to 3000 for example. + ctx.begin() + ctx.show_fps() + ctx.end(how: .passthru) +} + +fn main() { + mut ctx := gg.new_context( + window_title: 'Many Thousands of Circles' + bg_color: gx.black + width: 600 + height: 400 + frame_fn: frame + ) + ctx.run() +} diff --git a/vlib/gg/gg.c.v b/vlib/gg/gg.c.v index d8feadb18a..54cd2dbbaf 100644 --- a/vlib/gg/gg.c.v +++ b/vlib/gg/gg.c.v @@ -530,8 +530,56 @@ pub fn (ctx &Context) begin() { sgl.ortho(0.0, f32(sapp.width()), f32(sapp.height()), 0.0, -1.0, 1.0) } -// end finishes drawing for the context. -pub fn (ctx &Context) end() { +pub enum EndEnum { + clear + passthru +} + +[params] +pub struct EndOptions { + how EndEnum +} + +const dontcare_pass = gfx.PassAction{ + colors: [ + gfx.ColorAttachmentAction{ + action: .dontcare + value: gfx.Color{1.0, 1.0, 1.0, 1.0} + }, + gfx.ColorAttachmentAction{ + action: .dontcare + value: gfx.Color{1.0, 1.0, 1.0, 1.0} + }, + gfx.ColorAttachmentAction{ + action: .dontcare + value: gfx.Color{1.0, 1.0, 1.0, 1.0} + }, + gfx.ColorAttachmentAction{ + action: .dontcare + value: gfx.Color{1.0, 1.0, 1.0, 1.0} + }, + ]! +} + +// end finishes all the drawing for the context ctx. +// All accumulated draw calls before ctx.end(), will be done in a separate Sokol pass. +// +// Note: each Sokol pass, has a limit on the number of draw calls, that can be done in it. +// Once that limit is reached, the whole pass will not draw anything, which can be frustrating. +// +// To overcome this limitation, you may use *several passes*, when you want to make thousands +// of draw calls (for example, if you need to draw thousands of circles/rectangles/sprites etc), +// where each pass will render just a limited amount of primitives. +// +// In the context of the gg module (without dropping to using sgl and gfx directly), it means, +// that you will need a new pair of ctx.begin() and ctx.end() calls, surrounding all the draw +// calls, that should be done in each pass. +// +// The default ctx.end() is equivalent to ctx.end(how:.clear). It will erase the existing +// rendered content with the background color, before drawing anything else. +// You can call ctx.end(how:.passthru) for a pass, that *will not* erase the previously +// rendered content in the context. +pub fn (ctx &Context) end(options EndOptions) { $if show_fps ? { ctx.show_fps() } $else { @@ -539,7 +587,14 @@ pub fn (ctx &Context) end() { ctx.show_fps() } } - gfx.begin_default_pass(ctx.clear_pass, sapp.width(), sapp.height()) + match options.how { + .clear { + gfx.begin_default_pass(ctx.clear_pass, sapp.width(), sapp.height()) + } + .passthru { + gfx.begin_default_pass(gg.dontcare_pass, sapp.width(), sapp.height()) + } + } sgl.draw() gfx.end_pass() gfx.commit()