Skip to content

Commit 344f7cc

Browse files
committed
examples,gg: add examples/gg/grid_of_rectangles.v; add gg.Context.draw_rect_filled_no_context/5 and gg.Context.draw_rect_empty_no_context/5
1 parent 1e0bda4 commit 344f7cc

2 files changed

Lines changed: 134 additions & 0 deletions

File tree

examples/gg/grid_of_rectangles.v

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
module main
2+
3+
import gg
4+
import sokol.sgl
5+
6+
struct App {
7+
mut:
8+
gg &gg.Context = unsafe { nil }
9+
}
10+
11+
const cmax_x = 115
12+
const cmax_y = 81
13+
const cs = 10
14+
15+
const c_fg = gg.rgb(20, 30, 255)
16+
const c_em = gg.rgb(255, 50, 0)
17+
18+
fn main() {
19+
mut app := &App{}
20+
app.gg = gg.new_context(
21+
bg_color: gg.white
22+
width: cmax_x * cs
23+
height: cmax_y * cs
24+
create_window: true
25+
window_title: 'Grid with many rectangles (drawn with no context)'
26+
frame_fn: frame
27+
user_data: app
28+
)
29+
app.gg.run()
30+
}
31+
32+
fn frame(app &App) {
33+
app.gg.begin()
34+
// Note: this uses 2 separate loops, with each doing its own drawing for performance reasons.
35+
// The underlying Sokol library can eliminate sgl begin/end calls, when multiple primitives of the
36+
// same kind are drawn one after the other, without context changes.
37+
// However, filled rectangles are drawn with quads, while empty rectangles are drawn with lines.
38+
// In this case, if the draw calls are put in the same loop, using app.gg.draw_rect_filled/5 and app.gg.draw_rect_empty/5,
39+
// sokol can not batch the begin/end calls, and their overhead becomes much bigger.
40+
//
41+
// That is why:
42+
// a) we use several loops here.
43+
// b) we use a single sgl.begin_quads() before the first loop, and a single sgl.end() after it.
44+
// c) we use draw_rect_filled_no_context/5 inside the loop, instead of draw_rect_filled/5 .
45+
// e) we use a single sgl.begin_lines() before the second loop, and a single sgl.end() after it.
46+
// f) we use draw_rect_empty_no_context/5 inside the second loop, instead of draw_rect_empty/5 .
47+
// Note: the separation of the loops/kinds of draws, is several times more important for eliminating
48+
// the performance overhead (12-15% CPU usage), compared to the use of draw_rect_filled_no_context
49+
// vs draw_rect_filled (1-2% CPU usage), because of Sokol's optimisation.
50+
51+
$if !do_not_draw_rect_filled ? {
52+
sgl.begin_quads()
53+
for y in 0 .. cmax_y {
54+
for x in 0 .. cmax_x {
55+
cy, cx := y * cs, x * cs
56+
app.gg.draw_rect_filled_no_context(cx, cy, cs - 2, cs - 2, c_fg)
57+
}
58+
}
59+
sgl.end()
60+
}
61+
62+
$if draw_rect_empty ? {
63+
sgl.begin_lines()
64+
for y in 0 .. cmax_y {
65+
for x in 0 .. cmax_x {
66+
cy, cx := y * cs, x * cs
67+
app.gg.draw_rect_empty_no_context(cx + 2, cy + 2, cs - 3, cs - 3, c_em)
68+
}
69+
}
70+
sgl.end()
71+
}
72+
73+
app.gg.end()
74+
}

vlib/gg/draw.c.v

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ pub fn (ctx &Context) draw_convex_poly(points []f32, c Color) {
184184
// draw_rect_empty draws the outline of a rectangle.
185185
// `x`,`y` is the top-left corner of the rectangle.
186186
// `w` is the width, `h` is the height and `c` is the color of the outline.
187+
// Note: it is much more efficient to draw lots of empty rectangles one after the other,
188+
// without filled rectangles between them, than to draw a mix.
187189
pub fn (ctx &Context) draw_rect_empty(x f32, y f32, w f32, h f32, c Color) {
188190
if c.a != 255 {
189191
sgl.load_pipeline(ctx.pipeline.alpha)
@@ -215,9 +217,48 @@ pub fn (ctx &Context) draw_rect_empty(x f32, y f32, w f32, h f32, c Color) {
215217
sgl.end()
216218
}
217219

220+
// draw_rect_empty_no_context draws the outline of a rectangle, but without saving/restoring the context.
221+
// It is intended to be used in loops, where you do manually: `sgl.begin_lines()` *before* the loop,
222+
// then draw many rectangles, then call manually `sgl.end()` *after* the loop.
223+
// `x`,`y` is the top-left corner of the rectangle.
224+
// `w` is the width, `h` is the height and `c` is the color of the outline.
225+
// Note: it is much more efficient to draw lots of empty rectangles one after the other,
226+
// without filled rectangles between them, than to draw a mix.
227+
pub fn (ctx &Context) draw_rect_empty_no_context(x f32, y f32, w f32, h f32, c Color) {
228+
// The small offsets here, are to make sure, that the start and end points will be
229+
// inside pixels, and not on their borders. That in turn, makes it much more likely
230+
// that different OpenGL implementations will render them identically, for example
231+
// Mesa, with `LIBGL_ALWAYS_SOFTWARE=1` renders the same as HD4000.
232+
mut toffset := f32(0.1)
233+
mut boffset := f32(-0.1)
234+
tleft_x := toffset + x * ctx.scale
235+
tleft_y := toffset + y * ctx.scale
236+
bright_x := boffset + (x + w) * ctx.scale
237+
bright_y := boffset + (y + h) * ctx.scale
238+
// Note: the following line is deliberately commented, compare to draw_rect_empty/5;
239+
// sgl.begin_lines() // more predictable, compared to sgl.begin_line_strip, at the price of more vertexes send
240+
sgl.c4b(c.r, c.g, c.b, c.a)
241+
// top:
242+
sgl.v2f(tleft_x, tleft_y)
243+
sgl.v2f(bright_x, tleft_y)
244+
// left:
245+
sgl.v2f(tleft_x, tleft_y)
246+
sgl.v2f(tleft_x, bright_y)
247+
// right:
248+
sgl.v2f(bright_x, tleft_y)
249+
sgl.v2f(bright_x, bright_y)
250+
// bottom:
251+
sgl.v2f(tleft_x, bright_y)
252+
sgl.v2f(bright_x, bright_y)
253+
// Note: the following line is deliberately commented, compare to draw_rect_empty/5;
254+
// sgl.end()
255+
}
256+
218257
// draw_rect_filled draws a filled rectangle.
219258
// `x`,`y` is the top-left corner of the rectangle.
220259
// `w` is the width, `h` is the height and `c` is the color of the fill.
260+
// Note: it is much more efficient to draw lots of filled rectangles one after the other,
261+
// without empty rectangles between them, than to draw a mix.
221262
pub fn (ctx &Context) draw_rect_filled(x f32, y f32, w f32, h f32, c Color) {
222263
$if macos {
223264
if ctx.native_rendering {
@@ -239,6 +280,25 @@ pub fn (ctx &Context) draw_rect_filled(x f32, y f32, w f32, h f32, c Color) {
239280
sgl.end()
240281
}
241282

283+
// draw_rect_filled_no_context draws a filled rectangle, but without saving/restoring the context.
284+
// It is intended to be used in loops, where you do manually: `sgl.begin_quads()` *before* the loop,
285+
// then draw many rectangles, then call manually `sgl.end()` *after* the loop.
286+
// `x`,`y` is the top-left corner of the rectangle.
287+
// `w` is the width, `h` is the height and `c` is the color of the fill.
288+
// Note: it is much more efficient to draw lots of filled rectangles one after the other,
289+
// without empty rectangles between them, than to draw a mix.
290+
pub fn (ctx &Context) draw_rect_filled_no_context(x f32, y f32, w f32, h f32, c Color) {
291+
// Note: the following line is deliberately commented, compare to draw_rect_empty/5;
292+
// sgl.begin_quads()
293+
sgl.c4b(c.r, c.g, c.b, c.a)
294+
sgl.v2f(x * ctx.scale, y * ctx.scale)
295+
sgl.v2f((x + w) * ctx.scale, y * ctx.scale)
296+
sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale)
297+
sgl.v2f(x * ctx.scale, (y + h) * ctx.scale)
298+
// Note: the following line is deliberately commented, compare to draw_rect_filled/5;
299+
// sgl.end()
300+
}
301+
242302
pub enum PaintStyle {
243303
fill
244304
stroke

0 commit comments

Comments
 (0)