diff --git a/examples/gg/grid_of_rectangles.v b/examples/gg/grid_of_rectangles.v new file mode 100644 index 0000000000..0a7ab15569 --- /dev/null +++ b/examples/gg/grid_of_rectangles.v @@ -0,0 +1,74 @@ +module main + +import gg +import sokol.sgl + +struct App { +mut: + gg &gg.Context = unsafe { nil } +} + +const cmax_x = 115 +const cmax_y = 81 +const cs = 10 + +const c_fg = gg.rgb(20, 30, 255) +const c_em = gg.rgb(255, 50, 0) + +fn main() { + mut app := &App{} + app.gg = gg.new_context( + bg_color: gg.white + width: cmax_x * cs + height: cmax_y * cs + create_window: true + window_title: 'Grid with many rectangles (drawn with no context)' + frame_fn: frame + user_data: app + ) + app.gg.run() +} + +fn frame(app &App) { + app.gg.begin() + // Note: this uses 2 separate loops, with each doing its own drawing for performance reasons. + // The underlying Sokol library can eliminate sgl begin/end calls, when multiple primitives of the + // same kind are drawn one after the other, without context changes. + // However, filled rectangles are drawn with quads, while empty rectangles are drawn with lines. + // 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, + // sokol can not batch the begin/end calls, and their overhead becomes much bigger. + // + // That is why: + // a) we use several loops here. + // b) we use a single sgl.begin_quads() before the first loop, and a single sgl.end() after it. + // c) we use draw_rect_filled_no_context/5 inside the loop, instead of draw_rect_filled/5 . + // e) we use a single sgl.begin_lines() before the second loop, and a single sgl.end() after it. + // f) we use draw_rect_empty_no_context/5 inside the second loop, instead of draw_rect_empty/5 . + // Note: the separation of the loops/kinds of draws, is several times more important for eliminating + // the performance overhead (12-15% CPU usage), compared to the use of draw_rect_filled_no_context + // vs draw_rect_filled (1-2% CPU usage), because of Sokol's optimisation. + + $if !do_not_draw_rect_filled ? { + sgl.begin_quads() + for y in 0 .. cmax_y { + for x in 0 .. cmax_x { + cy, cx := y * cs, x * cs + app.gg.draw_rect_filled_no_context(cx, cy, cs - 2, cs - 2, c_fg) + } + } + sgl.end() + } + + $if draw_rect_empty ? { + sgl.begin_lines() + for y in 0 .. cmax_y { + for x in 0 .. cmax_x { + cy, cx := y * cs, x * cs + app.gg.draw_rect_empty_no_context(cx + 2, cy + 2, cs - 3, cs - 3, c_em) + } + } + sgl.end() + } + + app.gg.end() +} diff --git a/vlib/gg/draw.c.v b/vlib/gg/draw.c.v index 2be29c080a..c5cc4fe2b3 100644 --- a/vlib/gg/draw.c.v +++ b/vlib/gg/draw.c.v @@ -184,6 +184,8 @@ pub fn (ctx &Context) draw_convex_poly(points []f32, c Color) { // draw_rect_empty draws the outline of a rectangle. // `x`,`y` is the top-left corner of the rectangle. // `w` is the width, `h` is the height and `c` is the color of the outline. +// Note: it is much more efficient to draw lots of empty rectangles one after the other, +// without filled rectangles between them, than to draw a mix. pub fn (ctx &Context) draw_rect_empty(x f32, y f32, w f32, h f32, c Color) { if c.a != 255 { 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) { sgl.end() } +// draw_rect_empty_no_context draws the outline of a rectangle, but without saving/restoring the context. +// It is intended to be used in loops, where you do manually: `sgl.begin_lines()` *before* the loop, +// then draw many rectangles, then call manually `sgl.end()` *after* the loop. +// `x`,`y` is the top-left corner of the rectangle. +// `w` is the width, `h` is the height and `c` is the color of the outline. +// Note: it is much more efficient to draw lots of empty rectangles one after the other, +// without filled rectangles between them, than to draw a mix. +pub fn (ctx &Context) draw_rect_empty_no_context(x f32, y f32, w f32, h f32, c Color) { + // The small offsets here, are to make sure, that the start and end points will be + // inside pixels, and not on their borders. That in turn, makes it much more likely + // that different OpenGL implementations will render them identically, for example + // Mesa, with `LIBGL_ALWAYS_SOFTWARE=1` renders the same as HD4000. + mut toffset := f32(0.1) + mut boffset := f32(-0.1) + tleft_x := toffset + x * ctx.scale + tleft_y := toffset + y * ctx.scale + bright_x := boffset + (x + w) * ctx.scale + bright_y := boffset + (y + h) * ctx.scale + // Note: the following line is deliberately commented, compare to draw_rect_empty/5; + // sgl.begin_lines() // more predictable, compared to sgl.begin_line_strip, at the price of more vertexes send + sgl.c4b(c.r, c.g, c.b, c.a) + // top: + sgl.v2f(tleft_x, tleft_y) + sgl.v2f(bright_x, tleft_y) + // left: + sgl.v2f(tleft_x, tleft_y) + sgl.v2f(tleft_x, bright_y) + // right: + sgl.v2f(bright_x, tleft_y) + sgl.v2f(bright_x, bright_y) + // bottom: + sgl.v2f(tleft_x, bright_y) + sgl.v2f(bright_x, bright_y) + // Note: the following line is deliberately commented, compare to draw_rect_empty/5; + // sgl.end() +} + // draw_rect_filled draws a filled rectangle. // `x`,`y` is the top-left corner of the rectangle. // `w` is the width, `h` is the height and `c` is the color of the fill. +// Note: it is much more efficient to draw lots of filled rectangles one after the other, +// without empty rectangles between them, than to draw a mix. pub fn (ctx &Context) draw_rect_filled(x f32, y f32, w f32, h f32, c Color) { $if macos { 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) { sgl.end() } +// draw_rect_filled_no_context draws a filled rectangle, but without saving/restoring the context. +// It is intended to be used in loops, where you do manually: `sgl.begin_quads()` *before* the loop, +// then draw many rectangles, then call manually `sgl.end()` *after* the loop. +// `x`,`y` is the top-left corner of the rectangle. +// `w` is the width, `h` is the height and `c` is the color of the fill. +// Note: it is much more efficient to draw lots of filled rectangles one after the other, +// without empty rectangles between them, than to draw a mix. +pub fn (ctx &Context) draw_rect_filled_no_context(x f32, y f32, w f32, h f32, c Color) { + // Note: the following line is deliberately commented, compare to draw_rect_empty/5; + // sgl.begin_quads() + sgl.c4b(c.r, c.g, c.b, c.a) + sgl.v2f(x * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, y * ctx.scale) + sgl.v2f((x + w) * ctx.scale, (y + h) * ctx.scale) + sgl.v2f(x * ctx.scale, (y + h) * ctx.scale) + // Note: the following line is deliberately commented, compare to draw_rect_filled/5; + // sgl.end() +} + pub enum PaintStyle { fill stroke diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 08e068c693..484ab59c3f 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -2306,6 +2306,23 @@ pub fn (expr Expr) pos() token.Pos { } } +pub fn (expr Expr) is_constant() bool { + return match expr { + IntegerLiteral, FloatLiteral, BoolLiteral, StringLiteral { + true + } + InfixExpr, CastExpr, ArrayInit { + true + } + UnsafeExpr { + expr.expr.is_constant() + } + else { + return false + } + } +} + pub fn (expr Expr) is_lvalue() bool { return match expr { Ident, CTempVar { true } diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index a7e2bfe80a..e9fc1495ab 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -661,8 +661,13 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', } } if mut left is ast.Ident && left.info is ast.IdentVar { - if left.info.is_static && right_sym.kind == .map { - c.error('maps cannot be static', left.pos) + if left.info.is_static { + if right_sym.kind == .map { + c.error('maps cannot be static', left.pos) + } else if !right.is_constant() { + c.error('cannot initialized static variable with non-constant value', + left.pos) + } } } // Single side check diff --git a/vlib/v/checker/tests/static_init_err.out b/vlib/v/checker/tests/static_init_err.out new file mode 100644 index 0000000000..4d5f81d29d --- /dev/null +++ b/vlib/v/checker/tests/static_init_err.out @@ -0,0 +1,41 @@ +vlib/v/checker/tests/static_init_err.vv:10:2: warning: function `foo` must be called from an `unsafe` block + 8 | + 9 | fn main() { + 10 | foo() + | ~~~~~ + 11 | } +vlib/v/checker/tests/static_init_err.vv:4:9: warning: unused variable: `aa` + 2 | fn foo() { + 3 | b := 23 + 4 | static aa := b + | ~~ + 5 | static bb := 1 + 1 + 6 | static cc := ''.str() +vlib/v/checker/tests/static_init_err.vv:5:9: warning: unused variable: `bb` + 3 | b := 23 + 4 | static aa := b + 5 | static bb := 1 + 1 + | ~~ + 6 | static cc := ''.str() + 7 | } +vlib/v/checker/tests/static_init_err.vv:6:9: warning: unused variable: `cc` + 4 | static aa := b + 5 | static bb := 1 + 1 + 6 | static cc := ''.str() + | ~~ + 7 | } + 8 | +vlib/v/checker/tests/static_init_err.vv:4:9: error: cannot initialized static variable with non-constant value + 2 | fn foo() { + 3 | b := 23 + 4 | static aa := b + | ~~ + 5 | static bb := 1 + 1 + 6 | static cc := ''.str() +vlib/v/checker/tests/static_init_err.vv:6:9: error: cannot initialized static variable with non-constant value + 4 | static aa := b + 5 | static bb := 1 + 1 + 6 | static cc := ''.str() + | ~~ + 7 | } + 8 | diff --git a/vlib/v/checker/tests/static_init_err.vv b/vlib/v/checker/tests/static_init_err.vv new file mode 100644 index 0000000000..a90c397916 --- /dev/null +++ b/vlib/v/checker/tests/static_init_err.vv @@ -0,0 +1,11 @@ +@[unsafe] +fn foo() { + b := 23 + static aa := b + static bb := 1 + 1 + static cc := ''.str() +} + +fn main() { + foo() +}